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