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"
53 // ----------------------------------------------------------------------------
55 // ----------------------------------------------------------------------------
57 // the length of FTP status code (3 digits)
58 static const size_t LEN_CODE
= 3;
60 // ----------------------------------------------------------------------------
62 // ----------------------------------------------------------------------------
64 IMPLEMENT_DYNAMIC_CLASS(wxFTP
, wxProtocol
)
65 IMPLEMENT_PROTOCOL(wxFTP
, wxT("ftp"), wxT("ftp"), true)
67 // ============================================================================
69 // ============================================================================
71 // ----------------------------------------------------------------------------
72 // wxFTP constructor and destructor
73 // ----------------------------------------------------------------------------
78 m_currentTransfermode
= NONE
;
80 m_username
= wxT("anonymous");
81 m_password
<< wxGetUserId() << wxT('@') << wxGetFullHostName();
84 SetFlags(wxSOCKET_NOWAIT
);
86 m_bEncounteredError
= false;
93 // if we are streaming, this will issue
94 // an FTP ABORT command, to tell the server we are aborting
98 // now this issues a "QUIT" command to tell the server we are
102 // ----------------------------------------------------------------------------
103 // wxFTP connect and login methods
104 // ----------------------------------------------------------------------------
106 bool wxFTP::Connect(const wxSockAddress
& addr
, bool WXUNUSED(wait
))
108 if ( !wxProtocol::Connect(addr
) )
110 m_lastError
= wxPROTO_NETERR
;
116 m_lastError
= wxPROTO_CONNERR
;
120 // we should have 220 welcome message
121 if ( !CheckResult('2') )
128 command
.Printf(wxT("USER %s"), m_username
.c_str());
129 char rc
= SendCommand(command
);
132 // 230 return: user accepted without password
133 m_lastError
= wxPROTO_NOERR
;
139 m_lastError
= wxPROTO_CONNERR
;
144 command
.Printf(wxT("PASS %s"), m_password
.c_str());
145 if ( !CheckCommand(command
, '2') )
147 m_lastError
= wxPROTO_CONNERR
;
152 m_lastError
= wxPROTO_NOERR
;
156 bool wxFTP::Connect(const wxString
& host
, unsigned short port
)
163 else if (!addr
.Service(wxT("ftp")))
166 return Connect(addr
);
173 m_lastError
= wxPROTO_STREAMING
;
179 if ( !CheckCommand(wxT("QUIT"), '2') )
181 m_lastError
= wxPROTO_CONNERR
;
182 wxLogDebug(wxT("Failed to close connection gracefully."));
186 return wxSocketClient::Close();
189 // ============================================================================
191 // ============================================================================
193 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
198 // now wait for a connection from server
199 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
200 if ( !sockSrv
->WaitForAccept() )
202 m_lastError
= wxPROTO_CONNERR
;
203 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
208 m_lastError
= wxPROTO_NOERR
;
209 sock
= sockSrv
->Accept(true);
222 if ( !CheckCommand(wxT("ABOR"), '4') )
225 return CheckResult('2');
229 // ----------------------------------------------------------------------------
230 // Send command to FTP server
231 // ----------------------------------------------------------------------------
233 char wxFTP::SendCommand(const wxString
& command
)
237 m_lastError
= wxPROTO_STREAMING
;
241 wxString tmp_str
= command
+ wxT("\r\n");
242 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
243 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
245 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(wxT("PASS "), &password
) )
253 cmd
<< wxT("PASS ") << wxString(wxT('*'), password
.length());
262 m_lastError
= wxPROTO_NOERR
;
266 // ----------------------------------------------------------------------------
267 // Receive servers reply
268 // ----------------------------------------------------------------------------
270 char wxFTP::GetResult()
272 // if we've already had a read or write timeout error, the connection is
273 // probably toast, so don't bother, it just wastes the users time
274 if ( m_bEncounteredError
)
279 // m_lastResult will contain the entire server response, possibly on
281 m_lastResult
.clear();
283 // we handle multiline replies here according to RFC 959: it says that a
284 // reply may either be on 1 line of the form "xyz ..." or on several lines
285 // in whuch case it looks like
289 // and the intermeidate lines may start with xyz or not
290 bool badReply
= false;
291 bool firstLine
= true;
292 bool endOfReply
= false;
293 while ( !endOfReply
&& !badReply
)
296 m_lastError
= ReadLine(this,line
);
299 m_bEncounteredError
= true;
305 if ( !m_lastResult
.empty() )
307 // separate from last line
308 m_lastResult
+= wxT('\n');
311 m_lastResult
+= line
;
313 // unless this is an intermediate line of a multiline reply, it must
314 // contain the code in the beginning and '-' or ' ' following it
315 if ( line
.Len() < LEN_CODE
+ 1 )
322 else // line has at least 4 chars
324 // this is the char which tells us what we're dealing with
325 wxChar chMarker
= line
.GetChar(LEN_CODE
);
329 code
= wxString(line
, LEN_CODE
);
346 else // subsequent line of multiline reply
348 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
350 if ( chMarker
== wxT(' ') )
361 wxLogDebug(wxT("Broken FTP server: '%s' is not a valid reply."),
362 m_lastResult
.c_str());
364 m_lastError
= wxPROTO_PROTERR
;
369 m_lastError
= wxPROTO_NOERR
;
371 // if we got here we must have a non empty code string
372 return (char)code
[0u];
375 // ----------------------------------------------------------------------------
376 // wxFTP simple commands
377 // ----------------------------------------------------------------------------
379 bool wxFTP::SetTransferMode(TransferMode transferMode
)
381 if ( transferMode
== m_currentTransfermode
)
388 switch ( transferMode
)
391 wxFAIL_MSG(wxT("unknown FTP transfer mode"));
403 if ( !DoSimpleCommand(wxT("TYPE"), mode
) )
405 wxLogError(_("Failed to set FTP transfer mode to %s."),
406 (transferMode
== ASCII
? _("ASCII") : _("binary")));
411 // If we get here the operation has been successfully completed
412 // Set the status-member
413 m_currentTransfermode
= transferMode
;
418 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
420 wxString fullcmd
= command
;
423 fullcmd
<< wxT(' ') << arg
;
426 if ( !CheckCommand(fullcmd
, '2') )
428 wxLogDebug(wxT("FTP command '%s' failed."), fullcmd
.c_str());
429 m_lastError
= wxPROTO_NETERR
;
434 m_lastError
= wxPROTO_NOERR
;
438 bool wxFTP::ChDir(const wxString
& dir
)
440 // some servers might not understand ".." if they use different directory
441 // tree conventions, but they always understand CDUP - should we use it if
442 // dir == ".."? OTOH, do such servers (still) exist?
444 return DoSimpleCommand(wxT("CWD"), dir
);
447 bool wxFTP::MkDir(const wxString
& dir
)
449 return DoSimpleCommand(wxT("MKD"), dir
);
452 bool wxFTP::RmDir(const wxString
& dir
)
454 return DoSimpleCommand(wxT("RMD"), dir
);
457 wxString
wxFTP::Pwd()
461 if ( CheckCommand(wxT("PWD"), '2') )
463 // the result is at least that long if CheckCommand() succeeded
464 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
465 if ( *p
!= wxT('"') )
467 wxLogDebug(wxT("Missing starting quote in reply for PWD: %s"),
468 wxString(p
, m_lastResult
.end()));
472 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
474 if ( *p
== wxT('"') )
476 // check if the quote is doubled
478 if ( !*p
|| *p
!= wxT('"') )
480 // no, this is the end
483 //else: yes, it is: this is an embedded quote in the
484 // filename, treat as normal char
492 wxLogDebug(wxT("Missing ending quote in reply for PWD: %s"),
493 m_lastResult
.c_str() + LEN_CODE
+ 1);
499 m_lastError
= wxPROTO_PROTERR
;
500 wxLogDebug(wxT("FTP PWD command failed."));
506 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
510 str
= wxT("RNFR ") + src
;
511 if ( !CheckCommand(str
, '3') )
514 str
= wxT("RNTO ") + dst
;
516 return CheckCommand(str
, '2');
519 bool wxFTP::RmFile(const wxString
& path
)
522 str
= wxT("DELE ") + path
;
524 return CheckCommand(str
, '2');
527 // ----------------------------------------------------------------------------
528 // wxFTP port methods
529 // ----------------------------------------------------------------------------
531 wxSocketBase
*wxFTP::GetPort()
534 PASSIVE: Client sends a "PASV" to the server. The server responds with
535 an address and port number which it will be listening on. Then
536 the client connects to the server at the specified address and
539 ACTIVE: Client sends the server a PORT command which includes an
540 address and port number which the client will be listening on.
541 The server then connects to the client at that address and
545 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
548 m_bEncounteredError
= true;
552 // Now set the time for the new socket to the default or user selected
554 socket
->SetTimeout(m_uiDefaultTimeout
);
559 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
560 const wxIPV4address
& addrNew
)
562 // Just fills in the return value with the local IP
563 // address of the current socket. Also it fill in the
564 // PORT which the client will be listening on
566 wxString addrIP
= addrLocal
.IPAddress();
567 int portNew
= addrNew
.Service();
569 // We need to break the PORT number in bytes
570 addrIP
.Replace(wxT("."), wxT(","));
572 << wxString::Format(wxT("%d"), portNew
>> 8) << wxT(',')
573 << wxString::Format(wxT("%d"), portNew
& 0xff);
575 // Now we have a value like "10,0,0,1,5,23"
579 wxSocketBase
*wxFTP::GetActivePort()
581 // we need an address to listen on
582 wxIPV4address addrNew
, addrLocal
;
584 addrNew
.AnyAddress();
585 addrNew
.Service(0); // pick an open port number.
587 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
588 if (!sockSrv
->IsOk())
590 // We use IsOk() here to see if everything is ok
591 m_lastError
= wxPROTO_PROTERR
;
596 //gets the new address, actually it is just the port number
597 sockSrv
->GetLocal(addrNew
);
599 // Now we create the argument of the PORT command, we send in both
600 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
601 // value in addrLocal
602 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
603 if ( !DoSimpleCommand(wxT("PORT"), port
) )
605 m_lastError
= wxPROTO_PROTERR
;
607 wxLogError(_("The FTP server doesn't support the PORT command."));
611 m_lastError
= wxPROTO_NOERR
;
612 sockSrv
->Notify(false); // Don't send any events
616 wxSocketBase
*wxFTP::GetPassivePort()
618 if ( !DoSimpleCommand(wxT("PASV")) )
620 m_lastError
= wxPROTO_PROTERR
;
621 wxLogError(_("The FTP server doesn't support passive mode."));
625 size_t addrStart
= m_lastResult
.find(wxT('('));
626 size_t addrEnd
= (addrStart
== wxString::npos
)
628 : m_lastResult
.find(wxT(')'), addrStart
);
630 if ( addrEnd
== wxString::npos
)
632 m_lastError
= wxPROTO_PROTERR
;
636 // get the port number and address
638 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
639 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
640 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
642 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
643 (wxUint16
)a
[3] << 16 |
644 (wxUint16
)a
[4] << 8 |
646 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
649 addr
.Hostname(hostaddr
);
652 wxSocketClient
*client
= new wxSocketClient();
653 if ( !client
->Connect(addr
) )
655 m_lastError
= wxPROTO_CONNERR
;
660 client
->Notify(false);
662 m_lastError
= wxPROTO_NOERR
;
667 // ----------------------------------------------------------------------------
668 // wxFTP download and upload
669 // ----------------------------------------------------------------------------
671 class wxInputFTPStream
: public wxSocketInputStream
674 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
675 : wxSocketInputStream(*sock
)
678 // socket timeout automatically set in GetPort function
681 virtual ~wxInputFTPStream()
683 delete m_i_socket
; // keep at top
685 // when checking the result, the stream will
686 // almost always show an error, even if the file was
687 // properly transferred, thus, let's just grab the result
689 // we are looking for "226 transfer completed"
690 char code
= m_ftp
->GetResult();
693 // it was a good transfer.
695 m_ftp
->m_streaming
= false;
701 // the connection is probably toast. issue an abort, and
702 // then a close. there won't be any more waiting
703 // for this connection
708 // There was a problem with the transfer and the server
709 // has acknowledged it. If we issue an "ABORT" now, the user
710 // would get the "226" for the abort and think the xfer was
711 // complete, thus, don't do anything here, just return
716 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream
);
719 class wxOutputFTPStream
: public wxSocketOutputStream
722 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
723 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
727 virtual ~wxOutputFTPStream(void)
731 // close data connection first, this will generate "transfer
736 m_ftp
->GetResult(); // save result so user can get to it
738 m_ftp
->m_streaming
= false;
742 // abort data connection first
745 // and close it after
752 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream
);
755 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
757 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
759 m_lastError
= wxPROTO_CONNERR
;
763 wxSocketBase
*sock
= GetPort();
767 m_lastError
= wxPROTO_NETERR
;
771 wxString tmp_str
= wxT("RETR ") + wxURI::Unescape(path
);
772 if ( !CheckCommand(tmp_str
, '1') )
775 sock
= AcceptIfActive(sock
);
778 m_lastError
= wxPROTO_CONNERR
;
782 sock
->SetFlags(wxSOCKET_WAITALL
);
786 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
788 m_lastError
= wxPROTO_NOERR
;
792 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
794 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
796 m_lastError
= wxPROTO_CONNERR
;
800 wxSocketBase
*sock
= GetPort();
802 wxString tmp_str
= wxT("STOR ") + path
;
803 if ( !CheckCommand(tmp_str
, '1') )
806 sock
= AcceptIfActive(sock
);
810 m_lastError
= wxPROTO_NOERR
;
811 return new wxOutputFTPStream(this, sock
);
814 // ----------------------------------------------------------------------------
815 // FTP directory listing
816 // ----------------------------------------------------------------------------
818 bool wxFTP::GetList(wxArrayString
& files
,
819 const wxString
& wildcard
,
822 wxSocketBase
*sock
= GetPort();
824 m_lastError
= wxPROTO_NETERR
;
828 // NLST : List of Filenames (including Directory's !)
829 // LIST : depending on BS of FTP-Server
830 // - Unix : result like "ls" command
831 // - Windows : like "dir" command
833 wxString
line(details
? wxT("LIST") : wxT("NLST"));
834 if ( !wildcard
.empty() )
836 line
<< wxT(' ') << wildcard
;
839 if ( !CheckCommand(line
, '1') )
841 m_lastError
= wxPROTO_PROTERR
;
842 wxLogDebug(wxT("FTP 'LIST' command returned unexpected result from server"));
847 sock
= AcceptIfActive(sock
);
849 m_lastError
= wxPROTO_CONNERR
;
854 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
861 // the file list should be terminated by "226 Transfer complete""
862 m_lastError
= wxPROTO_NOERR
;
863 return CheckResult('2');
866 bool wxFTP::FileExists(const wxString
& fileName
)
868 // This function checks if the file specified in fileName exists in the
869 // current dir. It does so by simply doing an NLST (via GetList).
870 // If this succeeds (and the list is not empty) the file exists.
873 wxArrayString fileList
;
875 if ( GetList(fileList
, fileName
, false) )
877 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
878 // displays this behaviour when queried on a nonexistent file:
879 // NLST this_file_does_not_exist
880 // 150 Opening ASCII data connection for directory listing
881 // (no data transferred)
882 // 226 Transfer complete
883 // Here wxFTP::GetList(...) will succeed but it will return an empty
885 retval
= !fileList
.IsEmpty();
891 // ----------------------------------------------------------------------------
893 // ----------------------------------------------------------------------------
895 int wxFTP::GetFileSize(const wxString
& fileName
)
897 // return the filesize of the given file if possible
898 // return -1 otherwise (predominantly if file doesn't exist
903 // Check for existence of file via wxFTP::FileExists(...)
904 if ( FileExists(fileName
) )
908 // First try "SIZE" command using BINARY(IMAGE) transfermode
909 // Especially UNIX ftp-servers distinguish between the different
910 // transfermodes and reports different filesizes accordingly.
911 // The BINARY size is the interesting one: How much memory
912 // will we need to hold this file?
913 TransferMode oldTransfermode
= m_currentTransfermode
;
914 SetTransferMode(BINARY
);
915 command
<< wxT("SIZE ") << fileName
;
917 bool ok
= CheckCommand(command
, '2');
921 // The answer should be one line: "213 <filesize>\n"
922 // 213 is File Status (STD9)
923 // "SIZE" is not described anywhere..? It works on most servers
925 if ( wxSscanf(GetLastResult().c_str(), wxT("%i %i"),
926 &statuscode
, &filesize
) == 2 )
928 // We've gotten a good reply.
933 // Something bad happened.. A "2yz" reply with no size
939 // Set transfermode back to the original. Only the "SIZE"-command
940 // is dependent on transfermode
941 if ( oldTransfermode
!= NONE
)
943 SetTransferMode(oldTransfermode
);
946 // this is not a direct else clause.. The size command might return an
947 // invalid "2yz" reply
950 // The server didn't understand the "SIZE"-command or it
951 // returned an invalid reply.
952 // We now try to get details for the file with a "LIST"-command
953 // and then parse the output from there..
954 wxArrayString fileList
;
955 if ( GetList(fileList
, fileName
, true) )
957 if ( !fileList
.IsEmpty() )
959 // We _should_ only get one line in return, but just to be
960 // safe we run through the line(s) returned and look for a
961 // substring containing the name we are looking for. We
962 // stop the iteration at the first occurrence of the
963 // filename. The search is not case-sensitive.
964 const size_t numFiles
= fileList
.size();
966 for ( i
= 0; i
< fileList
.GetCount(); i
++ )
968 if ( fileList
[i
].Upper().Contains(fileName
.Upper()) )
974 // The index i points to the first occurrence of
975 // fileName in the array Now we have to find out what
976 // format the LIST has returned. There are two
977 // "schools": Unix-like
979 // '-rw-rw-rw- owner group size month day time filename'
983 // 'date size filename'
985 // check if the first character is '-'. This would
986 // indicate Unix-style (this also limits this function
987 // to searching for files, not directories)
988 if ( fileList
[i
].Mid(0, 1) == wxT("-") )
991 if ( wxSscanf(fileList
[i
].c_str(),
992 wxT("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
995 // Hmm... Invalid response
996 wxLogDebug(wxT("Invalid LIST response"));
999 else // Windows-style response (?)
1001 if ( wxSscanf(fileList
[i
].c_str(),
1002 wxT("%*s %*s %i %*s"),
1005 // something bad happened..?
1006 wxLogDebug(wxT("Invalid or unknown LIST response"));
1015 // filesize might still be -1 when exiting
1019 #endif // wxUSE_PROTOCOL_FTP