tons of wxFTP fixes and compilation fix for wxBase
[wxWidgets.git] / src / common / ftp.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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 // Created: 07/07/1997
11 // RCS-ID: $Id$
12 // Copyright: (c) 1997, 1998 Guilhem Lavaux
13 // Licence: wxWindows license
14 /////////////////////////////////////////////////////////////////////////////
15
16 // ============================================================================
17 // declarations
18 // ============================================================================
19
20 #ifdef __GNUG__
21 #pragma implementation "ftp.h"
22 #endif
23
24 // ----------------------------------------------------------------------------
25 // headers
26 // ----------------------------------------------------------------------------
27
28 // For compilers that support precompilation, includes "wx.h".
29 #include "wx/wxprec.h"
30
31 #ifdef __BORLANDC__
32 #pragma hdrstop
33 #endif
34
35 #if wxUSE_SOCKETS && wxUSE_STREAMS
36
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
44
45 #include "wx/sckaddr.h"
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
52 #if defined(__WXMAC__)
53 #include "/wx/mac/macsock.h"
54 #endif
55
56 #ifndef __MWERKS__
57 #include <memory.h>
58 #endif
59
60 // ----------------------------------------------------------------------------
61 // constants
62 // ----------------------------------------------------------------------------
63
64 // the length of FTP status code (3 digits)
65 static const size_t LEN_CODE = 3;
66
67 // ----------------------------------------------------------------------------
68 // macros
69 // ----------------------------------------------------------------------------
70
71 IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
72 IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), TRUE)
73
74 // ============================================================================
75 // implementation
76 // ============================================================================
77
78 // ----------------------------------------------------------------------------
79 // wxFTP constructor and destructor
80 // ----------------------------------------------------------------------------
81
82 wxFTP::wxFTP()
83 {
84 m_lastError = wxPROTO_NOERR;
85 m_streaming = FALSE;
86 m_modeSet = FALSE;
87
88 m_user = wxT("anonymous");
89 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
90
91 SetNotify(0);
92 SetFlags(wxSOCKET_NONE);
93 }
94
95 wxFTP::~wxFTP()
96 {
97 if ( m_streaming )
98 {
99 (void)Abort();
100 }
101
102 Close();
103 }
104
105 // ----------------------------------------------------------------------------
106 // wxFTP connect and login methods
107 // ----------------------------------------------------------------------------
108
109 bool wxFTP::Connect(wxSockAddress& addr, bool WXUNUSED(wait))
110 {
111 if ( !wxProtocol::Connect(addr) )
112 {
113 m_lastError = wxPROTO_NETERR;
114 return FALSE;
115 }
116
117 if ( !m_user )
118 {
119 m_lastError = wxPROTO_CONNERR;
120 return FALSE;
121 }
122
123 // we should have 220 welcome message
124 if ( !CheckResult('2') )
125 {
126 Close();
127 return FALSE;
128 }
129
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 }
138
139 if ( rc != '3' )
140 {
141 Close();
142 return FALSE;
143 }
144
145 command.Printf(wxT("PASS %s"), m_passwd.c_str());
146 if ( !CheckCommand(command, '2') )
147 {
148 Close();
149 return FALSE;
150 }
151
152 return TRUE;
153 }
154
155 bool wxFTP::Connect(const wxString& host)
156 {
157 wxIPV4address addr;
158 addr.Hostname(host);
159 addr.Service(wxT("ftp"));
160
161 return Connect(addr);
162 }
163
164 bool wxFTP::Close()
165 {
166 if ( m_streaming )
167 {
168 m_lastError = wxPROTO_STREAMING;
169 return FALSE;
170 }
171
172 if ( IsConnected() )
173 {
174 if ( !CheckCommand(wxT("QUIT"), '2') )
175 {
176 wxLogDebug(_T("Failed to close connection gracefully."));
177 }
178 }
179
180 return wxSocketClient::Close();
181 }
182
183 // ============================================================================
184 // low level methods
185 // ============================================================================
186
187 // ----------------------------------------------------------------------------
188 // Send command to FTP server
189 // ----------------------------------------------------------------------------
190
191 char wxFTP::SendCommand(const wxString& command)
192 {
193 if ( m_streaming )
194 {
195 m_lastError = wxPROTO_STREAMING;
196 return 0;
197 }
198
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())
202 {
203 m_lastError = wxPROTO_NETERR;
204 return 0;
205 }
206
207 wxLogTrace(_T("ftp"), _T("==> %s"), command.c_str());
208
209 return GetResult();
210 }
211
212 // ----------------------------------------------------------------------------
213 // Recieve servers reply
214 // ----------------------------------------------------------------------------
215
216 char wxFTP::GetResult()
217 {
218 wxString code;
219
220 // m_lastResult will contain the entire server response, possibly on
221 // multiple lines
222 m_lastResult.clear();
223
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 {
236 wxString line;
237 m_lastError = ReadLine(line);
238 if ( m_lastError )
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;
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
251 if ( line.Len() < LEN_CODE + 1 )
252 {
253 if ( firstLine )
254 {
255 badReply = TRUE;
256 }
257 else
258 {
259 wxLogTrace(_T("ftp"), _T("<== %s %s"),
260 code.c_str(), line.c_str());
261 }
262 }
263 else // line has at least 4 chars
264 {
265 // this is the char which tells us what we're dealing with
266 wxChar chMarker = line.GetChar(LEN_CODE);
267
268 if ( firstLine )
269 {
270 code = wxString(line, LEN_CODE);
271 wxLogTrace(_T("ftp"), _T("<== %s %s"),
272 code.c_str(), line.c_str() + LEN_CODE + 1);
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 {
291 if ( wxStrncmp(line, code, LEN_CODE) == 0 )
292 {
293 if ( chMarker == _T(' ') )
294 {
295 endOfReply = TRUE;
296 }
297
298 wxLogTrace(_T("ftp"), _T("<== %s %s"),
299 code.c_str(), line.c_str() + LEN_CODE + 1);
300 }
301 else
302 {
303 // just part of reply
304 wxLogTrace(_T("ftp"), _T("<== %s %s"),
305 code.c_str(), line.c_str());
306 }
307 }
308 }
309 }
310
311 if ( badReply )
312 {
313 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
314 m_lastResult.c_str());
315
316 m_lastError = wxPROTO_PROTERR;
317
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
329 bool 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
352 return FALSE;
353 }
354
355 m_modeSet = TRUE;
356
357 return TRUE;
358 }
359
360 bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
361 {
362 wxString fullcmd = command;
363 if ( !arg.empty() )
364 {
365 fullcmd << _T(' ') << arg;
366 }
367
368 if ( !CheckCommand(fullcmd, '2') )
369 {
370 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
371
372 return FALSE;
373 }
374
375 return TRUE;
376 }
377
378 bool wxFTP::ChDir(const wxString& dir)
379 {
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?
383
384 return DoSimpleCommand(_T("CWD"), dir);
385 }
386
387 bool wxFTP::MkDir(const wxString& dir)
388 {
389 return DoSimpleCommand(_T("MKD"), dir);
390 }
391
392 bool wxFTP::RmDir(const wxString& dir)
393 {
394 return DoSimpleCommand(_T("RMD"), dir);
395 }
396
397 wxString wxFTP::Pwd()
398 {
399 wxString path;
400
401 if ( CheckCommand(wxT("PWD"), '2') )
402 {
403 // the result is at least that long if CheckCommand() succeeded
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 }
436 else
437 {
438 wxLogDebug(_T("FTP PWD command failed."));
439 }
440
441 return path;
442 }
443
444 bool wxFTP::Rename(const wxString& src, const wxString& dst)
445 {
446 wxString str;
447
448 str = wxT("RNFR ") + src;
449 if ( !CheckCommand(str, '3') )
450 return FALSE;
451
452 str = wxT("RNTO ") + dst;
453
454 return CheckCommand(str, '2');
455 }
456
457 bool wxFTP::RmFile(const wxString& path)
458 {
459 wxString str;
460 str = wxT("DELE ") + path;
461
462 return CheckCommand(str, '2');
463 }
464
465 // ----------------------------------------------------------------------------
466 // wxFTP download and upload
467 // ----------------------------------------------------------------------------
468
469 class wxInputFTPStream : public wxSocketInputStream
470 {
471 public:
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;
498 };
499
500 class wxOutputFTPStream : public wxSocketOutputStream
501 {
502 public:
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;
532 };
533
534 wxSocketClient *wxFTP::GetPort()
535 {
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;
586 }
587
588 bool wxFTP::Abort()
589 {
590 if ( !m_streaming )
591 return TRUE;
592
593 m_streaming = FALSE;
594 if ( !CheckCommand(wxT("ABOR"), '4') )
595 return FALSE;
596
597 return CheckResult('2');
598 }
599
600 wxInputStream *wxFTP::GetInputStream(const wxString& path)
601 {
602 int pos_size;
603 wxInputFTPStream *in_stream;
604
605 if ( !m_modeSet && !SetTransferMode(BINARY) )
606 return NULL;
607
608 wxSocketClient *sock = GetPort();
609
610 if ( !sock )
611 {
612 m_lastError = wxPROTO_NETERR;
613 return NULL;
614 }
615
616 wxString tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
617 if ( !CheckCommand(tmp_str, '1') )
618 return NULL;
619
620 m_streaming = TRUE;
621
622 in_stream = new wxInputFTPStream(this, sock);
623
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);
628
629 in_stream->m_ftpsize = wxAtoi(WXSTRINGCAST str_size);
630 }
631
632 sock->SetFlags(wxSOCKET_WAITALL);
633
634 return in_stream;
635 }
636
637 wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
638 {
639 if ( !m_modeSet && !SetTransferMode(BINARY) )
640 return NULL;
641
642 wxSocketClient *sock = GetPort();
643
644 wxString tmp_str = wxT("STOR ") + path;
645 if ( !CheckCommand(tmp_str, '1') )
646 return FALSE;
647
648 m_streaming = TRUE;
649
650 return new wxOutputFTPStream(this, sock);
651 }
652
653 // ----------------------------------------------------------------------------
654 // FTP directory listing
655 // ----------------------------------------------------------------------------
656
657 bool wxFTP::GetList(wxArrayString& files,
658 const wxString& wildcard,
659 bool details)
660 {
661 wxSocketBase *sock = GetPort();
662 if (!sock)
663 return FALSE;
664
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"));
671 if ( !!wildcard )
672 {
673 line << _T(' ') << wildcard;
674 }
675
676 if (!CheckCommand(line, '1'))
677 {
678 return FALSE;
679 }
680 files.Empty();
681 while ( ReadLine(sock, line) == wxPROTO_NOERR )
682 {
683 files.Add(line);
684 }
685 delete sock;
686
687 // the file list should be terminated by "226 Transfer complete""
688 if ( !CheckResult('2') )
689 return FALSE;
690
691 return TRUE;
692 }
693
694 #ifdef WXWIN_COMPATIBILITY_2
695 // deprecated
696 wxList *wxFTP::GetList(const wxString& wildcard, bool details)
697 {
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;
732 }
733 #endif // WXWIN_COMPATIBILITY_2
734
735 #endif
736 // wxUSE_SOCKETS