1 /////////////////////////////////////////////////////////////////////////////
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 // Created: 07/07/1997
12 // Copyright: (c) 1997, 1998 Guilhem Lavaux
13 // Licence: wxWindows license
14 /////////////////////////////////////////////////////////////////////////////
16 // ============================================================================
18 // ============================================================================
21 #pragma implementation "ftp.h"
24 // ----------------------------------------------------------------------------
26 // ----------------------------------------------------------------------------
28 // For compilers that support precompilation, includes "wx.h".
29 #include "wx/wxprec.h"
35 #if wxUSE_SOCKETS && wxUSE_STREAMS
39 #include "wx/string.h"
45 #include "wx/sckaddr.h"
46 #include "wx/socket.h"
48 #include "wx/sckstrm.h"
49 #include "wx/protocol/protocol.h"
50 #include "wx/protocol/ftp.h"
52 #if defined(__WXMAC__)
53 #include "/wx/mac/macsock.h"
60 // ----------------------------------------------------------------------------
62 // ----------------------------------------------------------------------------
64 // the length of FTP status code (3 digits)
65 static const size_t LEN_CODE
= 3;
67 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
71 IMPLEMENT_DYNAMIC_CLASS(wxFTP
, wxProtocol
)
72 IMPLEMENT_PROTOCOL(wxFTP
, wxT("ftp"), wxT("ftp"), TRUE
)
74 // ============================================================================
76 // ============================================================================
78 // ----------------------------------------------------------------------------
79 // wxFTP constructor and destructor
80 // ----------------------------------------------------------------------------
84 m_lastError
= wxPROTO_NOERR
;
88 m_user
= wxT("anonymous");
89 m_passwd
<< wxGetUserId() << wxT('@') << wxGetFullHostName();
92 SetFlags(wxSOCKET_NONE
);
105 // ----------------------------------------------------------------------------
106 // wxFTP connect and login methods
107 // ----------------------------------------------------------------------------
109 bool wxFTP::Connect(wxSockAddress
& addr
, bool WXUNUSED(wait
))
111 if ( !wxProtocol::Connect(addr
) )
113 m_lastError
= wxPROTO_NETERR
;
119 m_lastError
= wxPROTO_CONNERR
;
123 // we should have 220 welcome message
124 if ( !CheckResult('2') )
131 command
.Printf(wxT("USER %s"), m_user
.c_str());
132 char rc
= SendCommand(command
);
135 // 230 return: user accepted without password
145 command
.Printf(wxT("PASS %s"), m_passwd
.c_str());
146 if ( !CheckCommand(command
, '2') )
155 bool wxFTP::Connect(const wxString
& host
)
159 addr
.Service(wxT("ftp"));
161 return Connect(addr
);
168 m_lastError
= wxPROTO_STREAMING
;
174 if ( !CheckCommand(wxT("QUIT"), '2') )
176 wxLogDebug(_T("Failed to close connection gracefully."));
180 return wxSocketClient::Close();
183 // ============================================================================
185 // ============================================================================
187 // ----------------------------------------------------------------------------
188 // Send command to FTP server
189 // ----------------------------------------------------------------------------
191 char wxFTP::SendCommand(const wxString
& command
)
195 m_lastError
= wxPROTO_STREAMING
;
199 wxString tmp_str
= command
+ wxT("\r\n");
200 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
201 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
203 m_lastError
= wxPROTO_NETERR
;
207 wxLogTrace(_T("ftp"), _T("==> %s"), command
.c_str());
212 // ----------------------------------------------------------------------------
213 // Recieve servers reply
214 // ----------------------------------------------------------------------------
216 char wxFTP::GetResult()
220 // m_lastResult will contain the entire server response, possibly on
222 m_lastResult
.clear();
224 // we handle multiline replies here according to RFC 959: it says that a
225 // reply may either be on 1 line of the form "xyz ..." or on several lines
226 // in whuch case it looks like
230 // and the intermeidate lines may start with xyz or not
231 bool badReply
= FALSE
;
232 bool firstLine
= TRUE
;
233 bool endOfReply
= FALSE
;
234 while ( !endOfReply
&& !badReply
)
237 m_lastError
= ReadLine(line
);
241 if ( !m_lastResult
.empty() )
243 // separate from last line
244 m_lastResult
+= _T('\n');
247 m_lastResult
+= line
;
249 // unless this is an intermediate line of a multiline reply, it must
250 // contain the code in the beginning and '-' or ' ' following it
251 if ( line
.Len() < LEN_CODE
+ 1 )
259 wxLogTrace(_T("ftp"), _T("<== %s %s"),
260 code
.c_str(), line
.c_str());
263 else // line has at least 4 chars
265 // this is the char which tells us what we're dealing with
266 wxChar chMarker
= line
.GetChar(LEN_CODE
);
270 code
= wxString(line
, LEN_CODE
);
271 wxLogTrace(_T("ftp"), _T("<== %s %s"),
272 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
289 else // subsequent line of multiline reply
291 if ( wxStrncmp(line
, code
, LEN_CODE
) == 0 )
293 if ( chMarker
== _T(' ') )
298 wxLogTrace(_T("ftp"), _T("<== %s %s"),
299 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
303 // just part of reply
304 wxLogTrace(_T("ftp"), _T("<== %s %s"),
305 code
.c_str(), line
.c_str());
313 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
314 m_lastResult
.c_str());
316 m_lastError
= wxPROTO_PROTERR
;
321 // if we got here we must have a non empty code string
325 // ----------------------------------------------------------------------------
326 // wxFTP simple commands
327 // ----------------------------------------------------------------------------
329 bool wxFTP::SetTransferMode(TransferMode transferMode
)
332 switch ( transferMode
)
335 wxFAIL_MSG(_T("unknown FTP transfer mode"));
347 if ( !DoSimpleCommand(_T("TYPE"), mode
) )
349 wxLogError(_("Failed to set FTP transfer mode to %s."),
350 transferMode
== ASCII
? _("ASCII") : _("binary"));
360 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
362 wxString fullcmd
= command
;
365 fullcmd
<< _T(' ') << arg
;
368 if ( !CheckCommand(fullcmd
, '2') )
370 wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str());
378 bool wxFTP::ChDir(const wxString
& dir
)
380 // some servers might not understand ".." if they use different directory
381 // tree conventions, but they always understand CDUP - should we use it if
382 // dir == ".."? OTOH, do such servers (still) exist?
384 return DoSimpleCommand(_T("CWD"), dir
);
387 bool wxFTP::MkDir(const wxString
& dir
)
389 return DoSimpleCommand(_T("MKD"), dir
);
392 bool wxFTP::RmDir(const wxString
& dir
)
394 return DoSimpleCommand(_T("RMD"), dir
);
397 wxString
wxFTP::Pwd()
401 if ( CheckCommand(wxT("PWD"), '2') )
403 // the result is at least that long if CheckCommand() succeeded
404 const wxChar
*p
= m_lastResult
.c_str() + LEN_CODE
+ 1;
407 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p
);
415 // check if the quote is doubled
417 if ( !*p
|| *p
!= _T('"') )
419 // no, this is the end
422 //else: yes, it is: this is an embedded quote in the
423 // filename, treat as normal char
431 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
432 m_lastResult
.c_str() + LEN_CODE
+ 1);
438 wxLogDebug(_T("FTP PWD command failed."));
444 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
448 str
= wxT("RNFR ") + src
;
449 if ( !CheckCommand(str
, '3') )
452 str
= wxT("RNTO ") + dst
;
454 return CheckCommand(str
, '2');
457 bool wxFTP::RmFile(const wxString
& path
)
460 str
= wxT("DELE ") + path
;
462 return CheckCommand(str
, '2');
465 // ----------------------------------------------------------------------------
466 // wxFTP download and upload
467 // ----------------------------------------------------------------------------
469 class wxInputFTPStream
: public wxSocketInputStream
472 wxInputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
473 : wxSocketInputStream(*sock
), m_ftp(ftp_clt
)
477 size_t GetSize() const { return m_ftpsize
; }
479 virtual ~wxInputFTPStream()
481 if ( LastError() == wxStream_NOERROR
)
483 // wait for "226 transfer completed"
484 m_ftp
->CheckResult('2');
486 m_ftp
->m_streaming
= FALSE
;
500 class wxOutputFTPStream
: public wxSocketOutputStream
503 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
504 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
508 virtual ~wxOutputFTPStream(void)
512 // close data connection first, this will generate "transfer
517 m_ftp
->CheckResult('2');
519 m_ftp
->m_streaming
= FALSE
;
523 // abort data connection first
526 // and close it after
534 wxSocketClient
*wxFTP::GetPort()
538 if ( !DoSimpleCommand(_T("PASV")) )
540 wxLogError(_("The FTP server doesn't support passive mode."));
545 const char *addrStart
= wxStrchr(m_lastResult
, _T('('));
548 m_lastError
= wxPROTO_PROTERR
;
553 const char *addrEnd
= wxStrchr(addrStart
, _T(')'));
556 m_lastError
= wxPROTO_PROTERR
;
561 wxString
straddr(addrStart
+ 1, addrEnd
);
563 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
564 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
566 wxUint32 hostaddr
= (wxUint16
)a
[5] << 24 |
567 (wxUint16
)a
[4] << 16 |
568 (wxUint16
)a
[3] << 8 |
570 wxUint16 port
= (wxUint16
)a
[0] << 8 | a
[1];
573 addr
.Hostname(hostaddr
);
576 wxSocketClient
*client
= new wxSocketClient();
577 if ( !client
->Connect(addr
) )
583 client
->Notify(FALSE
);
594 if ( !CheckCommand(wxT("ABOR"), '4') )
597 return CheckResult('2');
600 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
603 wxInputFTPStream
*in_stream
;
605 if ( !m_modeSet
&& !SetTransferMode(BINARY
) )
608 wxSocketClient
*sock
= GetPort();
612 m_lastError
= wxPROTO_NETERR
;
616 wxString tmp_str
= wxT("RETR ") + wxURL::ConvertFromURI(path
);
617 if ( !CheckCommand(tmp_str
, '1') )
622 in_stream
= new wxInputFTPStream(this, sock
);
624 pos_size
= m_lastResult
.Index(wxT('('));
625 if ( pos_size
!= wxNOT_FOUND
)
627 wxString str_size
= m_lastResult(pos_size
+1, m_lastResult
.Index(wxT(')'))-1);
629 in_stream
->m_ftpsize
= wxAtoi(WXSTRINGCAST str_size
);
632 sock
->SetFlags(wxSOCKET_WAITALL
);
637 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
639 if ( !m_modeSet
&& !SetTransferMode(BINARY
) )
642 wxSocketClient
*sock
= GetPort();
644 wxString tmp_str
= wxT("STOR ") + path
;
645 if ( !CheckCommand(tmp_str
, '1') )
650 return new wxOutputFTPStream(this, sock
);
653 // ----------------------------------------------------------------------------
654 // FTP directory listing
655 // ----------------------------------------------------------------------------
657 bool wxFTP::GetList(wxArrayString
& files
,
658 const wxString
& wildcard
,
661 wxSocketBase
*sock
= GetPort();
665 // NLST : List of Filenames (including Directory's !)
666 // LIST : depending on BS of FTP-Server
667 // - Unix : result like "ls" command
668 // - Windows : like "dir" command
670 wxString
line(details
? _T("LIST") : _T("NLST"));
673 line
<< _T(' ') << wildcard
;
676 if (!CheckCommand(line
, '1'))
681 while ( ReadLine(sock
, line
) == wxPROTO_NOERR
)
687 // the file list should be terminated by "226 Transfer complete""
688 if ( !CheckResult('2') )
694 #ifdef WXWIN_COMPATIBILITY_2
696 wxList
*wxFTP::GetList(const wxString
& wildcard
, bool details
)
698 wxSocketBase
*sock
= GetPort();
701 wxList
*file_list
= new wxList
;
703 // NLST : List of Filenames (including Directory's !)
704 // LIST : depending on BS of FTP-Server
705 // - Unix : result like "ls" command
706 // - Windows : like "dir" command
709 line
= _T("NLST"); // Default
712 if (!wildcard
.IsNull())
714 if (!CheckCommand(line
, '1'))
720 while (GetLine(sock
, line
) == wxPROTO_NOERR
)
722 file_list
->Append((wxObject
*)(new wxString(line
)));
724 if (!CheckResult('2'))
727 file_list
->DeleteContents(TRUE
);
733 #endif // WXWIN_COMPATIBILITY_2