1. wxFTP works (somehow)
[wxWidgets.git] / src / common / ftp.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: ftp.cpp
3 // Purpose: FTP protocol
4 // Author: Guilhem Lavaux
5 // Modified by:
6 // Created: 07/07/1997
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997, 1998 Guilhem Lavaux
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "ftp.h"
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #if wxUSE_SOCKETS
24
25 #ifndef __MWERKS__
26 #include <memory.h>
27 #endif
28 #if defined(__WXMAC__)
29 #include "/wx/mac/macsock.h"
30 #endif
31
32 #include <stdlib.h>
33 #include "wx/string.h"
34 #include "wx/utils.h"
35 #include "wx/sckaddr.h"
36 #include "wx/socket.h"
37 #include "wx/url.h"
38 #include "wx/sckstrm.h"
39 #include "wx/protocol/protocol.h"
40 #include "wx/protocol/ftp.h"
41 #include "wx/log.h"
42
43 #ifdef __BORLANDC__
44 #pragma hdrstop
45 #endif
46
47 // the length of FTP status code (3 digits)
48 static const size_t LEN_CODE = 3;
49
50 #define FTP_BSIZE 1024
51
52 IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
53 IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), TRUE)
54
55 ////////////////////////////////////////////////////////////////
56 ////// wxFTP constructor and destructor ////////////////////////
57 ////////////////////////////////////////////////////////////////
58
59 wxFTP::wxFTP()
60 : wxProtocol()
61 {
62 m_lastError = wxPROTO_NOERR;
63 m_streaming = FALSE;
64
65 m_user = wxT("anonymous");
66 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
67
68 SetNotify(0);
69 SetFlags(wxSOCKET_NONE);
70 }
71
72 wxFTP::~wxFTP()
73 {
74 Close();
75 }
76
77 ////////////////////////////////////////////////////////////////
78 ////// wxFTP connect and login methods /////////////////////////
79 ////////////////////////////////////////////////////////////////
80 bool wxFTP::Connect(wxSockAddress& addr, bool WXUNUSED(wait))
81 {
82 if (!wxProtocol::Connect(addr)) {
83 m_lastError = wxPROTO_NETERR;
84 return FALSE;
85 }
86
87 if (!m_user || !m_passwd) {
88 m_lastError = wxPROTO_CONNERR;
89 return FALSE;
90 }
91
92 wxString command;
93
94 if (!GetResult('2')) {
95 Close();
96 return FALSE;
97 }
98
99 command.sprintf(wxT("USER %s"), (const wxChar *)m_user);
100 if (!SendCommand(command, '3')) {
101 Close();
102 return FALSE;
103 }
104
105 command.sprintf(wxT("PASS %s"), (const wxChar *)m_passwd);
106 if (!SendCommand(command, '2')) {
107 Close();
108 return FALSE;
109 }
110
111 return TRUE;
112 }
113
114 bool wxFTP::Connect(const wxString& host)
115 {
116 wxIPV4address addr;
117 wxString my_host = host;
118
119 addr.Hostname(my_host);
120 addr.Service(wxT("ftp"));
121
122 return Connect(addr);
123 }
124
125 bool wxFTP::Close(bool force)
126 {
127 if ( m_streaming )
128 {
129 if ( !force )
130 {
131 m_lastError = wxPROTO_STREAMING;
132 return FALSE;
133 }
134
135 (void)Abort();
136 }
137
138 if ( IsConnected() )
139 SendCommand(wxT("QUIT"), '2');
140
141 return wxSocketClient::Close();
142 }
143
144 // ============================================================================
145 // low level methods
146 // ============================================================================
147
148 // ----------------------------------------------------------------------------
149 // Send command to FTP server
150 // ----------------------------------------------------------------------------
151
152 bool wxFTP::SendCommand(const wxString& command, char exp_ret)
153 {
154 wxString tmp_str;
155
156 if (m_streaming)
157 {
158 m_lastError = wxPROTO_STREAMING;
159 return FALSE;
160 }
161
162 tmp_str = command + wxT("\r\n");
163 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
164 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
165 {
166 m_lastError = wxPROTO_NETERR;
167 return FALSE;
168 }
169
170 wxLogTrace(_T("ftp"), _T("==> %s"), command.c_str());
171
172 return GetResult(exp_ret);
173 }
174
175 // ----------------------------------------------------------------------------
176 // Recieve servers reply
177 // ----------------------------------------------------------------------------
178
179 bool wxFTP::GetResult(char exp)
180 {
181 wxString code;
182
183 // we handle multiline replies here according to RFC 959: it says that a
184 // reply may either be on 1 line of the form "xyz ..." or on several lines
185 // in whuch case it looks like
186 // xyz-...
187 // ...
188 // xyz ...
189 // and the intermeidate lines may start with xyz or not
190 bool badReply = FALSE;
191 bool firstLine = TRUE;
192 bool endOfReply = FALSE;
193 while ( !endOfReply && !badReply )
194 {
195 m_lastError = ReadLine(m_lastResult);
196 if ( m_lastError )
197 return FALSE;
198
199 // unless this is an intermediate line of a multiline reply, it must
200 // contain the code in the beginning and '-' or ' ' following it
201 if ( m_lastResult.Len() < LEN_CODE + 1 )
202 {
203 if ( firstLine )
204 {
205 badReply = TRUE;
206 }
207 else
208 {
209 wxLogTrace(_T("ftp"), _T("<== %s %s"),
210 code.c_str(), m_lastResult.c_str());
211 }
212 }
213 else // line has at least 4 chars
214 {
215 // this is the char which tells us what we're dealing with
216 wxChar chMarker = m_lastResult.GetChar(LEN_CODE);
217
218 if ( firstLine )
219 {
220 code = wxString(m_lastResult, LEN_CODE);
221 wxLogTrace(_T("ftp"), _T("<== %s %s"),
222 code.c_str(), m_lastResult.c_str() + LEN_CODE + 1);
223
224 switch ( chMarker )
225 {
226 case _T(' '):
227 endOfReply = TRUE;
228 break;
229
230 case _T('-'):
231 firstLine = FALSE;
232 break;
233
234 default:
235 // unexpected
236 badReply = TRUE;
237 }
238 }
239 else // subsequent line of multiline reply
240 {
241 if ( wxStrncmp(m_lastResult, code, LEN_CODE) == 0 )
242 {
243 if ( chMarker == _T(' ') )
244 {
245 endOfReply = TRUE;
246 }
247
248 wxLogTrace(_T("ftp"), _T("<== %s %s"),
249 code.c_str(), m_lastResult.c_str() + LEN_CODE + 1);
250 }
251 else
252 {
253 // just part of reply
254 wxLogTrace(_T("ftp"), _T("<== %s %s"),
255 code.c_str(), m_lastResult.c_str());
256 }
257 }
258 }
259 }
260
261 if ( badReply )
262 {
263 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
264 m_lastResult.c_str());
265
266 m_lastError = wxPROTO_PROTERR;
267
268 return FALSE;
269 }
270
271 if ( code.GetChar(0) != exp )
272 {
273 m_lastError = wxPROTO_PROTERR;
274
275 return FALSE;
276 }
277
278 return TRUE;
279 }
280
281 ////////////////////////////////////////////////////////////////
282 ////// wxFTP low-level methods /////////////////////////////////
283 ////////////////////////////////////////////////////////////////
284 bool wxFTP::ChDir(const wxString& dir)
285 {
286 wxString str = dir;
287
288 str.Prepend(wxT("CWD "));
289 return SendCommand(str, '2');
290 }
291
292 bool wxFTP::MkDir(const wxString& dir)
293 {
294 wxString str = dir;
295 str.Prepend(wxT("MKD "));
296 return SendCommand(str, '2');
297 }
298
299 bool wxFTP::RmDir(const wxString& dir)
300 {
301 wxString str = dir;
302
303 str.Prepend(wxT("PWD "));
304 return SendCommand(str, '2');
305 }
306
307 wxString wxFTP::Pwd()
308 {
309 wxString path;
310
311 if ( SendCommand(wxT("PWD"), '2') )
312 {
313 // the result is at least that long if SendCommand() succeeded
314 const wxChar *p = m_lastResult.c_str() + LEN_CODE + 1;
315 if ( *p != _T('"') )
316 {
317 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p);
318 }
319 else
320 {
321 for ( p++; *p; p++ )
322 {
323 if ( *p == _T('"') )
324 {
325 // check if the quote is doubled
326 p++;
327 if ( !*p || *p != _T('"') )
328 {
329 // no, this is the end
330 break;
331 }
332 //else: yes, it is: this is an embedded quote in the
333 // filename, treat as normal char
334 }
335
336 path += *p;
337 }
338
339 if ( !*p )
340 {
341 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
342 m_lastResult.c_str() + LEN_CODE + 1);
343 }
344 }
345 }
346
347 return path;
348 }
349
350 bool wxFTP::Rename(const wxString& src, const wxString& dst)
351 {
352 wxString str;
353
354 str = wxT("RNFR ") + src;
355 if (!SendCommand(str, '3'))
356 return FALSE;
357
358 str = wxT("RNTO ") + dst;
359 return SendCommand(str, '2');
360 }
361
362 bool wxFTP::RmFile(const wxString& path)
363 {
364 wxString str;
365
366 str = wxT("DELE ");
367 str += path;
368 return SendCommand(str, '2');
369 }
370
371 ////////////////////////////////////////////////////////////////
372 ////// wxFTP download*upload ///////////////////////////////////
373 ////////////////////////////////////////////////////////////////
374
375 class wxInputFTPStream : public wxSocketInputStream {
376 public:
377 wxFTP *m_ftp;
378 size_t m_ftpsize;
379
380 wxInputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
381 : wxSocketInputStream(*sock), m_ftp(ftp_clt) {}
382 size_t GetSize() const { return m_ftpsize; }
383 virtual ~wxInputFTPStream(void)
384 {
385 if (LastError() == wxStream_NOERROR)
386 m_ftp->GetResult('2');
387 else
388 m_ftp->Abort();
389 delete m_i_socket;
390 }
391 };
392
393 class wxOutputFTPStream : public wxSocketOutputStream {
394 public:
395 wxFTP *m_ftp;
396
397 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
398 : wxSocketOutputStream(*sock), m_ftp(ftp_clt) {}
399 virtual ~wxOutputFTPStream(void)
400 {
401 if (LastError() != wxStream_NOERROR)
402 m_ftp->GetResult('2');
403 else
404 m_ftp->Abort();
405 delete m_o_socket;
406 }
407 };
408
409 wxSocketClient *wxFTP::GetPort()
410 {
411 wxIPV4address addr;
412 wxSocketClient *client;
413 int a[6];
414 wxString straddr;
415 int addr_pos;
416 wxUint16 port;
417 wxUint32 hostaddr;
418
419 if (!SendCommand(wxT("PASV"), '2'))
420 return NULL;
421
422 addr_pos = m_lastResult.Find(wxT('('));
423 if (addr_pos == -1) {
424 m_lastError = wxPROTO_PROTERR;
425 return NULL;
426 }
427 straddr = m_lastResult(addr_pos+1, m_lastResult.Length());
428 wxSscanf((const wxChar *)straddr,wxT("%d,%d,%d,%d,%d,%d"),&a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
429
430 hostaddr = (wxUint16)a[5] << 24 | (wxUint16)a[4] << 16 |
431 (wxUint16)a[3] << 8 | a[2];
432 addr.Hostname(hostaddr);
433
434 port = (wxUint16)a[0] << 8 | a[1];
435 addr.Service(port);
436
437 client = new wxSocketClient();
438 if (!client->Connect(addr)) {
439 delete client;
440 return NULL;
441 }
442 client->Notify(FALSE);
443
444 return client;
445 }
446
447 bool wxFTP::Abort()
448 {
449 if ( !m_streaming )
450 return TRUE;
451
452 m_streaming = FALSE;
453 if ( !SendCommand(wxT("ABOR"), '4') )
454 return FALSE;
455
456 return GetResult('2');
457 }
458
459 wxInputStream *wxFTP::GetInputStream(const wxString& path)
460 {
461 wxString tmp_str;
462 int pos_size;
463 wxInputFTPStream *in_stream;
464
465 if (!SendCommand(wxT("TYPE I"), '2'))
466 return NULL;
467
468 wxSocketClient *sock = GetPort();
469
470 if (!sock) {
471 m_lastError = wxPROTO_NETERR;
472 return NULL;
473 }
474
475 tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
476 if (!SendCommand(tmp_str, '1'))
477 return NULL;
478
479 in_stream = new wxInputFTPStream(this, sock);
480
481 pos_size = m_lastResult.Index(wxT('('));
482 if (pos_size != wxNOT_FOUND) {
483 wxString str_size = m_lastResult(pos_size+1, m_lastResult.Index(wxT(')'))-1);
484
485 in_stream->m_ftpsize = wxAtoi(WXSTRINGCAST str_size);
486 }
487 sock->SetFlags(wxSOCKET_WAITALL);
488
489 return in_stream;
490 }
491
492 wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
493 {
494 wxString tmp_str;
495
496 if (!SendCommand(wxT("TYPE I"), '2'))
497 return NULL;
498
499 wxSocketClient *sock = GetPort();
500
501 tmp_str = wxT("STOR ") + path;
502 if (!SendCommand(tmp_str, '1'))
503 return FALSE;
504
505 return new wxOutputFTPStream(this, sock);
506 }
507
508 bool wxFTP::GetList(wxArrayString& files, const wxString& wildcard)
509 {
510 wxSocketBase *sock = GetPort();
511 if ( !sock )
512 {
513 return FALSE;
514 }
515
516 wxString line = _T("NLST");
517 if ( !!wildcard )
518 {
519 // notice that there is no space here
520 line += wildcard;
521 }
522
523 if ( !SendCommand(line, '1') )
524 {
525 return FALSE;
526 }
527
528 files.Empty();
529
530 while ( ReadLine(sock, line) == wxPROTO_NOERR )
531 {
532 files.Add(line);
533 }
534
535 delete sock;
536
537 // the file list should be terminated by "226 Transfer complete""
538 if ( !GetResult('2') )
539 return FALSE;
540
541 return TRUE;
542 }
543
544 wxList *wxFTP::GetList(const wxString& wildcard)
545 {
546 wxList *file_list = new wxList;
547 wxSocketBase *sock = GetPort();
548 wxString tmp_str = wxT("NLST");
549
550 if (!wildcard.IsNull())
551 tmp_str += wildcard;
552
553 if (!SendCommand(tmp_str, '1')) {
554 delete sock;
555 delete file_list;
556 return NULL;
557 }
558
559 while (GetLine(sock, tmp_str) == wxPROTO_NOERR) {
560 file_list->Append((wxObject *)(new wxString(tmp_str)));
561 }
562
563 if (!GetResult('2')) {
564 delete sock;
565 file_list->DeleteContents(TRUE);
566 delete file_list;
567 return NULL;
568 }
569
570 return file_list;
571 }
572 #endif
573 // wxUSE_SOCKETS