]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
fixed wxSplitPath() bug and added tests for it
[wxWidgets.git] / src / common / ftp.cpp
CommitLineData
f4ada568
GL
1/////////////////////////////////////////////////////////////////////////////
2// Name: ftp.cpp
3// Purpose: FTP protocol
4// Author: Guilhem Lavaux
2e907fab
VZ
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, ...)
f4ada568
GL
10// Created: 07/07/1997
11// RCS-ID: $Id$
12// Copyright: (c) 1997, 1998 Guilhem Lavaux
13// Licence: wxWindows license
14/////////////////////////////////////////////////////////////////////////////
15
2e907fab
VZ
16// ============================================================================
17// declarations
18// ============================================================================
19
f4ada568 20#ifdef __GNUG__
2df7be7f 21 #pragma implementation "ftp.h"
f4ada568 22#endif
ec45f8ee 23
2e907fab
VZ
24// ----------------------------------------------------------------------------
25// headers
26// ----------------------------------------------------------------------------
27
ec45f8ee
UU
28// For compilers that support precompilation, includes "wx.h".
29#include "wx/wxprec.h"
30
19ae5cf0 31#ifdef __BORLANDC__
2df7be7f 32 #pragma hdrstop
19ae5cf0
UU
33#endif
34
f6bcfd97 35#if wxUSE_SOCKETS && wxUSE_STREAMS
35a4dab7 36
2e907fab
VZ
37#ifndef WX_PRECOMP
38 #include <stdlib.h>
39 #include "wx/string.h"
40 #include "wx/utils.h"
41 #include "wx/log.h"
42 #include "wx/intl.h"
43#endif // WX_PRECOMP
17dff81c 44
f4ada568 45#include "wx/sckaddr.h"
f4ada568
GL
46#include "wx/socket.h"
47#include "wx/url.h"
48#include "wx/sckstrm.h"
49#include "wx/protocol/protocol.h"
50#include "wx/protocol/ftp.h"
51
2e907fab
VZ
52#if defined(__WXMAC__)
53 #include "/wx/mac/macsock.h"
54#endif
55
56#ifndef __MWERKS__
57 #include <memory.h>
f4ada568
GL
58#endif
59
2e907fab
VZ
60// ----------------------------------------------------------------------------
61// constants
62// ----------------------------------------------------------------------------
63
8e907a13
VZ
64// the length of FTP status code (3 digits)
65static const size_t LEN_CODE = 3;
66
2e907fab
VZ
67// ----------------------------------------------------------------------------
68// macros
69// ----------------------------------------------------------------------------
f4ada568 70
f4ada568 71IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
223d09f6 72IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), TRUE)
f4ada568 73
2e907fab
VZ
74// ============================================================================
75// implementation
76// ============================================================================
77
78// ----------------------------------------------------------------------------
79// wxFTP constructor and destructor
80// ----------------------------------------------------------------------------
f4ada568
GL
81
82wxFTP::wxFTP()
f4ada568 83{
8e907a13
VZ
84 m_lastError = wxPROTO_NOERR;
85 m_streaming = FALSE;
2e907fab 86 m_modeSet = FALSE;
f4ada568 87
8e907a13
VZ
88 m_user = wxT("anonymous");
89 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
f4ada568 90
8e907a13
VZ
91 SetNotify(0);
92 SetFlags(wxSOCKET_NONE);
f4ada568
GL
93}
94
95wxFTP::~wxFTP()
96{
7ff26ec2
VZ
97 if ( m_streaming )
98 {
99 (void)Abort();
100 }
101
8e907a13 102 Close();
f4ada568
GL
103}
104
2e907fab
VZ
105// ----------------------------------------------------------------------------
106// wxFTP connect and login methods
107// ----------------------------------------------------------------------------
108
8a2c6ef8 109bool wxFTP::Connect(wxSockAddress& addr, bool WXUNUSED(wait))
f4ada568 110{
2e907fab
VZ
111 if ( !wxProtocol::Connect(addr) )
112 {
113 m_lastError = wxPROTO_NETERR;
114 return FALSE;
115 }
f4ada568 116
2e907fab
VZ
117 if ( !m_user )
118 {
119 m_lastError = wxPROTO_CONNERR;
120 return FALSE;
121 }
f4ada568 122
2e907fab
VZ
123 // we should have 220 welcome message
124 if ( !CheckResult('2') )
125 {
126 Close();
127 return FALSE;
128 }
f4ada568 129
2e907fab
VZ
130 wxString command;
131 command.Printf(wxT("USER %s"), m_user.c_str());
132 char rc = SendCommand(command);
133 if ( rc == '2' )
134 {
135 // 230 return: user accepted without password
136 return TRUE;
137 }
f4ada568 138
2e907fab
VZ
139 if ( rc != '3' )
140 {
141 Close();
142 return FALSE;
143 }
f4ada568 144
2e907fab
VZ
145 command.Printf(wxT("PASS %s"), m_passwd.c_str());
146 if ( !CheckCommand(command, '2') )
147 {
148 Close();
149 return FALSE;
150 }
f4ada568 151
2e907fab 152 return TRUE;
f4ada568
GL
153}
154
155bool wxFTP::Connect(const wxString& host)
156{
2e907fab
VZ
157 wxIPV4address addr;
158 addr.Hostname(host);
159 addr.Service(wxT("ftp"));
f4ada568 160
2e907fab 161 return Connect(addr);
f4ada568
GL
162}
163
7ff26ec2 164bool wxFTP::Close()
f4ada568 165{
8e907a13
VZ
166 if ( m_streaming )
167 {
7ff26ec2
VZ
168 m_lastError = wxPROTO_STREAMING;
169 return FALSE;
8e907a13
VZ
170 }
171
172 if ( IsConnected() )
2e907fab
VZ
173 {
174 if ( !CheckCommand(wxT("QUIT"), '2') )
175 {
176 wxLogDebug(_T("Failed to close connection gracefully."));
177 }
178 }
e8773bdf 179
8e907a13 180 return wxSocketClient::Close();
f4ada568
GL
181}
182
8e907a13
VZ
183// ============================================================================
184// low level methods
185// ============================================================================
186
187// ----------------------------------------------------------------------------
188// Send command to FTP server
189// ----------------------------------------------------------------------------
190
2e907fab 191char wxFTP::SendCommand(const wxString& command)
f4ada568 192{
2e907fab 193 if ( m_streaming )
8e907a13
VZ
194 {
195 m_lastError = wxPROTO_STREAMING;
2e907fab 196 return 0;
8e907a13
VZ
197 }
198
2e907fab 199 wxString tmp_str = command + wxT("\r\n");
8e907a13
VZ
200 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
201 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
202 {
203 m_lastError = wxPROTO_NETERR;
2e907fab 204 return 0;
8e907a13
VZ
205 }
206
207 wxLogTrace(_T("ftp"), _T("==> %s"), command.c_str());
208
2e907fab 209 return GetResult();
f4ada568
GL
210}
211
8e907a13
VZ
212// ----------------------------------------------------------------------------
213// Recieve servers reply
214// ----------------------------------------------------------------------------
215
2e907fab 216char wxFTP::GetResult()
f4ada568 217{
8e907a13
VZ
218 wxString code;
219
2e907fab
VZ
220 // m_lastResult will contain the entire server response, possibly on
221 // multiple lines
222 m_lastResult.clear();
223
8e907a13
VZ
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
227 // xyz-...
228 // ...
229 // xyz ...
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 )
235 {
2e907fab
VZ
236 wxString line;
237 m_lastError = ReadLine(line);
8e907a13 238 if ( m_lastError )
2e907fab
VZ
239 return 0;
240
241 if ( !m_lastResult.empty() )
242 {
243 // separate from last line
244 m_lastResult += _T('\n');
245 }
246
247 m_lastResult += line;
8e907a13
VZ
248
249 // unless this is an intermediate line of a multiline reply, it must
250 // contain the code in the beginning and '-' or ' ' following it
2e907fab 251 if ( line.Len() < LEN_CODE + 1 )
8e907a13
VZ
252 {
253 if ( firstLine )
254 {
255 badReply = TRUE;
256 }
257 else
258 {
259 wxLogTrace(_T("ftp"), _T("<== %s %s"),
2e907fab 260 code.c_str(), line.c_str());
8e907a13
VZ
261 }
262 }
263 else // line has at least 4 chars
264 {
265 // this is the char which tells us what we're dealing with
2e907fab 266 wxChar chMarker = line.GetChar(LEN_CODE);
8e907a13
VZ
267
268 if ( firstLine )
269 {
2e907fab 270 code = wxString(line, LEN_CODE);
8e907a13 271 wxLogTrace(_T("ftp"), _T("<== %s %s"),
2e907fab 272 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
273
274 switch ( chMarker )
275 {
276 case _T(' '):
277 endOfReply = TRUE;
278 break;
279
280 case _T('-'):
281 firstLine = FALSE;
282 break;
283
284 default:
285 // unexpected
286 badReply = TRUE;
287 }
288 }
289 else // subsequent line of multiline reply
290 {
2e907fab 291 if ( wxStrncmp(line, code, LEN_CODE) == 0 )
8e907a13
VZ
292 {
293 if ( chMarker == _T(' ') )
294 {
295 endOfReply = TRUE;
296 }
297
298 wxLogTrace(_T("ftp"), _T("<== %s %s"),
2e907fab 299 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
300 }
301 else
302 {
303 // just part of reply
304 wxLogTrace(_T("ftp"), _T("<== %s %s"),
2e907fab 305 code.c_str(), line.c_str());
8e907a13
VZ
306 }
307 }
308 }
309 }
f4ada568 310
8e907a13
VZ
311 if ( badReply )
312 {
313 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
314 m_lastResult.c_str());
f4ada568 315
8e907a13 316 m_lastError = wxPROTO_PROTERR;
f4ada568 317
2e907fab
VZ
318 return 0;
319 }
320
321 // if we got here we must have a non empty code string
322 return code[0u];
323}
324
325// ----------------------------------------------------------------------------
326// wxFTP simple commands
327// ----------------------------------------------------------------------------
328
329bool wxFTP::SetTransferMode(TransferMode transferMode)
330{
331 wxString mode;
332 switch ( transferMode )
333 {
334 default:
335 wxFAIL_MSG(_T("unknown FTP transfer mode"));
336 // fall through
337
338 case BINARY:
339 mode = _T('I');
340 break;
341
342 case ASCII:
343 mode = _T('A');
344 break;
345 }
346
347 if ( !DoSimpleCommand(_T("TYPE"), mode) )
348 {
349 wxLogError(_("Failed to set FTP transfer mode to %s."),
350 transferMode == ASCII ? _("ASCII") : _("binary"));
351
f4ada568
GL
352 return FALSE;
353 }
8e907a13 354
2e907fab
VZ
355 m_modeSet = TRUE;
356
357 return TRUE;
358}
359
360bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
361{
362 wxString fullcmd = command;
363 if ( !arg.empty() )
8e907a13 364 {
2e907fab
VZ
365 fullcmd << _T(' ') << arg;
366 }
367
368 if ( !CheckCommand(fullcmd, '2') )
369 {
370 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
8e907a13
VZ
371
372 return FALSE;
373 }
374
375 return TRUE;
f4ada568
GL
376}
377
f4ada568
GL
378bool wxFTP::ChDir(const wxString& dir)
379{
2e907fab
VZ
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?
f4ada568 383
2e907fab 384 return DoSimpleCommand(_T("CWD"), dir);
f4ada568
GL
385}
386
387bool wxFTP::MkDir(const wxString& dir)
388{
2e907fab 389 return DoSimpleCommand(_T("MKD"), dir);
f4ada568
GL
390}
391
392bool wxFTP::RmDir(const wxString& dir)
393{
2e907fab 394 return DoSimpleCommand(_T("RMD"), dir);
f4ada568
GL
395}
396
397wxString wxFTP::Pwd()
398{
8e907a13
VZ
399 wxString path;
400
2e907fab 401 if ( CheckCommand(wxT("PWD"), '2') )
8e907a13 402 {
2e907fab 403 // the result is at least that long if CheckCommand() succeeded
8e907a13
VZ
404 const wxChar *p = m_lastResult.c_str() + LEN_CODE + 1;
405 if ( *p != _T('"') )
406 {
407 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p);
408 }
409 else
410 {
411 for ( p++; *p; p++ )
412 {
413 if ( *p == _T('"') )
414 {
415 // check if the quote is doubled
416 p++;
417 if ( !*p || *p != _T('"') )
418 {
419 // no, this is the end
420 break;
421 }
422 //else: yes, it is: this is an embedded quote in the
423 // filename, treat as normal char
424 }
425
426 path += *p;
427 }
428
429 if ( !*p )
430 {
431 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
432 m_lastResult.c_str() + LEN_CODE + 1);
433 }
434 }
435 }
2e907fab
VZ
436 else
437 {
438 wxLogDebug(_T("FTP PWD command failed."));
439 }
f4ada568 440
8e907a13 441 return path;
f4ada568
GL
442}
443
444bool wxFTP::Rename(const wxString& src, const wxString& dst)
445{
2e907fab
VZ
446 wxString str;
447
448 str = wxT("RNFR ") + src;
449 if ( !CheckCommand(str, '3') )
450 return FALSE;
f4ada568 451
2e907fab 452 str = wxT("RNTO ") + dst;
f4ada568 453
2e907fab 454 return CheckCommand(str, '2');
f4ada568
GL
455}
456
457bool wxFTP::RmFile(const wxString& path)
458{
2e907fab
VZ
459 wxString str;
460 str = wxT("DELE ") + path;
f4ada568 461
2e907fab 462 return CheckCommand(str, '2');
f4ada568
GL
463}
464
2e907fab
VZ
465// ----------------------------------------------------------------------------
466// wxFTP download and upload
467// ----------------------------------------------------------------------------
f4ada568 468
2e907fab
VZ
469class wxInputFTPStream : public wxSocketInputStream
470{
f4ada568 471public:
445783de
VZ
472 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
473 : wxSocketInputStream(*sock)
2e907fab 474 {
445783de
VZ
475 m_ftp = ftp;
476
477 // FIXME make the timeout configurable
478
479 // set a shorter than default timeout
480 m_i_socket->SetTimeout(60); // 1 minute
2e907fab
VZ
481 }
482
483 size_t GetSize() const { return m_ftpsize; }
484
485 virtual ~wxInputFTPStream()
486 {
b0ad2006
VZ
487 delete m_i_socket;
488
2e907fab
VZ
489 if ( LastError() == wxStream_NOERROR )
490 {
491 // wait for "226 transfer completed"
492 m_ftp->CheckResult('2');
493
494 m_ftp->m_streaming = FALSE;
495 }
496 else
497 {
498 m_ftp->Abort();
499 }
2e907fab
VZ
500 }
501
502 wxFTP *m_ftp;
503 size_t m_ftpsize;
f4ada568
GL
504};
505
2e907fab
VZ
506class wxOutputFTPStream : public wxSocketOutputStream
507{
f4ada568 508public:
2e907fab
VZ
509 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
510 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
511 {
512 }
513
514 virtual ~wxOutputFTPStream(void)
515 {
516 if ( IsOk() )
517 {
518 // close data connection first, this will generate "transfer
519 // completed" reply
520 delete m_o_socket;
521
522 // read this reply
523 m_ftp->CheckResult('2');
524
525 m_ftp->m_streaming = FALSE;
526 }
527 else
528 {
529 // abort data connection first
530 m_ftp->Abort();
531
532 // and close it after
533 delete m_o_socket;
534 }
535 }
536
537 wxFTP *m_ftp;
f4ada568
GL
538};
539
540wxSocketClient *wxFTP::GetPort()
541{
2e907fab
VZ
542 int a[6];
543
544 if ( !DoSimpleCommand(_T("PASV")) )
545 {
546 wxLogError(_("The FTP server doesn't support passive mode."));
547
548 return NULL;
549 }
550
551 const char *addrStart = wxStrchr(m_lastResult, _T('('));
552 if ( !addrStart )
553 {
554 m_lastError = wxPROTO_PROTERR;
555
556 return NULL;
557 }
558
559 const char *addrEnd = wxStrchr(addrStart, _T(')'));
560 if ( !addrEnd )
561 {
562 m_lastError = wxPROTO_PROTERR;
563
564 return NULL;
565 }
566
567 wxString straddr(addrStart + 1, addrEnd);
568
569 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
570 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
571
572 wxUint32 hostaddr = (wxUint16)a[5] << 24 |
573 (wxUint16)a[4] << 16 |
574 (wxUint16)a[3] << 8 |
575 a[2];
576 wxUint16 port = (wxUint16)a[0] << 8 | a[1];
577
578 wxIPV4address addr;
579 addr.Hostname(hostaddr);
580 addr.Service(port);
581
582 wxSocketClient *client = new wxSocketClient();
583 if ( !client->Connect(addr) )
584 {
585 delete client;
586 return NULL;
587 }
588
589 client->Notify(FALSE);
590
591 return client;
f4ada568
GL
592}
593
8e907a13 594bool wxFTP::Abort()
f4ada568 595{
8e907a13
VZ
596 if ( !m_streaming )
597 return TRUE;
2e907fab 598
8e907a13 599 m_streaming = FALSE;
2e907fab 600 if ( !CheckCommand(wxT("ABOR"), '4') )
8e907a13
VZ
601 return FALSE;
602
2e907fab 603 return CheckResult('2');
f4ada568
GL
604}
605
606wxInputStream *wxFTP::GetInputStream(const wxString& path)
607{
2e907fab
VZ
608 int pos_size;
609 wxInputFTPStream *in_stream;
610
611 if ( !m_modeSet && !SetTransferMode(BINARY) )
612 return NULL;
613
614 wxSocketClient *sock = GetPort();
f4ada568 615
2e907fab
VZ
616 if ( !sock )
617 {
618 m_lastError = wxPROTO_NETERR;
619 return NULL;
620 }
f4ada568 621
2e907fab
VZ
622 wxString tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
623 if ( !CheckCommand(tmp_str, '1') )
624 return NULL;
f4ada568 625
2e907fab 626 m_streaming = TRUE;
f4ada568 627
2e907fab 628 in_stream = new wxInputFTPStream(this, sock);
f4ada568 629
2e907fab
VZ
630 pos_size = m_lastResult.Index(wxT('('));
631 if ( pos_size != wxNOT_FOUND )
632 {
633 wxString str_size = m_lastResult(pos_size+1, m_lastResult.Index(wxT(')'))-1);
9a1b2c28 634
2e907fab
VZ
635 in_stream->m_ftpsize = wxAtoi(WXSTRINGCAST str_size);
636 }
9a1b2c28 637
2e907fab 638 sock->SetFlags(wxSOCKET_WAITALL);
9a1b2c28 639
2e907fab 640 return in_stream;
f4ada568
GL
641}
642
643wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
644{
2e907fab
VZ
645 if ( !m_modeSet && !SetTransferMode(BINARY) )
646 return NULL;
f4ada568 647
2e907fab 648 wxSocketClient *sock = GetPort();
f4ada568 649
2e907fab
VZ
650 wxString tmp_str = wxT("STOR ") + path;
651 if ( !CheckCommand(tmp_str, '1') )
652 return FALSE;
f4ada568 653
2e907fab 654 m_streaming = TRUE;
f4ada568 655
2e907fab 656 return new wxOutputFTPStream(this, sock);
f4ada568
GL
657}
658
2e907fab
VZ
659// ----------------------------------------------------------------------------
660// FTP directory listing
661// ----------------------------------------------------------------------------
662
663bool wxFTP::GetList(wxArrayString& files,
664 const wxString& wildcard,
665 bool details)
8e907a13
VZ
666{
667 wxSocketBase *sock = GetPort();
2e907fab 668 if (!sock)
8e907a13 669 return FALSE;
8e907a13 670
2e907fab
VZ
671 // NLST : List of Filenames (including Directory's !)
672 // LIST : depending on BS of FTP-Server
673 // - Unix : result like "ls" command
674 // - Windows : like "dir" command
675 // - others : ?
676 wxString line(details ? _T("LIST") : _T("NLST"));
8e907a13
VZ
677 if ( !!wildcard )
678 {
2e907fab 679 line << _T(' ') << wildcard;
8e907a13
VZ
680 }
681
2e907fab 682 if (!CheckCommand(line, '1'))
8e907a13
VZ
683 {
684 return FALSE;
685 }
8e907a13 686 files.Empty();
8e907a13
VZ
687 while ( ReadLine(sock, line) == wxPROTO_NOERR )
688 {
689 files.Add(line);
690 }
8e907a13
VZ
691 delete sock;
692
693 // the file list should be terminated by "226 Transfer complete""
2e907fab 694 if ( !CheckResult('2') )
8e907a13
VZ
695 return FALSE;
696
697 return TRUE;
698}
699
2e907fab
VZ
700#ifdef WXWIN_COMPATIBILITY_2
701// deprecated
702wxList *wxFTP::GetList(const wxString& wildcard, bool details)
f4ada568 703{
2e907fab
VZ
704 wxSocketBase *sock = GetPort();
705 if (!sock)
706 return FALSE;
707 wxList *file_list = new wxList;
708 wxString line;
709 // NLST : List of Filenames (including Directory's !)
710 // LIST : depending on BS of FTP-Server
711 // - Unix : result like "ls" command
712 // - Windows : like "dir" command
713 // - others : ?
714 if (!details)
715 line = _T("NLST"); // Default
716 else
717 line = _T("LIST");
718 if (!wildcard.IsNull())
719 line += wildcard;
720 if (!CheckCommand(line, '1'))
721 {
722 delete sock;
723 delete file_list;
724 return NULL;
725 }
726 while (GetLine(sock, line) == wxPROTO_NOERR)
727 {
728 file_list->Append((wxObject *)(new wxString(line)));
729 }
730 if (!CheckResult('2'))
731 {
732 delete sock;
733 file_list->DeleteContents(TRUE);
734 delete file_list;
735 return NULL;
736 }
737 return file_list;
f4ada568 738}
2e907fab
VZ
739#endif // WXWIN_COMPATIBILITY_2
740
35a4dab7
GL
741#endif
742 // wxUSE_SOCKETS