]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
regenerated the makefiles to include filename.cpp
[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:
2e907fab
VZ
472 wxInputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
473 : wxSocketInputStream(*sock), m_ftp(ftp_clt)
474 {
475 }
476
477 size_t GetSize() const { return m_ftpsize; }
478
479 virtual ~wxInputFTPStream()
480 {
481 if ( LastError() == wxStream_NOERROR )
482 {
483 // wait for "226 transfer completed"
484 m_ftp->CheckResult('2');
485
486 m_ftp->m_streaming = FALSE;
487 }
488 else
489 {
490 m_ftp->Abort();
491 }
492
493 delete m_i_socket;
494 }
495
496 wxFTP *m_ftp;
497 size_t m_ftpsize;
f4ada568
GL
498};
499
2e907fab
VZ
500class wxOutputFTPStream : public wxSocketOutputStream
501{
f4ada568 502public:
2e907fab
VZ
503 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
504 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
505 {
506 }
507
508 virtual ~wxOutputFTPStream(void)
509 {
510 if ( IsOk() )
511 {
512 // close data connection first, this will generate "transfer
513 // completed" reply
514 delete m_o_socket;
515
516 // read this reply
517 m_ftp->CheckResult('2');
518
519 m_ftp->m_streaming = FALSE;
520 }
521 else
522 {
523 // abort data connection first
524 m_ftp->Abort();
525
526 // and close it after
527 delete m_o_socket;
528 }
529 }
530
531 wxFTP *m_ftp;
f4ada568
GL
532};
533
534wxSocketClient *wxFTP::GetPort()
535{
2e907fab
VZ
536 int a[6];
537
538 if ( !DoSimpleCommand(_T("PASV")) )
539 {
540 wxLogError(_("The FTP server doesn't support passive mode."));
541
542 return NULL;
543 }
544
545 const char *addrStart = wxStrchr(m_lastResult, _T('('));
546 if ( !addrStart )
547 {
548 m_lastError = wxPROTO_PROTERR;
549
550 return NULL;
551 }
552
553 const char *addrEnd = wxStrchr(addrStart, _T(')'));
554 if ( !addrEnd )
555 {
556 m_lastError = wxPROTO_PROTERR;
557
558 return NULL;
559 }
560
561 wxString straddr(addrStart + 1, addrEnd);
562
563 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
564 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
565
566 wxUint32 hostaddr = (wxUint16)a[5] << 24 |
567 (wxUint16)a[4] << 16 |
568 (wxUint16)a[3] << 8 |
569 a[2];
570 wxUint16 port = (wxUint16)a[0] << 8 | a[1];
571
572 wxIPV4address addr;
573 addr.Hostname(hostaddr);
574 addr.Service(port);
575
576 wxSocketClient *client = new wxSocketClient();
577 if ( !client->Connect(addr) )
578 {
579 delete client;
580 return NULL;
581 }
582
583 client->Notify(FALSE);
584
585 return client;
f4ada568
GL
586}
587
8e907a13 588bool wxFTP::Abort()
f4ada568 589{
8e907a13
VZ
590 if ( !m_streaming )
591 return TRUE;
2e907fab 592
8e907a13 593 m_streaming = FALSE;
2e907fab 594 if ( !CheckCommand(wxT("ABOR"), '4') )
8e907a13
VZ
595 return FALSE;
596
2e907fab 597 return CheckResult('2');
f4ada568
GL
598}
599
600wxInputStream *wxFTP::GetInputStream(const wxString& path)
601{
2e907fab
VZ
602 int pos_size;
603 wxInputFTPStream *in_stream;
604
605 if ( !m_modeSet && !SetTransferMode(BINARY) )
606 return NULL;
607
608 wxSocketClient *sock = GetPort();
f4ada568 609
2e907fab
VZ
610 if ( !sock )
611 {
612 m_lastError = wxPROTO_NETERR;
613 return NULL;
614 }
f4ada568 615
2e907fab
VZ
616 wxString tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
617 if ( !CheckCommand(tmp_str, '1') )
618 return NULL;
f4ada568 619
2e907fab 620 m_streaming = TRUE;
f4ada568 621
2e907fab 622 in_stream = new wxInputFTPStream(this, sock);
f4ada568 623
2e907fab
VZ
624 pos_size = m_lastResult.Index(wxT('('));
625 if ( pos_size != wxNOT_FOUND )
626 {
627 wxString str_size = m_lastResult(pos_size+1, m_lastResult.Index(wxT(')'))-1);
9a1b2c28 628
2e907fab
VZ
629 in_stream->m_ftpsize = wxAtoi(WXSTRINGCAST str_size);
630 }
9a1b2c28 631
2e907fab 632 sock->SetFlags(wxSOCKET_WAITALL);
9a1b2c28 633
2e907fab 634 return in_stream;
f4ada568
GL
635}
636
637wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
638{
2e907fab
VZ
639 if ( !m_modeSet && !SetTransferMode(BINARY) )
640 return NULL;
f4ada568 641
2e907fab 642 wxSocketClient *sock = GetPort();
f4ada568 643
2e907fab
VZ
644 wxString tmp_str = wxT("STOR ") + path;
645 if ( !CheckCommand(tmp_str, '1') )
646 return FALSE;
f4ada568 647
2e907fab 648 m_streaming = TRUE;
f4ada568 649
2e907fab 650 return new wxOutputFTPStream(this, sock);
f4ada568
GL
651}
652
2e907fab
VZ
653// ----------------------------------------------------------------------------
654// FTP directory listing
655// ----------------------------------------------------------------------------
656
657bool wxFTP::GetList(wxArrayString& files,
658 const wxString& wildcard,
659 bool details)
8e907a13
VZ
660{
661 wxSocketBase *sock = GetPort();
2e907fab 662 if (!sock)
8e907a13 663 return FALSE;
8e907a13 664
2e907fab
VZ
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
669 // - others : ?
670 wxString line(details ? _T("LIST") : _T("NLST"));
8e907a13
VZ
671 if ( !!wildcard )
672 {
2e907fab 673 line << _T(' ') << wildcard;
8e907a13
VZ
674 }
675
2e907fab 676 if (!CheckCommand(line, '1'))
8e907a13
VZ
677 {
678 return FALSE;
679 }
8e907a13 680 files.Empty();
8e907a13
VZ
681 while ( ReadLine(sock, line) == wxPROTO_NOERR )
682 {
683 files.Add(line);
684 }
8e907a13
VZ
685 delete sock;
686
687 // the file list should be terminated by "226 Transfer complete""
2e907fab 688 if ( !CheckResult('2') )
8e907a13
VZ
689 return FALSE;
690
691 return TRUE;
692}
693
2e907fab
VZ
694#ifdef WXWIN_COMPATIBILITY_2
695// deprecated
696wxList *wxFTP::GetList(const wxString& wildcard, bool details)
f4ada568 697{
2e907fab
VZ
698 wxSocketBase *sock = GetPort();
699 if (!sock)
700 return FALSE;
701 wxList *file_list = new wxList;
702 wxString line;
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
707 // - others : ?
708 if (!details)
709 line = _T("NLST"); // Default
710 else
711 line = _T("LIST");
712 if (!wildcard.IsNull())
713 line += wildcard;
714 if (!CheckCommand(line, '1'))
715 {
716 delete sock;
717 delete file_list;
718 return NULL;
719 }
720 while (GetLine(sock, line) == wxPROTO_NOERR)
721 {
722 file_list->Append((wxObject *)(new wxString(line)));
723 }
724 if (!CheckResult('2'))
725 {
726 delete sock;
727 file_list->DeleteContents(TRUE);
728 delete file_list;
729 return NULL;
730 }
731 return file_list;
f4ada568 732}
2e907fab
VZ
733#endif // WXWIN_COMPATIBILITY_2
734
35a4dab7
GL
735#endif
736 // wxUSE_SOCKETS