]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
Also redirect wxEVT_KEY_UP and wxEVT_CHAR from the embedded wxTextCtrl
[wxWidgets.git] / src / common / ftp.cpp
CommitLineData
f4ada568 1/////////////////////////////////////////////////////////////////////////////
e2454588 2// Name: common/ftp.cpp
f4ada568
GL
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, ...)
e2454588 10// Randall Fox (support for active mode)
f4ada568
GL
11// Created: 07/07/1997
12// RCS-ID: $Id$
13// Copyright: (c) 1997, 1998 Guilhem Lavaux
e2454588 14// (c) 1998-2004 wxWidgets team
65571936 15// Licence: wxWindows licence
f4ada568
GL
16/////////////////////////////////////////////////////////////////////////////
17
2e907fab
VZ
18// ============================================================================
19// declarations
20// ============================================================================
21
2e907fab
VZ
22// ----------------------------------------------------------------------------
23// headers
24// ----------------------------------------------------------------------------
25
ec45f8ee
UU
26// For compilers that support precompilation, includes "wx.h".
27#include "wx/wxprec.h"
28
19ae5cf0 29#ifdef __BORLANDC__
2df7be7f 30 #pragma hdrstop
19ae5cf0
UU
31#endif
32
a5d46b73 33#if wxUSE_PROTOCOL_FTP
35a4dab7 34
2e907fab
VZ
35#ifndef WX_PRECOMP
36 #include <stdlib.h>
37 #include "wx/string.h"
38 #include "wx/utils.h"
39 #include "wx/log.h"
40 #include "wx/intl.h"
2523e9b7 41 #include "wx/wxcrtvararg.h"
2e907fab 42#endif // WX_PRECOMP
17dff81c 43
f4ada568 44#include "wx/sckaddr.h"
f4ada568
GL
45#include "wx/socket.h"
46#include "wx/url.h"
47#include "wx/sckstrm.h"
48#include "wx/protocol/protocol.h"
49#include "wx/protocol/ftp.h"
50
2e907fab
VZ
51#ifndef __MWERKS__
52 #include <memory.h>
f4ada568
GL
53#endif
54
2e907fab
VZ
55// ----------------------------------------------------------------------------
56// constants
57// ----------------------------------------------------------------------------
58
8e907a13
VZ
59// the length of FTP status code (3 digits)
60static const size_t LEN_CODE = 3;
61
2e907fab
VZ
62// ----------------------------------------------------------------------------
63// macros
64// ----------------------------------------------------------------------------
f4ada568 65
f4ada568 66IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
a62848fd 67IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), true)
f4ada568 68
2e907fab
VZ
69// ============================================================================
70// implementation
71// ============================================================================
72
73// ----------------------------------------------------------------------------
74// wxFTP constructor and destructor
75// ----------------------------------------------------------------------------
f4ada568
GL
76
77wxFTP::wxFTP()
f4ada568 78{
a62848fd 79 m_streaming = false;
3f2bcf34 80 m_currentTransfermode = NONE;
f4ada568 81
730b772b
FM
82 m_username = wxT("anonymous");
83 m_password << wxGetUserId() << wxT('@') << wxGetFullHostName();
f4ada568 84
8e907a13 85 SetNotify(0);
89ba044b 86 SetFlags(wxSOCKET_NOWAIT);
e2454588 87 m_bPassive = true;
e2454588 88 m_bEncounteredError = false;
f4ada568
GL
89}
90
91wxFTP::~wxFTP()
92{
7ff26ec2
VZ
93 if ( m_streaming )
94 {
e2454588
VZ
95 // if we are streaming, this will issue
96 // an FTP ABORT command, to tell the server we are aborting
7ff26ec2
VZ
97 (void)Abort();
98 }
99
e2454588 100 // now this issues a "QUIT" command to tell the server we are
8e907a13 101 Close();
f4ada568
GL
102}
103
2e907fab
VZ
104// ----------------------------------------------------------------------------
105// wxFTP connect and login methods
106// ----------------------------------------------------------------------------
107
ddc7f0c9 108bool wxFTP::Connect(const wxSockAddress& addr, bool WXUNUSED(wait))
f4ada568 109{
2e907fab
VZ
110 if ( !wxProtocol::Connect(addr) )
111 {
112 m_lastError = wxPROTO_NETERR;
a62848fd 113 return false;
2e907fab 114 }
f4ada568 115
730b772b 116 if ( !m_username )
2e907fab
VZ
117 {
118 m_lastError = wxPROTO_CONNERR;
a62848fd 119 return false;
2e907fab 120 }
f4ada568 121
2e907fab
VZ
122 // we should have 220 welcome message
123 if ( !CheckResult('2') )
124 {
125 Close();
a62848fd 126 return false;
2e907fab 127 }
f4ada568 128
2e907fab 129 wxString command;
730b772b 130 command.Printf(wxT("USER %s"), m_username.c_str());
2e907fab
VZ
131 char rc = SendCommand(command);
132 if ( rc == '2' )
133 {
134 // 230 return: user accepted without password
730b772b 135 m_lastError = wxPROTO_NOERR;
a62848fd 136 return true;
2e907fab 137 }
f4ada568 138
2e907fab
VZ
139 if ( rc != '3' )
140 {
730b772b 141 m_lastError = wxPROTO_CONNERR;
2e907fab 142 Close();
a62848fd 143 return false;
2e907fab 144 }
f4ada568 145
730b772b 146 command.Printf(wxT("PASS %s"), m_password.c_str());
2e907fab
VZ
147 if ( !CheckCommand(command, '2') )
148 {
730b772b 149 m_lastError = wxPROTO_CONNERR;
2e907fab 150 Close();
a62848fd 151 return false;
2e907fab 152 }
f4ada568 153
730b772b 154 m_lastError = wxPROTO_NOERR;
a62848fd 155 return true;
f4ada568
GL
156}
157
158bool wxFTP::Connect(const wxString& host)
159{
2e907fab
VZ
160 wxIPV4address addr;
161 addr.Hostname(host);
162 addr.Service(wxT("ftp"));
f4ada568 163
2e907fab 164 return Connect(addr);
f4ada568
GL
165}
166
7ff26ec2 167bool wxFTP::Close()
f4ada568 168{
8e907a13
VZ
169 if ( m_streaming )
170 {
7ff26ec2 171 m_lastError = wxPROTO_STREAMING;
a62848fd 172 return false;
8e907a13
VZ
173 }
174
175 if ( IsConnected() )
2e907fab
VZ
176 {
177 if ( !CheckCommand(wxT("QUIT"), '2') )
178 {
730b772b 179 m_lastError = wxPROTO_CONNERR;
2e907fab
VZ
180 wxLogDebug(_T("Failed to close connection gracefully."));
181 }
182 }
e8773bdf 183
8e907a13 184 return wxSocketClient::Close();
f4ada568
GL
185}
186
8e907a13
VZ
187// ============================================================================
188// low level methods
189// ============================================================================
190
21f0f3a2
FM
191wxSocketBase *wxFTP::AcceptIfActive(wxSocketBase *sock)
192{
193 if ( m_bPassive )
194 return sock;
195
196 // now wait for a connection from server
197 wxSocketServer *sockSrv = (wxSocketServer *)sock;
198 if ( !sockSrv->WaitForAccept() )
199 {
200 m_lastError = wxPROTO_CONNERR;
201 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
202 delete sock;
203 sock = NULL;
204 }
205 else
206 {
730b772b 207 m_lastError = wxPROTO_NOERR;
21f0f3a2
FM
208 sock = sockSrv->Accept(true);
209 delete sockSrv;
210 }
211
212 return sock;
213}
214
215bool wxFTP::Abort()
216{
217 if ( !m_streaming )
218 return true;
219
220 m_streaming = false;
221 if ( !CheckCommand(wxT("ABOR"), '4') )
222 return false;
223
224 return CheckResult('2');
225}
226
21f0f3a2 227
8e907a13
VZ
228// ----------------------------------------------------------------------------
229// Send command to FTP server
230// ----------------------------------------------------------------------------
231
2e907fab 232char wxFTP::SendCommand(const wxString& command)
f4ada568 233{
2e907fab 234 if ( m_streaming )
8e907a13
VZ
235 {
236 m_lastError = wxPROTO_STREAMING;
2e907fab 237 return 0;
8e907a13
VZ
238 }
239
2e907fab 240 wxString tmp_str = command + wxT("\r\n");
8e907a13
VZ
241 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
242 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
243 {
244 m_lastError = wxPROTO_NETERR;
2e907fab 245 return 0;
8e907a13
VZ
246 }
247
b92fd37c
VZ
248#ifdef __WXDEBUG__
249 // don't show the passwords in the logs (even in debug ones)
250 wxString cmd, password;
251 if ( command.Upper().StartsWith(_T("PASS "), &password) )
252 {
253 cmd << _T("PASS ") << wxString(_T('*'), password.length());
254 }
255 else
256 {
257 cmd = command;
258 }
259
260 wxLogTrace(FTP_TRACE_MASK, _T("==> %s"), cmd.c_str());
261#endif // __WXDEBUG__
8e907a13 262
730b772b 263 m_lastError = wxPROTO_NOERR;
2e907fab 264 return GetResult();
f4ada568
GL
265}
266
8e907a13 267// ----------------------------------------------------------------------------
21f0f3a2 268// Receive servers reply
8e907a13
VZ
269// ----------------------------------------------------------------------------
270
2e907fab 271char wxFTP::GetResult()
f4ada568 272{
e2454588
VZ
273 // if we've already had a read or write timeout error, the connection is
274 // probably toast, so don't bother, it just wastes the users time
275 if ( m_bEncounteredError )
276 return 0;
277
8e907a13
VZ
278 wxString code;
279
2e907fab
VZ
280 // m_lastResult will contain the entire server response, possibly on
281 // multiple lines
282 m_lastResult.clear();
283
8e907a13
VZ
284 // we handle multiline replies here according to RFC 959: it says that a
285 // reply may either be on 1 line of the form "xyz ..." or on several lines
286 // in whuch case it looks like
287 // xyz-...
288 // ...
289 // xyz ...
290 // and the intermeidate lines may start with xyz or not
a62848fd
WS
291 bool badReply = false;
292 bool firstLine = true;
293 bool endOfReply = false;
8e907a13
VZ
294 while ( !endOfReply && !badReply )
295 {
2e907fab 296 wxString line;
e2454588 297 m_lastError = ReadLine(this,line);
8e907a13 298 if ( m_lastError )
e2454588
VZ
299 {
300 m_bEncounteredError = true;
2e907fab 301 return 0;
e2454588 302 }
2e907fab
VZ
303
304 if ( !m_lastResult.empty() )
305 {
306 // separate from last line
307 m_lastResult += _T('\n');
308 }
309
310 m_lastResult += line;
8e907a13
VZ
311
312 // unless this is an intermediate line of a multiline reply, it must
313 // contain the code in the beginning and '-' or ' ' following it
2e907fab 314 if ( line.Len() < LEN_CODE + 1 )
8e907a13
VZ
315 {
316 if ( firstLine )
317 {
a62848fd 318 badReply = true;
8e907a13
VZ
319 }
320 else
321 {
b92fd37c 322 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 323 code.c_str(), line.c_str());
8e907a13
VZ
324 }
325 }
326 else // line has at least 4 chars
327 {
328 // this is the char which tells us what we're dealing with
2e907fab 329 wxChar chMarker = line.GetChar(LEN_CODE);
8e907a13
VZ
330
331 if ( firstLine )
332 {
2e907fab 333 code = wxString(line, LEN_CODE);
b92fd37c 334 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 335 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
336
337 switch ( chMarker )
338 {
339 case _T(' '):
a62848fd 340 endOfReply = true;
8e907a13
VZ
341 break;
342
343 case _T('-'):
a62848fd 344 firstLine = false;
8e907a13
VZ
345 break;
346
347 default:
348 // unexpected
a62848fd 349 badReply = true;
8e907a13
VZ
350 }
351 }
352 else // subsequent line of multiline reply
353 {
86501081 354 if ( line.compare(0, LEN_CODE, code) == 0 )
8e907a13
VZ
355 {
356 if ( chMarker == _T(' ') )
357 {
a62848fd 358 endOfReply = true;
8e907a13
VZ
359 }
360
b92fd37c 361 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 362 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
363 }
364 else
365 {
366 // just part of reply
b92fd37c 367 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 368 code.c_str(), line.c_str());
8e907a13
VZ
369 }
370 }
371 }
372 }
f4ada568 373
8e907a13
VZ
374 if ( badReply )
375 {
376 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
377 m_lastResult.c_str());
f4ada568 378
8e907a13 379 m_lastError = wxPROTO_PROTERR;
f4ada568 380
2e907fab
VZ
381 return 0;
382 }
730b772b
FM
383 else
384 m_lastError = wxPROTO_NOERR;
2e907fab
VZ
385
386 // if we got here we must have a non empty code string
f9df3f05 387 return (char)code[0u];
2e907fab
VZ
388}
389
390// ----------------------------------------------------------------------------
391// wxFTP simple commands
392// ----------------------------------------------------------------------------
393
394bool wxFTP::SetTransferMode(TransferMode transferMode)
395{
b92fd37c
VZ
396 if ( transferMode == m_currentTransfermode )
397 {
398 // nothing to do
a62848fd 399 return true;
b92fd37c
VZ
400 }
401
2e907fab
VZ
402 wxString mode;
403 switch ( transferMode )
404 {
405 default:
406 wxFAIL_MSG(_T("unknown FTP transfer mode"));
407 // fall through
408
409 case BINARY:
410 mode = _T('I');
411 break;
412
413 case ASCII:
414 mode = _T('A');
415 break;
416 }
417
418 if ( !DoSimpleCommand(_T("TYPE"), mode) )
419 {
86501081 420 wxLogError(_("Failed to set FTP transfer mode to %s."),
86435b1a 421 (transferMode == ASCII ? _("ASCII") : _("binary")));
2e907fab 422
a62848fd 423 return false;
f4ada568 424 }
8e907a13 425
3103e8a9 426 // If we get here the operation has been successfully completed
3f2bcf34
VZ
427 // Set the status-member
428 m_currentTransfermode = transferMode;
2e907fab 429
a62848fd 430 return true;
2e907fab
VZ
431}
432
433bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
434{
435 wxString fullcmd = command;
436 if ( !arg.empty() )
8e907a13 437 {
2e907fab
VZ
438 fullcmd << _T(' ') << arg;
439 }
440
441 if ( !CheckCommand(fullcmd, '2') )
442 {
443 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
730b772b 444 m_lastError = wxPROTO_NETERR;
8e907a13 445
a62848fd 446 return false;
8e907a13
VZ
447 }
448
730b772b 449 m_lastError = wxPROTO_NOERR;
a62848fd 450 return true;
f4ada568
GL
451}
452
f4ada568
GL
453bool wxFTP::ChDir(const wxString& dir)
454{
2e907fab
VZ
455 // some servers might not understand ".." if they use different directory
456 // tree conventions, but they always understand CDUP - should we use it if
457 // dir == ".."? OTOH, do such servers (still) exist?
f4ada568 458
2e907fab 459 return DoSimpleCommand(_T("CWD"), dir);
f4ada568
GL
460}
461
462bool wxFTP::MkDir(const wxString& dir)
463{
2e907fab 464 return DoSimpleCommand(_T("MKD"), dir);
f4ada568
GL
465}
466
467bool wxFTP::RmDir(const wxString& dir)
468{
2e907fab 469 return DoSimpleCommand(_T("RMD"), dir);
f4ada568
GL
470}
471
472wxString wxFTP::Pwd()
473{
8e907a13
VZ
474 wxString path;
475
2e907fab 476 if ( CheckCommand(wxT("PWD"), '2') )
8e907a13 477 {
2e907fab 478 // the result is at least that long if CheckCommand() succeeded
c9f78968 479 wxString::const_iterator p = m_lastResult.begin() + LEN_CODE + 1;
8e907a13
VZ
480 if ( *p != _T('"') )
481 {
c9f78968
VS
482 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
483 wxString(p, m_lastResult.end()));
8e907a13
VZ
484 }
485 else
486 {
c9f78968 487 for ( ++p; (bool)*p; ++p ) // FIXME-DMARS
8e907a13
VZ
488 {
489 if ( *p == _T('"') )
490 {
491 // check if the quote is doubled
c9f78968 492 ++p;
8e907a13
VZ
493 if ( !*p || *p != _T('"') )
494 {
495 // no, this is the end
496 break;
497 }
498 //else: yes, it is: this is an embedded quote in the
499 // filename, treat as normal char
500 }
501
502 path += *p;
503 }
504
505 if ( !*p )
506 {
507 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
508 m_lastResult.c_str() + LEN_CODE + 1);
509 }
510 }
511 }
2e907fab
VZ
512 else
513 {
730b772b 514 m_lastError = wxPROTO_PROTERR;
2e907fab
VZ
515 wxLogDebug(_T("FTP PWD command failed."));
516 }
f4ada568 517
8e907a13 518 return path;
f4ada568
GL
519}
520
521bool wxFTP::Rename(const wxString& src, const wxString& dst)
522{
2e907fab
VZ
523 wxString str;
524
525 str = wxT("RNFR ") + src;
526 if ( !CheckCommand(str, '3') )
a62848fd 527 return false;
f4ada568 528
2e907fab 529 str = wxT("RNTO ") + dst;
f4ada568 530
2e907fab 531 return CheckCommand(str, '2');
f4ada568
GL
532}
533
534bool wxFTP::RmFile(const wxString& path)
535{
2e907fab
VZ
536 wxString str;
537 str = wxT("DELE ") + path;
f4ada568 538
2e907fab 539 return CheckCommand(str, '2');
f4ada568
GL
540}
541
2e907fab 542// ----------------------------------------------------------------------------
21f0f3a2 543// wxFTP port methods
2e907fab 544// ----------------------------------------------------------------------------
f4ada568 545
e2454588
VZ
546wxSocketBase *wxFTP::GetPort()
547{
548 /*
549 PASSIVE: Client sends a "PASV" to the server. The server responds with
550 an address and port number which it will be listening on. Then
551 the client connects to the server at the specified address and
552 port.
553
554 ACTIVE: Client sends the server a PORT command which includes an
555 address and port number which the client will be listening on.
556 The server then connects to the client at that address and
557 port.
558 */
559
560 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
561 if ( !socket )
562 {
563 m_bEncounteredError = true;
564 return NULL;
565 }
566
567 // Now set the time for the new socket to the default or user selected
568 // timeout period
569 socket->SetTimeout(m_uiDefaultTimeout);
570
571 return socket;
572}
573
fbfb8bcc
VZ
574wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
575 const wxIPV4address& addrNew)
e2454588
VZ
576{
577 // Just fills in the return value with the local IP
578 // address of the current socket. Also it fill in the
579 // PORT which the client will be listening on
2e907fab 580
e2454588
VZ
581 wxString addrIP = addrLocal.IPAddress();
582 int portNew = addrNew.Service();
583
584 // We need to break the PORT number in bytes
585 addrIP.Replace(_T("."), _T(","));
586 addrIP << _T(',')
587 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
588 << wxString::Format(_T("%d"), portNew & 0xff);
589
590 // Now we have a value like "10,0,0,1,5,23"
591 return addrIP;
592}
593
594wxSocketBase *wxFTP::GetActivePort()
595{
596 // we need an address to listen on
597 wxIPV4address addrNew, addrLocal;
598 GetLocal(addrLocal);
599 addrNew.AnyAddress();
600 addrNew.Service(0); // pick an open port number.
601
602 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
603 if (!sockSrv->Ok())
604 {
605 // We use Ok() here to see if everything is ok
606 m_lastError = wxPROTO_PROTERR;
607 delete sockSrv;
2e907fab
VZ
608 return NULL;
609 }
610
e2454588
VZ
611 //gets the new address, actually it is just the port number
612 sockSrv->GetLocal(addrNew);
613
614 // Now we create the argument of the PORT command, we send in both
615 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
616 // value in addrLocal
617 wxString port = GetPortCmdArgument(addrLocal, addrNew);
e0920004 618 if ( !DoSimpleCommand(_T("PORT"), port) )
2e907fab
VZ
619 {
620 m_lastError = wxPROTO_PROTERR;
e2454588
VZ
621 delete sockSrv;
622 wxLogError(_("The FTP server doesn't support the PORT command."));
623 return NULL;
624 }
2e907fab 625
730b772b 626 m_lastError = wxPROTO_NOERR;
e2454588
VZ
627 sockSrv->Notify(false); // Don't send any events
628 return sockSrv;
629}
630
631wxSocketBase *wxFTP::GetPassivePort()
632{
633 if ( !DoSimpleCommand(_T("PASV")) )
634 {
730b772b 635 m_lastError = wxPROTO_PROTERR;
e2454588 636 wxLogError(_("The FTP server doesn't support passive mode."));
2e907fab
VZ
637 return NULL;
638 }
639
86501081
VS
640 size_t addrStart = m_lastResult.find(_T('('));
641 size_t addrEnd = (addrStart == wxString::npos)
642 ? wxString::npos
643 : m_lastResult.find(_T(')'), addrStart);
644
645 if ( addrEnd == wxString::npos )
2e907fab
VZ
646 {
647 m_lastError = wxPROTO_PROTERR;
2e907fab
VZ
648 return NULL;
649 }
650
e2454588
VZ
651 // get the port number and address
652 int a[6];
86501081 653 wxString straddr(m_lastResult, addrStart + 1, addrEnd - (addrStart + 1));
2e907fab
VZ
654 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
655 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
656
377f6397
SC
657 wxUint32 hostaddr = (wxUint16)a[2] << 24 |
658 (wxUint16)a[3] << 16 |
659 (wxUint16)a[4] << 8 |
660 a[5];
254a2129 661 wxUint16 port = (wxUint16)(a[0] << 8 | a[1]);
2e907fab
VZ
662
663 wxIPV4address addr;
664 addr.Hostname(hostaddr);
665 addr.Service(port);
666
667 wxSocketClient *client = new wxSocketClient();
668 if ( !client->Connect(addr) )
669 {
730b772b 670 m_lastError = wxPROTO_CONNERR;
2e907fab
VZ
671 delete client;
672 return NULL;
673 }
674
a62848fd 675 client->Notify(false);
2e907fab 676
730b772b 677 m_lastError = wxPROTO_NOERR;
2e907fab 678 return client;
f4ada568
GL
679}
680
21f0f3a2
FM
681
682// ----------------------------------------------------------------------------
683// wxFTP download and upload
684// ----------------------------------------------------------------------------
685
686class wxInputFTPStream : public wxSocketInputStream
f4ada568 687{
21f0f3a2
FM
688public:
689 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
690 : wxSocketInputStream(*sock)
691 {
692 m_ftp = ftp;
693 // socket timeout automatically set in GetPort function
694 }
2e907fab 695
21f0f3a2
FM
696 virtual ~wxInputFTPStream()
697 {
698 delete m_i_socket; // keep at top
8e907a13 699
21f0f3a2
FM
700 // when checking the result, the stream will
701 // almost always show an error, even if the file was
702 // properly transfered, thus, lets just grab the result
703
704 // we are looking for "226 transfer completed"
705 char code = m_ftp->GetResult();
706 if ('2' == code)
707 {
708 // it was a good transfer.
709 // we're done!
710 m_ftp->m_streaming = false;
711 return;
712 }
713 // did we timeout?
714 if (0 == code)
715 {
716 // the connection is probably toast. issue an abort, and
717 // then a close. there won't be any more waiting
718 // for this connection
719 m_ftp->Abort();
720 m_ftp->Close();
721 return;
722 }
723 // There was a problem with the transfer and the server
724 // has acknowledged it. If we issue an "ABORT" now, the user
725 // would get the "226" for the abort and think the xfer was
726 // complete, thus, don't do anything here, just return
727 }
728
729 wxFTP *m_ftp;
730
c0c133e1 731 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream);
21f0f3a2
FM
732};
733
734class wxOutputFTPStream : public wxSocketOutputStream
735{
736public:
737 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
738 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
739 {
740 }
741
742 virtual ~wxOutputFTPStream(void)
743 {
744 if ( IsOk() )
745 {
746 // close data connection first, this will generate "transfer
747 // completed" reply
748 delete m_o_socket;
749
750 // read this reply
751 m_ftp->GetResult(); // save result so user can get to it
752
753 m_ftp->m_streaming = false;
754 }
755 else
756 {
757 // abort data connection first
758 m_ftp->Abort();
759
760 // and close it after
761 delete m_o_socket;
762 }
763 }
764
765 wxFTP *m_ftp;
766
c0c133e1 767 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream);
21f0f3a2 768};
f4ada568
GL
769
770wxInputStream *wxFTP::GetInputStream(const wxString& path)
771{
3f2bcf34 772 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
730b772b
FM
773 {
774 m_lastError = wxPROTO_CONNERR;
2e907fab 775 return NULL;
730b772b 776 }
2e907fab 777
e2454588 778 wxSocketBase *sock = GetPort();
f4ada568 779
2e907fab
VZ
780 if ( !sock )
781 {
782 m_lastError = wxPROTO_NETERR;
783 return NULL;
784 }
f4ada568 785
6cc280ca 786 wxString tmp_str = wxT("RETR ") + wxURI::Unescape(path);
2e907fab
VZ
787 if ( !CheckCommand(tmp_str, '1') )
788 return NULL;
f4ada568 789
e2454588
VZ
790 sock = AcceptIfActive(sock);
791 if ( !sock )
730b772b
FM
792 {
793 m_lastError = wxPROTO_CONNERR;
e2454588 794 return NULL;
730b772b 795 }
f4ada568 796
e2454588 797 sock->SetFlags(wxSOCKET_WAITALL);
9a1b2c28 798
e2454588 799 m_streaming = true;
9a1b2c28 800
e2454588 801 wxInputFTPStream *in_stream = new wxInputFTPStream(this, sock);
9a1b2c28 802
730b772b 803 m_lastError = wxPROTO_NOERR;
2e907fab 804 return in_stream;
f4ada568
GL
805}
806
807wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
808{
3f2bcf34 809 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
730b772b
FM
810 {
811 m_lastError = wxPROTO_CONNERR;
2e907fab 812 return NULL;
730b772b 813 }
f4ada568 814
e2454588 815 wxSocketBase *sock = GetPort();
f4ada568 816
2e907fab
VZ
817 wxString tmp_str = wxT("STOR ") + path;
818 if ( !CheckCommand(tmp_str, '1') )
8f901032 819 return NULL;
f4ada568 820
e2454588
VZ
821 sock = AcceptIfActive(sock);
822
a62848fd 823 m_streaming = true;
f4ada568 824
730b772b 825 m_lastError = wxPROTO_NOERR;
2e907fab 826 return new wxOutputFTPStream(this, sock);
f4ada568
GL
827}
828
2e907fab
VZ
829// ----------------------------------------------------------------------------
830// FTP directory listing
831// ----------------------------------------------------------------------------
832
833bool wxFTP::GetList(wxArrayString& files,
834 const wxString& wildcard,
835 bool details)
8e907a13
VZ
836{
837 wxSocketBase *sock = GetPort();
730b772b
FM
838 if (!sock) {
839 m_lastError = wxPROTO_NETERR;
a62848fd 840 return false;
730b772b 841 }
8e907a13 842
2e907fab
VZ
843 // NLST : List of Filenames (including Directory's !)
844 // LIST : depending on BS of FTP-Server
845 // - Unix : result like "ls" command
846 // - Windows : like "dir" command
847 // - others : ?
848 wxString line(details ? _T("LIST") : _T("NLST"));
f9df3f05 849 if ( !wildcard.empty() )
8e907a13 850 {
2e907fab 851 line << _T(' ') << wildcard;
8e907a13
VZ
852 }
853
e2454588 854 if ( !CheckCommand(line, '1') )
8e907a13 855 {
e2454588 856 m_lastError = wxPROTO_PROTERR;
ef5eca7a 857 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
e2454588 858 delete sock;
a62848fd 859 return false;
8e907a13 860 }
e2454588
VZ
861
862 sock = AcceptIfActive(sock);
730b772b
FM
863 if ( !sock ) {
864 m_lastError = wxPROTO_CONNERR;
e2454588 865 return false;
730b772b 866 }
e2454588 867
8e907a13 868 files.Empty();
e2454588 869 while (ReadLine(sock, line) == wxPROTO_NOERR )
8e907a13
VZ
870 {
871 files.Add(line);
872 }
e2454588 873
8e907a13
VZ
874 delete sock;
875
876 // the file list should be terminated by "226 Transfer complete""
730b772b 877 m_lastError = wxPROTO_NOERR;
e2454588 878 return CheckResult('2');
8e907a13
VZ
879}
880
b92fd37c
VZ
881bool wxFTP::FileExists(const wxString& fileName)
882{
3f2bcf34
VZ
883 // This function checks if the file specified in fileName exists in the
884 // current dir. It does so by simply doing an NLST (via GetList).
885 // If this succeeds (and the list is not empty) the file exists.
886
a62848fd 887 bool retval = false;
3f2bcf34
VZ
888 wxArrayString fileList;
889
a62848fd 890 if ( GetList(fileList, fileName, false) )
3f2bcf34
VZ
891 {
892 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
6a17b868 893 // displays this behaviour when queried on a nonexistent file:
3f2bcf34
VZ
894 // NLST this_file_does_not_exist
895 // 150 Opening ASCII data connection for directory listing
896 // (no data transferred)
897 // 226 Transfer complete
b92fd37c
VZ
898 // Here wxFTP::GetList(...) will succeed but it will return an empty
899 // list.
900 retval = !fileList.IsEmpty();
3f2bcf34 901 }
b92fd37c 902
3f2bcf34 903 return retval;
b92fd37c
VZ
904}
905
906// ----------------------------------------------------------------------------
907// FTP GetSize
908// ----------------------------------------------------------------------------
909
910int wxFTP::GetFileSize(const wxString& fileName)
911{
3f2bcf34
VZ
912 // return the filesize of the given file if possible
913 // return -1 otherwise (predominantly if file doesn't exist
914 // in current dir)
915
916 int filesize = -1;
b92fd37c 917
3f2bcf34 918 // Check for existance of file via wxFTP::FileExists(...)
b92fd37c
VZ
919 if ( FileExists(fileName) )
920 {
921 wxString command;
922
923 // First try "SIZE" command using BINARY(IMAGE) transfermode
924 // Especially UNIX ftp-servers distinguish between the different
925 // transfermodes and reports different filesizes accordingly.
926 // The BINARY size is the interesting one: How much memory
927 // will we need to hold this file?
3f2bcf34 928 TransferMode oldTransfermode = m_currentTransfermode;
b92fd37c
VZ
929 SetTransferMode(BINARY);
930 command << _T("SIZE ") << fileName;
931
932 bool ok = CheckCommand(command, '2');
933
934 if ( ok )
935 {
936 // The answer should be one line: "213 <filesize>\n"
937 // 213 is File Status (STD9)
3f2bcf34
VZ
938 // "SIZE" is not described anywhere..? It works on most servers
939 int statuscode;
940 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
b92fd37c 941 &statuscode, &filesize) == 2 )
3f2bcf34
VZ
942 {
943 // We've gotten a good reply.
a62848fd 944 ok = true;
3f2bcf34
VZ
945 }
946 else
947 {
948 // Something bad happened.. A "2yz" reply with no size
949 // Fallback
a62848fd 950 ok = false;
3f2bcf34 951 }
b92fd37c 952 }
3f2bcf34
VZ
953
954 // Set transfermode back to the original. Only the "SIZE"-command
955 // is dependant on transfermode
b92fd37c
VZ
956 if ( oldTransfermode != NONE )
957 {
958 SetTransferMode(oldTransfermode);
959 }
3f2bcf34 960
999836aa
VZ
961 // this is not a direct else clause.. The size command might return an
962 // invalid "2yz" reply
963 if ( !ok )
b92fd37c 964 {
3f2bcf34
VZ
965 // The server didn't understand the "SIZE"-command or it
966 // returned an invalid reply.
967 // We now try to get details for the file with a "LIST"-command
968 // and then parse the output from there..
969 wxArrayString fileList;
a62848fd 970 if ( GetList(fileList, fileName, true) )
3f2bcf34
VZ
971 {
972 if ( !fileList.IsEmpty() )
973 {
b92fd37c
VZ
974 // We _should_ only get one line in return, but just to be
975 // safe we run through the line(s) returned and look for a
976 // substring containing the name we are looking for. We
977 // stop the iteration at the first occurrence of the
978 // filename. The search is not case-sensitive.
a62848fd 979 bool foundIt = false;
b92fd37c
VZ
980
981 size_t i;
b4a980f4 982 for ( i = 0; !foundIt && i < fileList.GetCount(); i++ )
3f2bcf34
VZ
983 {
984 foundIt = fileList[i].Upper().Contains(fileName.Upper());
985 }
b92fd37c 986
3f2bcf34
VZ
987 if ( foundIt )
988 {
b92fd37c
VZ
989 // The index i points to the first occurrence of
990 // fileName in the array Now we have to find out what
991 // format the LIST has returned. There are two
992 // "schools": Unix-like
993 //
994 // '-rw-rw-rw- owner group size month day time filename'
995 //
996 // or Windows-like
997 //
998 // 'date size filename'
999
1000 // check if the first character is '-'. This would
1001 // indicate Unix-style (this also limits this function
1002 // to searching for files, not directories)
1003 if ( fileList[i].Mid(0, 1) == _T("-") )
3f2bcf34 1004 {
b92fd37c 1005
3f2bcf34 1006 if ( wxSscanf(fileList[i].c_str(),
1489a2c0 1007 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
999836aa 1008 &filesize) != 9 )
3f2bcf34
VZ
1009 {
1010 // Hmm... Invalid response
1011 wxLogTrace(FTP_TRACE_MASK,
b92fd37c 1012 _T("Invalid LIST response"));
3f2bcf34
VZ
1013 }
1014 }
1015 else // Windows-style response (?)
1016 {
1017 if ( wxSscanf(fileList[i].c_str(),
b92fd37c 1018 _T("%*s %*s %i %*s"),
999836aa 1019 &filesize) != 4 )
3f2bcf34
VZ
1020 {
1021 // something bad happened..?
1022 wxLogTrace(FTP_TRACE_MASK,
b92fd37c 1023 _T("Invalid or unknown LIST response"));
3f2bcf34
VZ
1024 }
1025 }
1026 }
1027 }
1028 }
b92fd37c
VZ
1029 }
1030 }
1031
3f2bcf34
VZ
1032 // filesize might still be -1 when exiting
1033 return filesize;
b92fd37c
VZ
1034}
1035
a5d46b73
VZ
1036#endif // wxUSE_PROTOCOL_FTP
1037