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
, 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."));
211 m_lastError
= wxPROTO_NOERR
;
212 sock
= sockSrv
->Accept(true);
225 if ( !CheckCommand(wxT("ABOR"), '4') )
228 return CheckResult('2');
232 // ----------------------------------------------------------------------------
233 // Send command to FTP server
234 // ----------------------------------------------------------------------------
236 char wxFTP::SendCommand(const wxString
& command
)
240 m_lastError
= wxPROTO_STREAMING
;
244 wxString tmp_str
= command
+ wxT("\r\n");
245 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
246 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
248 m_lastError
= wxPROTO_NETERR
;
252 // don't show the passwords in the logs (even in debug ones)
253 wxString cmd
, password
;
254 if ( command
.Upper().StartsWith(wxT("PASS "), &password
) )
256 cmd
<< wxT("PASS ") << wxString(wxT('*'), password
.length());
265 m_lastError
= wxPROTO_NOERR
;
269 // ----------------------------------------------------------------------------
270 // Receive servers reply
271 // ----------------------------------------------------------------------------
273 char wxFTP::GetResult()
275 // if we've already had a read or write timeout error, the connection is
276 // probably toast, so don't bother, it just wastes the users time
277 if ( m_bEncounteredError
)
282 // m_lastResult will contain the entire server response, possibly on
284 m_lastResult
.clear();
286 // we handle multiline replies here according to RFC 959: it says that a
287 // reply may either be on 1 line of the form "xyz ..." or on several lines
288 // in whuch case it looks like
292 // and the intermeidate lines may start with xyz or not
293 bool badReply
= false;
294 bool firstLine
= true;
295 bool endOfReply
= false;
296 while ( !endOfReply
&& !badReply
)
299 m_lastError
= ReadLine(this,line
);
302 m_bEncounteredError
= true;
308 if ( !m_lastResult
.empty() )
310 // separate from last line
311 m_lastResult
+= wxT('\n');
314 m_lastResult
+= line
;
316 // unless this is an intermediate line of a multiline reply, it must
317 // contain the code in the beginning and '-' or ' ' following it
318 if ( line
.Len() < LEN_CODE
+ 1 )
325 else // line has at least 4 chars
327 // this is the char which tells us what we're dealing with
328 wxChar chMarker
= line
.GetChar(LEN_CODE
);
332 code
= wxString(line
, LEN_CODE
);
349 else // subsequent line of multiline reply
351 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
353 if ( chMarker
== wxT(' ') )
364 wxLogDebug(wxT("Broken FTP server: '%s' is not a valid reply."),
365 m_lastResult
.c_str());
367 m_lastError
= wxPROTO_PROTERR
;
372 m_lastError
= wxPROTO_NOERR
;
374 // if we got here we must have a non empty code string
375 return (char)code
[0u];
378 // ----------------------------------------------------------------------------
379 // wxFTP simple commands
380 // ----------------------------------------------------------------------------
382 bool wxFTP::SetTransferMode(TransferMode transferMode
)
384 if ( transferMode
== m_currentTransfermode
)
391 switch ( transferMode
)
394 wxFAIL_MSG(wxT("unknown FTP transfer mode"));
406 if ( !DoSimpleCommand(wxT("TYPE"), mode
) )
408 wxLogError(_("Failed to set FTP transfer mode to %s."),
409 (transferMode
== ASCII
? _("ASCII") : _("binary")));
414 // If we get here the operation has been successfully completed
415 // Set the status-member
416 m_currentTransfermode
= transferMode
;
421 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
423 wxString fullcmd
= command
;
426 fullcmd
<< wxT(' ') << arg
;
429 if ( !CheckCommand(fullcmd
, '2') )
431 wxLogDebug(wxT("FTP command '%s' failed."), fullcmd
.c_str());
432 m_lastError
= wxPROTO_NETERR
;
437 m_lastError
= wxPROTO_NOERR
;
441 bool wxFTP::ChDir(const wxString
& dir
)
443 // some servers might not understand ".." if they use different directory
444 // tree conventions, but they always understand CDUP - should we use it if
445 // dir == ".."? OTOH, do such servers (still) exist?
447 return DoSimpleCommand(wxT("CWD"), dir
);
450 bool wxFTP::MkDir(const wxString
& dir
)
452 return DoSimpleCommand(wxT("MKD"), dir
);
455 bool wxFTP::RmDir(const wxString
& dir
)
457 return DoSimpleCommand(wxT("RMD"), dir
);
460 wxString
wxFTP::Pwd()
464 if ( CheckCommand(wxT("PWD"), '2') )
466 // the result is at least that long if CheckCommand() succeeded
467 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
468 if ( *p
!= wxT('"') )
470 wxLogDebug(wxT("Missing starting quote in reply for PWD: %s"),
471 wxString(p
, m_lastResult
.end()));
475 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
477 if ( *p
== wxT('"') )
479 // check if the quote is doubled
481 if ( !*p
|| *p
!= wxT('"') )
483 // no, this is the end
486 //else: yes, it is: this is an embedded quote in the
487 // filename, treat as normal char
495 wxLogDebug(wxT("Missing ending quote in reply for PWD: %s"),
496 m_lastResult
.c_str() + LEN_CODE
+ 1);
502 m_lastError
= wxPROTO_PROTERR
;
503 wxLogDebug(wxT("FTP PWD command failed."));
509 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
513 str
= wxT("RNFR ") + src
;
514 if ( !CheckCommand(str
, '3') )
517 str
= wxT("RNTO ") + dst
;
519 return CheckCommand(str
, '2');
522 bool wxFTP::RmFile(const wxString
& path
)
525 str
= wxT("DELE ") + path
;
527 return CheckCommand(str
, '2');
530 // ----------------------------------------------------------------------------
531 // wxFTP port methods
532 // ----------------------------------------------------------------------------
534 wxSocketBase
*wxFTP::GetPort()
537 PASSIVE: Client sends a "PASV" to the server. The server responds with
538 an address and port number which it will be listening on. Then
539 the client connects to the server at the specified address and
542 ACTIVE: Client sends the server a PORT command which includes an
543 address and port number which the client will be listening on.
544 The server then connects to the client at that address and
548 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
551 m_bEncounteredError
= true;
555 // Now set the time for the new socket to the default or user selected
557 socket
->SetTimeout(m_uiDefaultTimeout
);
562 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
563 const wxIPV4address
& addrNew
)
565 // Just fills in the return value with the local IP
566 // address of the current socket. Also it fill in the
567 // PORT which the client will be listening on
569 wxString addrIP
= addrLocal
.IPAddress();
570 int portNew
= addrNew
.Service();
572 // We need to break the PORT number in bytes
573 addrIP
.Replace(wxT("."), wxT(","));
575 << wxString::Format(wxT("%d"), portNew
>> 8) << wxT(',')
576 << wxString::Format(wxT("%d"), portNew
& 0xff);
578 // Now we have a value like "10,0,0,1,5,23"
582 wxSocketBase
*wxFTP::GetActivePort()
584 // we need an address to listen on
585 wxIPV4address addrNew
, addrLocal
;
587 addrNew
.AnyAddress();
588 addrNew
.Service(0); // pick an open port number.
590 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
593 // We use Ok() here to see if everything is ok
594 m_lastError
= wxPROTO_PROTERR
;
599 //gets the new address, actually it is just the port number
600 sockSrv
->GetLocal(addrNew
);
602 // Now we create the argument of the PORT command, we send in both
603 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
604 // value in addrLocal
605 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
606 if ( !DoSimpleCommand(wxT("PORT"), port
) )
608 m_lastError
= wxPROTO_PROTERR
;
610 wxLogError(_("The FTP server doesn't support the PORT command."));
614 m_lastError
= wxPROTO_NOERR
;
615 sockSrv
->Notify(false); // Don't send any events
619 wxSocketBase
*wxFTP::GetPassivePort()
621 if ( !DoSimpleCommand(wxT("PASV")) )
623 m_lastError
= wxPROTO_PROTERR
;
624 wxLogError(_("The FTP server doesn't support passive mode."));
628 size_t addrStart
= m_lastResult
.find(wxT('('));
629 size_t addrEnd
= (addrStart
== wxString::npos
)
631 : m_lastResult
.find(wxT(')'), addrStart
);
633 if ( addrEnd
== wxString::npos
)
635 m_lastError
= wxPROTO_PROTERR
;
639 // get the port number and address
641 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
642 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
643 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
645 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
646 (wxUint16
)a
[3] << 16 |
647 (wxUint16
)a
[4] << 8 |
649 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
652 addr
.Hostname(hostaddr
);
655 wxSocketClient
*client
= new wxSocketClient();
656 if ( !client
->Connect(addr
) )
658 m_lastError
= wxPROTO_CONNERR
;
663 client
->Notify(false);
665 m_lastError
= wxPROTO_NOERR
;
670 // ----------------------------------------------------------------------------
671 // wxFTP download and upload
672 // ----------------------------------------------------------------------------
674 class wxInputFTPStream
: public wxSocketInputStream
677 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
678 : wxSocketInputStream(*sock
)
681 // socket timeout automatically set in GetPort function
684 virtual ~wxInputFTPStream()
686 delete m_i_socket
; // keep at top
688 // when checking the result, the stream will
689 // almost always show an error, even if the file was
690 // properly transfered, thus, lets just grab the result
692 // we are looking for "226 transfer completed"
693 char code
= m_ftp
->GetResult();
696 // it was a good transfer.
698 m_ftp
->m_streaming
= false;
704 // the connection is probably toast. issue an abort, and
705 // then a close. there won't be any more waiting
706 // for this connection
711 // There was a problem with the transfer and the server
712 // has acknowledged it. If we issue an "ABORT" now, the user
713 // would get the "226" for the abort and think the xfer was
714 // complete, thus, don't do anything here, just return
719 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream
);
722 class wxOutputFTPStream
: public wxSocketOutputStream
725 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
726 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
730 virtual ~wxOutputFTPStream(void)
734 // close data connection first, this will generate "transfer
739 m_ftp
->GetResult(); // save result so user can get to it
741 m_ftp
->m_streaming
= false;
745 // abort data connection first
748 // and close it after
755 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream
);
758 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
760 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
762 m_lastError
= wxPROTO_CONNERR
;
766 wxSocketBase
*sock
= GetPort();
770 m_lastError
= wxPROTO_NETERR
;
774 wxString tmp_str
= wxT("RETR ") + wxURI::Unescape(path
);
775 if ( !CheckCommand(tmp_str
, '1') )
778 sock
= AcceptIfActive(sock
);
781 m_lastError
= wxPROTO_CONNERR
;
785 sock
->SetFlags(wxSOCKET_WAITALL
);
789 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
791 m_lastError
= wxPROTO_NOERR
;
795 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
797 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
799 m_lastError
= wxPROTO_CONNERR
;
803 wxSocketBase
*sock
= GetPort();
805 wxString tmp_str
= wxT("STOR ") + path
;
806 if ( !CheckCommand(tmp_str
, '1') )
809 sock
= AcceptIfActive(sock
);
813 m_lastError
= wxPROTO_NOERR
;
814 return new wxOutputFTPStream(this, sock
);
817 // ----------------------------------------------------------------------------
818 // FTP directory listing
819 // ----------------------------------------------------------------------------
821 bool wxFTP::GetList(wxArrayString
& files
,
822 const wxString
& wildcard
,
825 wxSocketBase
*sock
= GetPort();
827 m_lastError
= wxPROTO_NETERR
;
831 // NLST : List of Filenames (including Directory's !)
832 // LIST : depending on BS of FTP-Server
833 // - Unix : result like "ls" command
834 // - Windows : like "dir" command
836 wxString
line(details
? wxT("LIST") : wxT("NLST"));
837 if ( !wildcard
.empty() )
839 line
<< wxT(' ') << wildcard
;
842 if ( !CheckCommand(line
, '1') )
844 m_lastError
= wxPROTO_PROTERR
;
845 wxLogDebug(wxT("FTP 'LIST' command returned unexpected result from server"));
850 sock
= AcceptIfActive(sock
);
852 m_lastError
= wxPROTO_CONNERR
;
857 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
864 // the file list should be terminated by "226 Transfer complete""
865 m_lastError
= wxPROTO_NOERR
;
866 return CheckResult('2');
869 bool wxFTP::FileExists(const wxString
& fileName
)
871 // This function checks if the file specified in fileName exists in the
872 // current dir. It does so by simply doing an NLST (via GetList).
873 // If this succeeds (and the list is not empty) the file exists.
876 wxArrayString fileList
;
878 if ( GetList(fileList
, fileName
, false) )
880 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
881 // displays this behaviour when queried on a nonexistent file:
882 // NLST this_file_does_not_exist
883 // 150 Opening ASCII data connection for directory listing
884 // (no data transferred)
885 // 226 Transfer complete
886 // Here wxFTP::GetList(...) will succeed but it will return an empty
888 retval
= !fileList
.IsEmpty();
894 // ----------------------------------------------------------------------------
896 // ----------------------------------------------------------------------------
898 int wxFTP::GetFileSize(const wxString
& fileName
)
900 // return the filesize of the given file if possible
901 // return -1 otherwise (predominantly if file doesn't exist
906 // Check for existance of file via wxFTP::FileExists(...)
907 if ( FileExists(fileName
) )
911 // First try "SIZE" command using BINARY(IMAGE) transfermode
912 // Especially UNIX ftp-servers distinguish between the different
913 // transfermodes and reports different filesizes accordingly.
914 // The BINARY size is the interesting one: How much memory
915 // will we need to hold this file?
916 TransferMode oldTransfermode
= m_currentTransfermode
;
917 SetTransferMode(BINARY
);
918 command
<< wxT("SIZE ") << fileName
;
920 bool ok
= CheckCommand(command
, '2');
924 // The answer should be one line: "213 <filesize>\n"
925 // 213 is File Status (STD9)
926 // "SIZE" is not described anywhere..? It works on most servers
928 if ( wxSscanf(GetLastResult().c_str(), wxT("%i %i"),
929 &statuscode
, &filesize
) == 2 )
931 // We've gotten a good reply.
936 // Something bad happened.. A "2yz" reply with no size
942 // Set transfermode back to the original. Only the "SIZE"-command
943 // is dependant on transfermode
944 if ( oldTransfermode
!= NONE
)
946 SetTransferMode(oldTransfermode
);
949 // this is not a direct else clause.. The size command might return an
950 // invalid "2yz" reply
953 // The server didn't understand the "SIZE"-command or it
954 // returned an invalid reply.
955 // We now try to get details for the file with a "LIST"-command
956 // and then parse the output from there..
957 wxArrayString fileList
;
958 if ( GetList(fileList
, fileName
, true) )
960 if ( !fileList
.IsEmpty() )
962 // We _should_ only get one line in return, but just to be
963 // safe we run through the line(s) returned and look for a
964 // substring containing the name we are looking for. We
965 // stop the iteration at the first occurrence of the
966 // filename. The search is not case-sensitive.
967 const size_t numFiles
= fileList
.size();
969 for ( i
= 0; i
< fileList
.GetCount(); i
++ )
971 if ( fileList
[i
].Upper().Contains(fileName
.Upper()) )
977 // The index i points to the first occurrence of
978 // fileName in the array Now we have to find out what
979 // format the LIST has returned. There are two
980 // "schools": Unix-like
982 // '-rw-rw-rw- owner group size month day time filename'
986 // 'date size filename'
988 // check if the first character is '-'. This would
989 // indicate Unix-style (this also limits this function
990 // to searching for files, not directories)
991 if ( fileList
[i
].Mid(0, 1) == wxT("-") )
994 if ( wxSscanf(fileList
[i
].c_str(),
995 wxT("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
998 // Hmm... Invalid response
999 wxLogDebug(wxT("Invalid LIST response"));
1002 else // Windows-style response (?)
1004 if ( wxSscanf(fileList
[i
].c_str(),
1005 wxT("%*s %*s %i %*s"),
1008 // something bad happened..?
1009 wxLogDebug(wxT("Invalid or unknown LIST response"));
1018 // filesize might still be -1 when exiting
1022 #endif // wxUSE_PROTOCOL_FTP