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
)
162 addr
.Service(wxT("ftp"));
164 return Connect(addr
);
171 m_lastError
= wxPROTO_STREAMING
;
177 if ( !CheckCommand(wxT("QUIT"), '2') )
179 m_lastError
= wxPROTO_CONNERR
;
180 wxLogDebug(wxT("Failed to close connection gracefully."));
184 return wxSocketClient::Close();
187 // ============================================================================
189 // ============================================================================
191 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
196 // now wait for a connection from server
197 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
198 if ( !sockSrv
->WaitForAccept() )
200 m_lastError
= wxPROTO_CONNERR
;
201 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
207 m_lastError
= wxPROTO_NOERR
;
208 sock
= sockSrv
->Accept(true);
221 if ( !CheckCommand(wxT("ABOR"), '4') )
224 return CheckResult('2');
228 // ----------------------------------------------------------------------------
229 // Send command to FTP server
230 // ----------------------------------------------------------------------------
232 char wxFTP::SendCommand(const wxString
& command
)
236 m_lastError
= wxPROTO_STREAMING
;
240 wxString tmp_str
= command
+ wxT("\r\n");
241 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
242 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
244 m_lastError
= wxPROTO_NETERR
;
248 // don't show the passwords in the logs (even in debug ones)
249 wxString cmd
, password
;
250 if ( command
.Upper().StartsWith(wxT("PASS "), &password
) )
252 cmd
<< wxT("PASS ") << wxString(wxT('*'), password
.length());
261 m_lastError
= wxPROTO_NOERR
;
265 // ----------------------------------------------------------------------------
266 // Receive servers reply
267 // ----------------------------------------------------------------------------
269 char wxFTP::GetResult()
271 // if we've already had a read or write timeout error, the connection is
272 // probably toast, so don't bother, it just wastes the users time
273 if ( m_bEncounteredError
)
278 // m_lastResult will contain the entire server response, possibly on
280 m_lastResult
.clear();
282 // we handle multiline replies here according to RFC 959: it says that a
283 // reply may either be on 1 line of the form "xyz ..." or on several lines
284 // in whuch case it looks like
288 // and the intermeidate lines may start with xyz or not
289 bool badReply
= false;
290 bool firstLine
= true;
291 bool endOfReply
= false;
292 while ( !endOfReply
&& !badReply
)
295 m_lastError
= ReadLine(this,line
);
298 m_bEncounteredError
= true;
304 if ( !m_lastResult
.empty() )
306 // separate from last line
307 m_lastResult
+= wxT('\n');
310 m_lastResult
+= line
;
312 // unless this is an intermediate line of a multiline reply, it must
313 // contain the code in the beginning and '-' or ' ' following it
314 if ( line
.Len() < LEN_CODE
+ 1 )
321 else // line has at least 4 chars
323 // this is the char which tells us what we're dealing with
324 wxChar chMarker
= line
.GetChar(LEN_CODE
);
328 code
= wxString(line
, LEN_CODE
);
345 else // subsequent line of multiline reply
347 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
349 if ( chMarker
== wxT(' ') )
360 wxLogDebug(wxT("Broken FTP server: '%s' is not a valid reply."),
361 m_lastResult
.c_str());
363 m_lastError
= wxPROTO_PROTERR
;
368 m_lastError
= wxPROTO_NOERR
;
370 // if we got here we must have a non empty code string
371 return (char)code
[0u];
374 // ----------------------------------------------------------------------------
375 // wxFTP simple commands
376 // ----------------------------------------------------------------------------
378 bool wxFTP::SetTransferMode(TransferMode transferMode
)
380 if ( transferMode
== m_currentTransfermode
)
387 switch ( transferMode
)
390 wxFAIL_MSG(wxT("unknown FTP transfer mode"));
402 if ( !DoSimpleCommand(wxT("TYPE"), mode
) )
404 wxLogError(_("Failed to set FTP transfer mode to %s."),
405 (transferMode
== ASCII
? _("ASCII") : _("binary")));
410 // If we get here the operation has been successfully completed
411 // Set the status-member
412 m_currentTransfermode
= transferMode
;
417 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
419 wxString fullcmd
= command
;
422 fullcmd
<< wxT(' ') << arg
;
425 if ( !CheckCommand(fullcmd
, '2') )
427 wxLogDebug(wxT("FTP command '%s' failed."), fullcmd
.c_str());
428 m_lastError
= wxPROTO_NETERR
;
433 m_lastError
= wxPROTO_NOERR
;
437 bool wxFTP::ChDir(const wxString
& dir
)
439 // some servers might not understand ".." if they use different directory
440 // tree conventions, but they always understand CDUP - should we use it if
441 // dir == ".."? OTOH, do such servers (still) exist?
443 return DoSimpleCommand(wxT("CWD"), dir
);
446 bool wxFTP::MkDir(const wxString
& dir
)
448 return DoSimpleCommand(wxT("MKD"), dir
);
451 bool wxFTP::RmDir(const wxString
& dir
)
453 return DoSimpleCommand(wxT("RMD"), dir
);
456 wxString
wxFTP::Pwd()
460 if ( CheckCommand(wxT("PWD"), '2') )
462 // the result is at least that long if CheckCommand() succeeded
463 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
464 if ( *p
!= wxT('"') )
466 wxLogDebug(wxT("Missing starting quote in reply for PWD: %s"),
467 wxString(p
, m_lastResult
.end()));
471 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
473 if ( *p
== wxT('"') )
475 // check if the quote is doubled
477 if ( !*p
|| *p
!= wxT('"') )
479 // no, this is the end
482 //else: yes, it is: this is an embedded quote in the
483 // filename, treat as normal char
491 wxLogDebug(wxT("Missing ending quote in reply for PWD: %s"),
492 m_lastResult
.c_str() + LEN_CODE
+ 1);
498 m_lastError
= wxPROTO_PROTERR
;
499 wxLogDebug(wxT("FTP PWD command failed."));
505 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
509 str
= wxT("RNFR ") + src
;
510 if ( !CheckCommand(str
, '3') )
513 str
= wxT("RNTO ") + dst
;
515 return CheckCommand(str
, '2');
518 bool wxFTP::RmFile(const wxString
& path
)
521 str
= wxT("DELE ") + path
;
523 return CheckCommand(str
, '2');
526 // ----------------------------------------------------------------------------
527 // wxFTP port methods
528 // ----------------------------------------------------------------------------
530 wxSocketBase
*wxFTP::GetPort()
533 PASSIVE: Client sends a "PASV" to the server. The server responds with
534 an address and port number which it will be listening on. Then
535 the client connects to the server at the specified address and
538 ACTIVE: Client sends the server a PORT command which includes an
539 address and port number which the client will be listening on.
540 The server then connects to the client at that address and
544 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
547 m_bEncounteredError
= true;
551 // Now set the time for the new socket to the default or user selected
553 socket
->SetTimeout(m_uiDefaultTimeout
);
558 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
559 const wxIPV4address
& addrNew
)
561 // Just fills in the return value with the local IP
562 // address of the current socket. Also it fill in the
563 // PORT which the client will be listening on
565 wxString addrIP
= addrLocal
.IPAddress();
566 int portNew
= addrNew
.Service();
568 // We need to break the PORT number in bytes
569 addrIP
.Replace(wxT("."), wxT(","));
571 << wxString::Format(wxT("%d"), portNew
>> 8) << wxT(',')
572 << wxString::Format(wxT("%d"), portNew
& 0xff);
574 // Now we have a value like "10,0,0,1,5,23"
578 wxSocketBase
*wxFTP::GetActivePort()
580 // we need an address to listen on
581 wxIPV4address addrNew
, addrLocal
;
583 addrNew
.AnyAddress();
584 addrNew
.Service(0); // pick an open port number.
586 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
589 // We use Ok() here to see if everything is ok
590 m_lastError
= wxPROTO_PROTERR
;
595 //gets the new address, actually it is just the port number
596 sockSrv
->GetLocal(addrNew
);
598 // Now we create the argument of the PORT command, we send in both
599 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
600 // value in addrLocal
601 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
602 if ( !DoSimpleCommand(wxT("PORT"), port
) )
604 m_lastError
= wxPROTO_PROTERR
;
606 wxLogError(_("The FTP server doesn't support the PORT command."));
610 m_lastError
= wxPROTO_NOERR
;
611 sockSrv
->Notify(false); // Don't send any events
615 wxSocketBase
*wxFTP::GetPassivePort()
617 if ( !DoSimpleCommand(wxT("PASV")) )
619 m_lastError
= wxPROTO_PROTERR
;
620 wxLogError(_("The FTP server doesn't support passive mode."));
624 size_t addrStart
= m_lastResult
.find(wxT('('));
625 size_t addrEnd
= (addrStart
== wxString::npos
)
627 : m_lastResult
.find(wxT(')'), addrStart
);
629 if ( addrEnd
== wxString::npos
)
631 m_lastError
= wxPROTO_PROTERR
;
635 // get the port number and address
637 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
638 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
639 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
641 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
642 (wxUint16
)a
[3] << 16 |
643 (wxUint16
)a
[4] << 8 |
645 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
648 addr
.Hostname(hostaddr
);
651 wxSocketClient
*client
= new wxSocketClient();
652 if ( !client
->Connect(addr
) )
654 m_lastError
= wxPROTO_CONNERR
;
659 client
->Notify(false);
661 m_lastError
= wxPROTO_NOERR
;
666 // ----------------------------------------------------------------------------
667 // wxFTP download and upload
668 // ----------------------------------------------------------------------------
670 class wxInputFTPStream
: public wxSocketInputStream
673 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
674 : wxSocketInputStream(*sock
)
677 // socket timeout automatically set in GetPort function
680 virtual ~wxInputFTPStream()
682 delete m_i_socket
; // keep at top
684 // when checking the result, the stream will
685 // almost always show an error, even if the file was
686 // properly transfered, thus, lets just grab the result
688 // we are looking for "226 transfer completed"
689 char code
= m_ftp
->GetResult();
692 // it was a good transfer.
694 m_ftp
->m_streaming
= false;
700 // the connection is probably toast. issue an abort, and
701 // then a close. there won't be any more waiting
702 // for this connection
707 // There was a problem with the transfer and the server
708 // has acknowledged it. If we issue an "ABORT" now, the user
709 // would get the "226" for the abort and think the xfer was
710 // complete, thus, don't do anything here, just return
715 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream
);
718 class wxOutputFTPStream
: public wxSocketOutputStream
721 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
722 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
726 virtual ~wxOutputFTPStream(void)
730 // close data connection first, this will generate "transfer
735 m_ftp
->GetResult(); // save result so user can get to it
737 m_ftp
->m_streaming
= false;
741 // abort data connection first
744 // and close it after
751 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream
);
754 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
756 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
758 m_lastError
= wxPROTO_CONNERR
;
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
);
777 m_lastError
= wxPROTO_CONNERR
;
781 sock
->SetFlags(wxSOCKET_WAITALL
);
785 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
787 m_lastError
= wxPROTO_NOERR
;
791 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
793 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
795 m_lastError
= wxPROTO_CONNERR
;
799 wxSocketBase
*sock
= GetPort();
801 wxString tmp_str
= wxT("STOR ") + path
;
802 if ( !CheckCommand(tmp_str
, '1') )
805 sock
= AcceptIfActive(sock
);
809 m_lastError
= wxPROTO_NOERR
;
810 return new wxOutputFTPStream(this, sock
);
813 // ----------------------------------------------------------------------------
814 // FTP directory listing
815 // ----------------------------------------------------------------------------
817 bool wxFTP::GetList(wxArrayString
& files
,
818 const wxString
& wildcard
,
821 wxSocketBase
*sock
= GetPort();
823 m_lastError
= wxPROTO_NETERR
;
827 // NLST : List of Filenames (including Directory's !)
828 // LIST : depending on BS of FTP-Server
829 // - Unix : result like "ls" command
830 // - Windows : like "dir" command
832 wxString
line(details
? wxT("LIST") : wxT("NLST"));
833 if ( !wildcard
.empty() )
835 line
<< wxT(' ') << wildcard
;
838 if ( !CheckCommand(line
, '1') )
840 m_lastError
= wxPROTO_PROTERR
;
841 wxLogDebug(wxT("FTP 'LIST' command returned unexpected result from server"));
846 sock
= AcceptIfActive(sock
);
848 m_lastError
= wxPROTO_CONNERR
;
853 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
860 // the file list should be terminated by "226 Transfer complete""
861 m_lastError
= wxPROTO_NOERR
;
862 return CheckResult('2');
865 bool wxFTP::FileExists(const wxString
& fileName
)
867 // This function checks if the file specified in fileName exists in the
868 // current dir. It does so by simply doing an NLST (via GetList).
869 // If this succeeds (and the list is not empty) the file exists.
872 wxArrayString fileList
;
874 if ( GetList(fileList
, fileName
, false) )
876 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
877 // displays this behaviour when queried on a nonexistent file:
878 // NLST this_file_does_not_exist
879 // 150 Opening ASCII data connection for directory listing
880 // (no data transferred)
881 // 226 Transfer complete
882 // Here wxFTP::GetList(...) will succeed but it will return an empty
884 retval
= !fileList
.IsEmpty();
890 // ----------------------------------------------------------------------------
892 // ----------------------------------------------------------------------------
894 int wxFTP::GetFileSize(const wxString
& fileName
)
896 // return the filesize of the given file if possible
897 // return -1 otherwise (predominantly if file doesn't exist
902 // Check for existance of file via wxFTP::FileExists(...)
903 if ( FileExists(fileName
) )
907 // First try "SIZE" command using BINARY(IMAGE) transfermode
908 // Especially UNIX ftp-servers distinguish between the different
909 // transfermodes and reports different filesizes accordingly.
910 // The BINARY size is the interesting one: How much memory
911 // will we need to hold this file?
912 TransferMode oldTransfermode
= m_currentTransfermode
;
913 SetTransferMode(BINARY
);
914 command
<< wxT("SIZE ") << fileName
;
916 bool ok
= CheckCommand(command
, '2');
920 // The answer should be one line: "213 <filesize>\n"
921 // 213 is File Status (STD9)
922 // "SIZE" is not described anywhere..? It works on most servers
924 if ( wxSscanf(GetLastResult().c_str(), wxT("%i %i"),
925 &statuscode
, &filesize
) == 2 )
927 // We've gotten a good reply.
932 // Something bad happened.. A "2yz" reply with no size
938 // Set transfermode back to the original. Only the "SIZE"-command
939 // is dependant on transfermode
940 if ( oldTransfermode
!= NONE
)
942 SetTransferMode(oldTransfermode
);
945 // this is not a direct else clause.. The size command might return an
946 // invalid "2yz" reply
949 // The server didn't understand the "SIZE"-command or it
950 // returned an invalid reply.
951 // We now try to get details for the file with a "LIST"-command
952 // and then parse the output from there..
953 wxArrayString fileList
;
954 if ( GetList(fileList
, fileName
, true) )
956 if ( !fileList
.IsEmpty() )
958 // We _should_ only get one line in return, but just to be
959 // safe we run through the line(s) returned and look for a
960 // substring containing the name we are looking for. We
961 // stop the iteration at the first occurrence of the
962 // filename. The search is not case-sensitive.
963 bool foundIt
= false;
966 for ( i
= 0; !foundIt
&& i
< fileList
.GetCount(); i
++ )
968 foundIt
= fileList
[i
].Upper().Contains(fileName
.Upper());
973 // The index i points to the first occurrence of
974 // fileName in the array Now we have to find out what
975 // format the LIST has returned. There are two
976 // "schools": Unix-like
978 // '-rw-rw-rw- owner group size month day time filename'
982 // 'date size filename'
984 // check if the first character is '-'. This would
985 // indicate Unix-style (this also limits this function
986 // to searching for files, not directories)
987 if ( fileList
[i
].Mid(0, 1) == wxT("-") )
990 if ( wxSscanf(fileList
[i
].c_str(),
991 wxT("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
994 // Hmm... Invalid response
995 wxLogDebug(wxT("Invalid LIST response"));
998 else // Windows-style response (?)
1000 if ( wxSscanf(fileList
[i
].c_str(),
1001 wxT("%*s %*s %i %*s"),
1004 // something bad happened..?
1005 wxLogDebug(wxT("Invalid or unknown LIST response"));
1014 // filesize might still be -1 when exiting
1018 #endif // wxUSE_PROTOCOL_FTP