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