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