]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
Added wxVector::swap().
[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 // don't show the passwords in the logs (even in debug ones)
249 wxString cmd, password;
250 if ( command.Upper().StartsWith(_T("PASS "), &password) )
251 {
252 cmd << _T("PASS ") << wxString(_T('*'), password.length());
253 }
254 else
255 {
256 cmd = command;
257 }
258
0576cd9e 259 LogRequest(cmd);
8e907a13 260
730b772b 261 m_lastError = wxPROTO_NOERR;
2e907fab 262 return GetResult();
f4ada568
GL
263}
264
8e907a13 265// ----------------------------------------------------------------------------
21f0f3a2 266// Receive servers reply
8e907a13
VZ
267// ----------------------------------------------------------------------------
268
2e907fab 269char wxFTP::GetResult()
f4ada568 270{
e2454588
VZ
271 // if we've already had a read or write timeout error, the connection is
272 // probably toast, so don't bother, it just wastes the users time
273 if ( m_bEncounteredError )
274 return 0;
275
8e907a13
VZ
276 wxString code;
277
2e907fab
VZ
278 // m_lastResult will contain the entire server response, possibly on
279 // multiple lines
280 m_lastResult.clear();
281
8e907a13
VZ
282 // we handle multiline replies here according to RFC 959: it says that a
283 // reply may either be on 1 line of the form "xyz ..." or on several lines
284 // in whuch case it looks like
285 // xyz-...
286 // ...
287 // xyz ...
288 // and the intermeidate lines may start with xyz or not
a62848fd
WS
289 bool badReply = false;
290 bool firstLine = true;
291 bool endOfReply = false;
8e907a13
VZ
292 while ( !endOfReply && !badReply )
293 {
2e907fab 294 wxString line;
e2454588 295 m_lastError = ReadLine(this,line);
8e907a13 296 if ( m_lastError )
e2454588
VZ
297 {
298 m_bEncounteredError = true;
2e907fab 299 return 0;
e2454588 300 }
2e907fab 301
0576cd9e
VZ
302 LogResponse(line);
303
2e907fab
VZ
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 319 }
8e907a13
VZ
320 }
321 else // line has at least 4 chars
322 {
323 // this is the char which tells us what we're dealing with
2e907fab 324 wxChar chMarker = line.GetChar(LEN_CODE);
8e907a13
VZ
325
326 if ( firstLine )
327 {
2e907fab 328 code = wxString(line, LEN_CODE);
8e907a13
VZ
329
330 switch ( chMarker )
331 {
332 case _T(' '):
a62848fd 333 endOfReply = true;
8e907a13
VZ
334 break;
335
336 case _T('-'):
a62848fd 337 firstLine = false;
8e907a13
VZ
338 break;
339
340 default:
341 // unexpected
a62848fd 342 badReply = true;
8e907a13
VZ
343 }
344 }
345 else // subsequent line of multiline reply
346 {
86501081 347 if ( line.compare(0, LEN_CODE, code) == 0 )
8e907a13
VZ
348 {
349 if ( chMarker == _T(' ') )
350 {
a62848fd 351 endOfReply = true;
8e907a13 352 }
8e907a13
VZ
353 }
354 }
355 }
356 }
f4ada568 357
8e907a13
VZ
358 if ( badReply )
359 {
360 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
361 m_lastResult.c_str());
f4ada568 362
8e907a13 363 m_lastError = wxPROTO_PROTERR;
f4ada568 364
2e907fab
VZ
365 return 0;
366 }
730b772b
FM
367 else
368 m_lastError = wxPROTO_NOERR;
2e907fab
VZ
369
370 // if we got here we must have a non empty code string
f9df3f05 371 return (char)code[0u];
2e907fab
VZ
372}
373
374// ----------------------------------------------------------------------------
375// wxFTP simple commands
376// ----------------------------------------------------------------------------
377
378bool wxFTP::SetTransferMode(TransferMode transferMode)
379{
b92fd37c
VZ
380 if ( transferMode == m_currentTransfermode )
381 {
382 // nothing to do
a62848fd 383 return true;
b92fd37c
VZ
384 }
385
2e907fab
VZ
386 wxString mode;
387 switch ( transferMode )
388 {
389 default:
390 wxFAIL_MSG(_T("unknown FTP transfer mode"));
391 // fall through
392
393 case BINARY:
394 mode = _T('I');
395 break;
396
397 case ASCII:
398 mode = _T('A');
399 break;
400 }
401
402 if ( !DoSimpleCommand(_T("TYPE"), mode) )
403 {
86501081 404 wxLogError(_("Failed to set FTP transfer mode to %s."),
86435b1a 405 (transferMode == ASCII ? _("ASCII") : _("binary")));
2e907fab 406
a62848fd 407 return false;
f4ada568 408 }
8e907a13 409
3103e8a9 410 // If we get here the operation has been successfully completed
3f2bcf34
VZ
411 // Set the status-member
412 m_currentTransfermode = transferMode;
2e907fab 413
a62848fd 414 return true;
2e907fab
VZ
415}
416
417bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
418{
419 wxString fullcmd = command;
420 if ( !arg.empty() )
8e907a13 421 {
2e907fab
VZ
422 fullcmd << _T(' ') << arg;
423 }
424
425 if ( !CheckCommand(fullcmd, '2') )
426 {
427 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
730b772b 428 m_lastError = wxPROTO_NETERR;
8e907a13 429
a62848fd 430 return false;
8e907a13
VZ
431 }
432
730b772b 433 m_lastError = wxPROTO_NOERR;
a62848fd 434 return true;
f4ada568
GL
435}
436
f4ada568
GL
437bool wxFTP::ChDir(const wxString& dir)
438{
2e907fab
VZ
439 // some servers might not understand ".." if they use different directory
440 // tree conventions, but they always understand CDUP - should we use it if
441 // dir == ".."? OTOH, do such servers (still) exist?
f4ada568 442
2e907fab 443 return DoSimpleCommand(_T("CWD"), dir);
f4ada568
GL
444}
445
446bool wxFTP::MkDir(const wxString& dir)
447{
2e907fab 448 return DoSimpleCommand(_T("MKD"), dir);
f4ada568
GL
449}
450
451bool wxFTP::RmDir(const wxString& dir)
452{
2e907fab 453 return DoSimpleCommand(_T("RMD"), dir);
f4ada568
GL
454}
455
456wxString wxFTP::Pwd()
457{
8e907a13
VZ
458 wxString path;
459
2e907fab 460 if ( CheckCommand(wxT("PWD"), '2') )
8e907a13 461 {
2e907fab 462 // the result is at least that long if CheckCommand() succeeded
c9f78968 463 wxString::const_iterator p = m_lastResult.begin() + LEN_CODE + 1;
8e907a13
VZ
464 if ( *p != _T('"') )
465 {
c9f78968
VS
466 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
467 wxString(p, m_lastResult.end()));
8e907a13
VZ
468 }
469 else
470 {
c9f78968 471 for ( ++p; (bool)*p; ++p ) // FIXME-DMARS
8e907a13
VZ
472 {
473 if ( *p == _T('"') )
474 {
475 // check if the quote is doubled
c9f78968 476 ++p;
8e907a13
VZ
477 if ( !*p || *p != _T('"') )
478 {
479 // no, this is the end
480 break;
481 }
482 //else: yes, it is: this is an embedded quote in the
483 // filename, treat as normal char
484 }
485
486 path += *p;
487 }
488
489 if ( !*p )
490 {
491 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
492 m_lastResult.c_str() + LEN_CODE + 1);
493 }
494 }
495 }
2e907fab
VZ
496 else
497 {
730b772b 498 m_lastError = wxPROTO_PROTERR;
2e907fab
VZ
499 wxLogDebug(_T("FTP PWD command failed."));
500 }
f4ada568 501
8e907a13 502 return path;
f4ada568
GL
503}
504
505bool wxFTP::Rename(const wxString& src, const wxString& dst)
506{
2e907fab
VZ
507 wxString str;
508
509 str = wxT("RNFR ") + src;
510 if ( !CheckCommand(str, '3') )
a62848fd 511 return false;
f4ada568 512
2e907fab 513 str = wxT("RNTO ") + dst;
f4ada568 514
2e907fab 515 return CheckCommand(str, '2');
f4ada568
GL
516}
517
518bool wxFTP::RmFile(const wxString& path)
519{
2e907fab
VZ
520 wxString str;
521 str = wxT("DELE ") + path;
f4ada568 522
2e907fab 523 return CheckCommand(str, '2');
f4ada568
GL
524}
525
2e907fab 526// ----------------------------------------------------------------------------
21f0f3a2 527// wxFTP port methods
2e907fab 528// ----------------------------------------------------------------------------
f4ada568 529
e2454588
VZ
530wxSocketBase *wxFTP::GetPort()
531{
532 /*
533 PASSIVE: Client sends a "PASV" to the server. The server responds with
534 an address and port number which it will be listening on. Then
535 the client connects to the server at the specified address and
536 port.
537
538 ACTIVE: Client sends the server a PORT command which includes an
539 address and port number which the client will be listening on.
540 The server then connects to the client at that address and
541 port.
542 */
543
544 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
545 if ( !socket )
546 {
547 m_bEncounteredError = true;
548 return NULL;
549 }
550
551 // Now set the time for the new socket to the default or user selected
552 // timeout period
553 socket->SetTimeout(m_uiDefaultTimeout);
554
555 return socket;
556}
557
fbfb8bcc
VZ
558wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
559 const wxIPV4address& addrNew)
e2454588
VZ
560{
561 // Just fills in the return value with the local IP
562 // address of the current socket. Also it fill in the
563 // PORT which the client will be listening on
2e907fab 564
e2454588
VZ
565 wxString addrIP = addrLocal.IPAddress();
566 int portNew = addrNew.Service();
567
568 // We need to break the PORT number in bytes
569 addrIP.Replace(_T("."), _T(","));
570 addrIP << _T(',')
571 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
572 << wxString::Format(_T("%d"), portNew & 0xff);
573
574 // Now we have a value like "10,0,0,1,5,23"
575 return addrIP;
576}
577
578wxSocketBase *wxFTP::GetActivePort()
579{
580 // we need an address to listen on
581 wxIPV4address addrNew, addrLocal;
582 GetLocal(addrLocal);
583 addrNew.AnyAddress();
584 addrNew.Service(0); // pick an open port number.
585
586 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
587 if (!sockSrv->Ok())
588 {
589 // We use Ok() here to see if everything is ok
590 m_lastError = wxPROTO_PROTERR;
591 delete sockSrv;
2e907fab
VZ
592 return NULL;
593 }
594
e2454588
VZ
595 //gets the new address, actually it is just the port number
596 sockSrv->GetLocal(addrNew);
597
598 // Now we create the argument of the PORT command, we send in both
599 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
600 // value in addrLocal
601 wxString port = GetPortCmdArgument(addrLocal, addrNew);
e0920004 602 if ( !DoSimpleCommand(_T("PORT"), port) )
2e907fab
VZ
603 {
604 m_lastError = wxPROTO_PROTERR;
e2454588
VZ
605 delete sockSrv;
606 wxLogError(_("The FTP server doesn't support the PORT command."));
607 return NULL;
608 }
2e907fab 609
730b772b 610 m_lastError = wxPROTO_NOERR;
e2454588
VZ
611 sockSrv->Notify(false); // Don't send any events
612 return sockSrv;
613}
614
615wxSocketBase *wxFTP::GetPassivePort()
616{
617 if ( !DoSimpleCommand(_T("PASV")) )
618 {
730b772b 619 m_lastError = wxPROTO_PROTERR;
e2454588 620 wxLogError(_("The FTP server doesn't support passive mode."));
2e907fab
VZ
621 return NULL;
622 }
623
86501081
VS
624 size_t addrStart = m_lastResult.find(_T('('));
625 size_t addrEnd = (addrStart == wxString::npos)
626 ? wxString::npos
627 : m_lastResult.find(_T(')'), addrStart);
628
629 if ( addrEnd == wxString::npos )
2e907fab
VZ
630 {
631 m_lastError = wxPROTO_PROTERR;
2e907fab
VZ
632 return NULL;
633 }
634
e2454588
VZ
635 // get the port number and address
636 int a[6];
86501081 637 wxString straddr(m_lastResult, addrStart + 1, addrEnd - (addrStart + 1));
2e907fab
VZ
638 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
639 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
640
377f6397
SC
641 wxUint32 hostaddr = (wxUint16)a[2] << 24 |
642 (wxUint16)a[3] << 16 |
643 (wxUint16)a[4] << 8 |
644 a[5];
254a2129 645 wxUint16 port = (wxUint16)(a[0] << 8 | a[1]);
2e907fab
VZ
646
647 wxIPV4address addr;
648 addr.Hostname(hostaddr);
649 addr.Service(port);
650
651 wxSocketClient *client = new wxSocketClient();
652 if ( !client->Connect(addr) )
653 {
730b772b 654 m_lastError = wxPROTO_CONNERR;
2e907fab
VZ
655 delete client;
656 return NULL;
657 }
658
a62848fd 659 client->Notify(false);
2e907fab 660
730b772b 661 m_lastError = wxPROTO_NOERR;
2e907fab 662 return client;
f4ada568
GL
663}
664
21f0f3a2
FM
665
666// ----------------------------------------------------------------------------
667// wxFTP download and upload
668// ----------------------------------------------------------------------------
669
670class wxInputFTPStream : public wxSocketInputStream
f4ada568 671{
21f0f3a2
FM
672public:
673 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
674 : wxSocketInputStream(*sock)
675 {
676 m_ftp = ftp;
677 // socket timeout automatically set in GetPort function
678 }
2e907fab 679
21f0f3a2
FM
680 virtual ~wxInputFTPStream()
681 {
682 delete m_i_socket; // keep at top
8e907a13 683
21f0f3a2
FM
684 // when checking the result, the stream will
685 // almost always show an error, even if the file was
686 // properly transfered, thus, lets just grab the result
687
688 // we are looking for "226 transfer completed"
689 char code = m_ftp->GetResult();
690 if ('2' == code)
691 {
692 // it was a good transfer.
693 // we're done!
694 m_ftp->m_streaming = false;
695 return;
696 }
697 // did we timeout?
698 if (0 == code)
699 {
700 // the connection is probably toast. issue an abort, and
701 // then a close. there won't be any more waiting
702 // for this connection
703 m_ftp->Abort();
704 m_ftp->Close();
705 return;
706 }
707 // There was a problem with the transfer and the server
708 // has acknowledged it. If we issue an "ABORT" now, the user
709 // would get the "226" for the abort and think the xfer was
710 // complete, thus, don't do anything here, just return
711 }
712
713 wxFTP *m_ftp;
714
c0c133e1 715 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream);
21f0f3a2
FM
716};
717
718class wxOutputFTPStream : public wxSocketOutputStream
719{
720public:
721 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
722 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
723 {
724 }
725
726 virtual ~wxOutputFTPStream(void)
727 {
728 if ( IsOk() )
729 {
730 // close data connection first, this will generate "transfer
731 // completed" reply
732 delete m_o_socket;
733
734 // read this reply
735 m_ftp->GetResult(); // save result so user can get to it
736
737 m_ftp->m_streaming = false;
738 }
739 else
740 {
741 // abort data connection first
742 m_ftp->Abort();
743
744 // and close it after
745 delete m_o_socket;
746 }
747 }
748
749 wxFTP *m_ftp;
750
c0c133e1 751 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream);
21f0f3a2 752};
f4ada568
GL
753
754wxInputStream *wxFTP::GetInputStream(const wxString& path)
755{
3f2bcf34 756 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
730b772b
FM
757 {
758 m_lastError = wxPROTO_CONNERR;
2e907fab 759 return NULL;
730b772b 760 }
2e907fab 761
e2454588 762 wxSocketBase *sock = GetPort();
f4ada568 763
2e907fab
VZ
764 if ( !sock )
765 {
766 m_lastError = wxPROTO_NETERR;
767 return NULL;
768 }
f4ada568 769
6cc280ca 770 wxString tmp_str = wxT("RETR ") + wxURI::Unescape(path);
2e907fab
VZ
771 if ( !CheckCommand(tmp_str, '1') )
772 return NULL;
f4ada568 773
e2454588
VZ
774 sock = AcceptIfActive(sock);
775 if ( !sock )
730b772b
FM
776 {
777 m_lastError = wxPROTO_CONNERR;
e2454588 778 return NULL;
730b772b 779 }
f4ada568 780
e2454588 781 sock->SetFlags(wxSOCKET_WAITALL);
9a1b2c28 782
e2454588 783 m_streaming = true;
9a1b2c28 784
e2454588 785 wxInputFTPStream *in_stream = new wxInputFTPStream(this, sock);
9a1b2c28 786
730b772b 787 m_lastError = wxPROTO_NOERR;
2e907fab 788 return in_stream;
f4ada568
GL
789}
790
791wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
792{
3f2bcf34 793 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
730b772b
FM
794 {
795 m_lastError = wxPROTO_CONNERR;
2e907fab 796 return NULL;
730b772b 797 }
f4ada568 798
e2454588 799 wxSocketBase *sock = GetPort();
f4ada568 800
2e907fab
VZ
801 wxString tmp_str = wxT("STOR ") + path;
802 if ( !CheckCommand(tmp_str, '1') )
8f901032 803 return NULL;
f4ada568 804
e2454588
VZ
805 sock = AcceptIfActive(sock);
806
a62848fd 807 m_streaming = true;
f4ada568 808
730b772b 809 m_lastError = wxPROTO_NOERR;
2e907fab 810 return new wxOutputFTPStream(this, sock);
f4ada568
GL
811}
812
2e907fab
VZ
813// ----------------------------------------------------------------------------
814// FTP directory listing
815// ----------------------------------------------------------------------------
816
817bool wxFTP::GetList(wxArrayString& files,
818 const wxString& wildcard,
819 bool details)
8e907a13
VZ
820{
821 wxSocketBase *sock = GetPort();
730b772b
FM
822 if (!sock) {
823 m_lastError = wxPROTO_NETERR;
a62848fd 824 return false;
730b772b 825 }
8e907a13 826
2e907fab
VZ
827 // NLST : List of Filenames (including Directory's !)
828 // LIST : depending on BS of FTP-Server
829 // - Unix : result like "ls" command
830 // - Windows : like "dir" command
831 // - others : ?
832 wxString line(details ? _T("LIST") : _T("NLST"));
f9df3f05 833 if ( !wildcard.empty() )
8e907a13 834 {
2e907fab 835 line << _T(' ') << wildcard;
8e907a13
VZ
836 }
837
e2454588 838 if ( !CheckCommand(line, '1') )
8e907a13 839 {
e2454588 840 m_lastError = wxPROTO_PROTERR;
ef5eca7a 841 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
e2454588 842 delete sock;
a62848fd 843 return false;
8e907a13 844 }
e2454588
VZ
845
846 sock = AcceptIfActive(sock);
730b772b
FM
847 if ( !sock ) {
848 m_lastError = wxPROTO_CONNERR;
e2454588 849 return false;
730b772b 850 }
e2454588 851
8e907a13 852 files.Empty();
e2454588 853 while (ReadLine(sock, line) == wxPROTO_NOERR )
8e907a13
VZ
854 {
855 files.Add(line);
856 }
e2454588 857
8e907a13
VZ
858 delete sock;
859
860 // the file list should be terminated by "226 Transfer complete""
730b772b 861 m_lastError = wxPROTO_NOERR;
e2454588 862 return CheckResult('2');
8e907a13
VZ
863}
864
b92fd37c
VZ
865bool wxFTP::FileExists(const wxString& fileName)
866{
3f2bcf34
VZ
867 // This function checks if the file specified in fileName exists in the
868 // current dir. It does so by simply doing an NLST (via GetList).
869 // If this succeeds (and the list is not empty) the file exists.
870
a62848fd 871 bool retval = false;
3f2bcf34
VZ
872 wxArrayString fileList;
873
a62848fd 874 if ( GetList(fileList, fileName, false) )
3f2bcf34
VZ
875 {
876 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
6a17b868 877 // displays this behaviour when queried on a nonexistent file:
3f2bcf34
VZ
878 // NLST this_file_does_not_exist
879 // 150 Opening ASCII data connection for directory listing
880 // (no data transferred)
881 // 226 Transfer complete
b92fd37c
VZ
882 // Here wxFTP::GetList(...) will succeed but it will return an empty
883 // list.
884 retval = !fileList.IsEmpty();
3f2bcf34 885 }
b92fd37c 886
3f2bcf34 887 return retval;
b92fd37c
VZ
888}
889
890// ----------------------------------------------------------------------------
891// FTP GetSize
892// ----------------------------------------------------------------------------
893
894int wxFTP::GetFileSize(const wxString& fileName)
895{
3f2bcf34
VZ
896 // return the filesize of the given file if possible
897 // return -1 otherwise (predominantly if file doesn't exist
898 // in current dir)
899
900 int filesize = -1;
b92fd37c 901
3f2bcf34 902 // Check for existance of file via wxFTP::FileExists(...)
b92fd37c
VZ
903 if ( FileExists(fileName) )
904 {
905 wxString command;
906
907 // First try "SIZE" command using BINARY(IMAGE) transfermode
908 // Especially UNIX ftp-servers distinguish between the different
909 // transfermodes and reports different filesizes accordingly.
910 // The BINARY size is the interesting one: How much memory
911 // will we need to hold this file?
3f2bcf34 912 TransferMode oldTransfermode = m_currentTransfermode;
b92fd37c
VZ
913 SetTransferMode(BINARY);
914 command << _T("SIZE ") << fileName;
915
916 bool ok = CheckCommand(command, '2');
917
918 if ( ok )
919 {
920 // The answer should be one line: "213 <filesize>\n"
921 // 213 is File Status (STD9)
3f2bcf34
VZ
922 // "SIZE" is not described anywhere..? It works on most servers
923 int statuscode;
924 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
b92fd37c 925 &statuscode, &filesize) == 2 )
3f2bcf34
VZ
926 {
927 // We've gotten a good reply.
a62848fd 928 ok = true;
3f2bcf34
VZ
929 }
930 else
931 {
932 // Something bad happened.. A "2yz" reply with no size
933 // Fallback
a62848fd 934 ok = false;
3f2bcf34 935 }
b92fd37c 936 }
3f2bcf34
VZ
937
938 // Set transfermode back to the original. Only the "SIZE"-command
939 // is dependant on transfermode
b92fd37c
VZ
940 if ( oldTransfermode != NONE )
941 {
942 SetTransferMode(oldTransfermode);
943 }
3f2bcf34 944
999836aa
VZ
945 // this is not a direct else clause.. The size command might return an
946 // invalid "2yz" reply
947 if ( !ok )
b92fd37c 948 {
3f2bcf34
VZ
949 // The server didn't understand the "SIZE"-command or it
950 // returned an invalid reply.
951 // We now try to get details for the file with a "LIST"-command
952 // and then parse the output from there..
953 wxArrayString fileList;
a62848fd 954 if ( GetList(fileList, fileName, true) )
3f2bcf34
VZ
955 {
956 if ( !fileList.IsEmpty() )
957 {
b92fd37c
VZ
958 // We _should_ only get one line in return, but just to be
959 // safe we run through the line(s) returned and look for a
960 // substring containing the name we are looking for. We
961 // stop the iteration at the first occurrence of the
962 // filename. The search is not case-sensitive.
a62848fd 963 bool foundIt = false;
b92fd37c
VZ
964
965 size_t i;
b4a980f4 966 for ( i = 0; !foundIt && i < fileList.GetCount(); i++ )
3f2bcf34
VZ
967 {
968 foundIt = fileList[i].Upper().Contains(fileName.Upper());
969 }
b92fd37c 970
3f2bcf34
VZ
971 if ( foundIt )
972 {
b92fd37c
VZ
973 // The index i points to the first occurrence of
974 // fileName in the array Now we have to find out what
975 // format the LIST has returned. There are two
976 // "schools": Unix-like
977 //
978 // '-rw-rw-rw- owner group size month day time filename'
979 //
980 // or Windows-like
981 //
982 // 'date size filename'
983
984 // check if the first character is '-'. This would
985 // indicate Unix-style (this also limits this function
986 // to searching for files, not directories)
987 if ( fileList[i].Mid(0, 1) == _T("-") )
3f2bcf34 988 {
b92fd37c 989
3f2bcf34 990 if ( wxSscanf(fileList[i].c_str(),
1489a2c0 991 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
999836aa 992 &filesize) != 9 )
3f2bcf34
VZ
993 {
994 // Hmm... Invalid response
0576cd9e 995 wxLogDebug(wxT("Invalid LIST response"));
3f2bcf34
VZ
996 }
997 }
998 else // Windows-style response (?)
999 {
1000 if ( wxSscanf(fileList[i].c_str(),
b92fd37c 1001 _T("%*s %*s %i %*s"),
999836aa 1002 &filesize) != 4 )
3f2bcf34
VZ
1003 {
1004 // something bad happened..?
0576cd9e 1005 wxLogDebug(wxT("Invalid or unknown LIST response"));
3f2bcf34
VZ
1006 }
1007 }
1008 }
1009 }
1010 }
b92fd37c
VZ
1011 }
1012 }
1013
3f2bcf34
VZ
1014 // filesize might still be -1 when exiting
1015 return filesize;
b92fd37c
VZ
1016}
1017
a5d46b73
VZ
1018#endif // wxUSE_PROTOCOL_FTP
1019