]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
at least partially implemented vswscanf() -- otherwise wx apps compiled against glibc...
[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
14f355c2 22#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
2df7be7f 23 #pragma implementation "ftp.h"
f4ada568 24#endif
ec45f8ee 25
2e907fab
VZ
26// ----------------------------------------------------------------------------
27// headers
28// ----------------------------------------------------------------------------
29
ec45f8ee
UU
30// For compilers that support precompilation, includes "wx.h".
31#include "wx/wxprec.h"
32
19ae5cf0 33#ifdef __BORLANDC__
2df7be7f 34 #pragma hdrstop
19ae5cf0
UU
35#endif
36
a5d46b73 37#if wxUSE_PROTOCOL_FTP
35a4dab7 38
2e907fab
VZ
39#ifndef WX_PRECOMP
40 #include <stdlib.h>
41 #include "wx/string.h"
42 #include "wx/utils.h"
43 #include "wx/log.h"
44 #include "wx/intl.h"
45#endif // WX_PRECOMP
17dff81c 46
f4ada568 47#include "wx/sckaddr.h"
f4ada568
GL
48#include "wx/socket.h"
49#include "wx/url.h"
50#include "wx/sckstrm.h"
51#include "wx/protocol/protocol.h"
52#include "wx/protocol/ftp.h"
53
2e907fab 54#if defined(__WXMAC__)
03e11df5 55 #include "wx/mac/macsock.h"
2e907fab
VZ
56#endif
57
58#ifndef __MWERKS__
59 #include <memory.h>
f4ada568
GL
60#endif
61
2e907fab
VZ
62// ----------------------------------------------------------------------------
63// constants
64// ----------------------------------------------------------------------------
65
8e907a13
VZ
66// the length of FTP status code (3 digits)
67static const size_t LEN_CODE = 3;
68
2e907fab
VZ
69// ----------------------------------------------------------------------------
70// macros
71// ----------------------------------------------------------------------------
f4ada568 72
f4ada568 73IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
a62848fd 74IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), true)
f4ada568 75
2e907fab
VZ
76// ============================================================================
77// implementation
78// ============================================================================
79
80// ----------------------------------------------------------------------------
81// wxFTP constructor and destructor
82// ----------------------------------------------------------------------------
f4ada568
GL
83
84wxFTP::wxFTP()
f4ada568 85{
8e907a13 86 m_lastError = wxPROTO_NOERR;
a62848fd 87 m_streaming = false;
3f2bcf34 88 m_currentTransfermode = NONE;
f4ada568 89
8e907a13
VZ
90 m_user = wxT("anonymous");
91 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
f4ada568 92
8e907a13
VZ
93 SetNotify(0);
94 SetFlags(wxSOCKET_NONE);
e2454588
VZ
95 m_bPassive = true;
96 SetDefaultTimeout(60); // Default is Sixty Seconds
97 m_bEncounteredError = false;
f4ada568
GL
98}
99
100wxFTP::~wxFTP()
101{
7ff26ec2
VZ
102 if ( m_streaming )
103 {
e2454588
VZ
104 // if we are streaming, this will issue
105 // an FTP ABORT command, to tell the server we are aborting
7ff26ec2
VZ
106 (void)Abort();
107 }
108
e2454588 109 // now this issues a "QUIT" command to tell the server we are
8e907a13 110 Close();
f4ada568
GL
111}
112
2e907fab
VZ
113// ----------------------------------------------------------------------------
114// wxFTP connect and login methods
115// ----------------------------------------------------------------------------
116
8a2c6ef8 117bool wxFTP::Connect(wxSockAddress& addr, bool WXUNUSED(wait))
f4ada568 118{
2e907fab
VZ
119 if ( !wxProtocol::Connect(addr) )
120 {
121 m_lastError = wxPROTO_NETERR;
a62848fd 122 return false;
2e907fab 123 }
f4ada568 124
2e907fab
VZ
125 if ( !m_user )
126 {
127 m_lastError = wxPROTO_CONNERR;
a62848fd 128 return false;
2e907fab 129 }
f4ada568 130
2e907fab
VZ
131 // we should have 220 welcome message
132 if ( !CheckResult('2') )
133 {
134 Close();
a62848fd 135 return false;
2e907fab 136 }
f4ada568 137
2e907fab
VZ
138 wxString command;
139 command.Printf(wxT("USER %s"), m_user.c_str());
140 char rc = SendCommand(command);
141 if ( rc == '2' )
142 {
143 // 230 return: user accepted without password
a62848fd 144 return true;
2e907fab 145 }
f4ada568 146
2e907fab
VZ
147 if ( rc != '3' )
148 {
149 Close();
a62848fd 150 return false;
2e907fab 151 }
f4ada568 152
2e907fab
VZ
153 command.Printf(wxT("PASS %s"), m_passwd.c_str());
154 if ( !CheckCommand(command, '2') )
155 {
156 Close();
a62848fd 157 return false;
2e907fab 158 }
f4ada568 159
a62848fd 160 return true;
f4ada568
GL
161}
162
163bool wxFTP::Connect(const wxString& host)
164{
2e907fab
VZ
165 wxIPV4address addr;
166 addr.Hostname(host);
167 addr.Service(wxT("ftp"));
f4ada568 168
2e907fab 169 return Connect(addr);
f4ada568
GL
170}
171
7ff26ec2 172bool wxFTP::Close()
f4ada568 173{
8e907a13
VZ
174 if ( m_streaming )
175 {
7ff26ec2 176 m_lastError = wxPROTO_STREAMING;
a62848fd 177 return false;
8e907a13
VZ
178 }
179
180 if ( IsConnected() )
2e907fab
VZ
181 {
182 if ( !CheckCommand(wxT("QUIT"), '2') )
183 {
184 wxLogDebug(_T("Failed to close connection gracefully."));
185 }
186 }
e8773bdf 187
8e907a13 188 return wxSocketClient::Close();
f4ada568
GL
189}
190
8e907a13
VZ
191// ============================================================================
192// low level methods
193// ============================================================================
194
195// ----------------------------------------------------------------------------
196// Send command to FTP server
197// ----------------------------------------------------------------------------
198
2e907fab 199char wxFTP::SendCommand(const wxString& command)
f4ada568 200{
2e907fab 201 if ( m_streaming )
8e907a13
VZ
202 {
203 m_lastError = wxPROTO_STREAMING;
2e907fab 204 return 0;
8e907a13
VZ
205 }
206
2e907fab 207 wxString tmp_str = command + wxT("\r\n");
8e907a13
VZ
208 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
209 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
210 {
211 m_lastError = wxPROTO_NETERR;
2e907fab 212 return 0;
8e907a13
VZ
213 }
214
b92fd37c
VZ
215#ifdef __WXDEBUG__
216 // don't show the passwords in the logs (even in debug ones)
217 wxString cmd, password;
218 if ( command.Upper().StartsWith(_T("PASS "), &password) )
219 {
220 cmd << _T("PASS ") << wxString(_T('*'), password.length());
221 }
222 else
223 {
224 cmd = command;
225 }
226
227 wxLogTrace(FTP_TRACE_MASK, _T("==> %s"), cmd.c_str());
228#endif // __WXDEBUG__
8e907a13 229
2e907fab 230 return GetResult();
f4ada568
GL
231}
232
8e907a13
VZ
233// ----------------------------------------------------------------------------
234// Recieve servers reply
235// ----------------------------------------------------------------------------
236
2e907fab 237char wxFTP::GetResult()
f4ada568 238{
e2454588
VZ
239 // if we've already had a read or write timeout error, the connection is
240 // probably toast, so don't bother, it just wastes the users time
241 if ( m_bEncounteredError )
242 return 0;
243
8e907a13
VZ
244 wxString code;
245
2e907fab
VZ
246 // m_lastResult will contain the entire server response, possibly on
247 // multiple lines
248 m_lastResult.clear();
249
8e907a13
VZ
250 // we handle multiline replies here according to RFC 959: it says that a
251 // reply may either be on 1 line of the form "xyz ..." or on several lines
252 // in whuch case it looks like
253 // xyz-...
254 // ...
255 // xyz ...
256 // and the intermeidate lines may start with xyz or not
a62848fd
WS
257 bool badReply = false;
258 bool firstLine = true;
259 bool endOfReply = false;
8e907a13
VZ
260 while ( !endOfReply && !badReply )
261 {
2e907fab 262 wxString line;
e2454588 263 m_lastError = ReadLine(this,line);
8e907a13 264 if ( m_lastError )
e2454588
VZ
265 {
266 m_bEncounteredError = true;
2e907fab 267 return 0;
e2454588 268 }
2e907fab
VZ
269
270 if ( !m_lastResult.empty() )
271 {
272 // separate from last line
273 m_lastResult += _T('\n');
274 }
275
276 m_lastResult += line;
8e907a13
VZ
277
278 // unless this is an intermediate line of a multiline reply, it must
279 // contain the code in the beginning and '-' or ' ' following it
2e907fab 280 if ( line.Len() < LEN_CODE + 1 )
8e907a13
VZ
281 {
282 if ( firstLine )
283 {
a62848fd 284 badReply = true;
8e907a13
VZ
285 }
286 else
287 {
b92fd37c 288 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 289 code.c_str(), line.c_str());
8e907a13
VZ
290 }
291 }
292 else // line has at least 4 chars
293 {
294 // this is the char which tells us what we're dealing with
2e907fab 295 wxChar chMarker = line.GetChar(LEN_CODE);
8e907a13
VZ
296
297 if ( firstLine )
298 {
2e907fab 299 code = wxString(line, LEN_CODE);
b92fd37c 300 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 301 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
302
303 switch ( chMarker )
304 {
305 case _T(' '):
a62848fd 306 endOfReply = true;
8e907a13
VZ
307 break;
308
309 case _T('-'):
a62848fd 310 firstLine = false;
8e907a13
VZ
311 break;
312
313 default:
314 // unexpected
a62848fd 315 badReply = true;
8e907a13
VZ
316 }
317 }
318 else // subsequent line of multiline reply
319 {
2e907fab 320 if ( wxStrncmp(line, code, LEN_CODE) == 0 )
8e907a13
VZ
321 {
322 if ( chMarker == _T(' ') )
323 {
a62848fd 324 endOfReply = true;
8e907a13
VZ
325 }
326
b92fd37c 327 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 328 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
329 }
330 else
331 {
332 // just part of reply
b92fd37c 333 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 334 code.c_str(), line.c_str());
8e907a13
VZ
335 }
336 }
337 }
338 }
f4ada568 339
8e907a13
VZ
340 if ( badReply )
341 {
342 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
343 m_lastResult.c_str());
f4ada568 344
8e907a13 345 m_lastError = wxPROTO_PROTERR;
f4ada568 346
2e907fab
VZ
347 return 0;
348 }
349
350 // if we got here we must have a non empty code string
351 return code[0u];
352}
353
354// ----------------------------------------------------------------------------
355// wxFTP simple commands
356// ----------------------------------------------------------------------------
357
358bool wxFTP::SetTransferMode(TransferMode transferMode)
359{
b92fd37c
VZ
360 if ( transferMode == m_currentTransfermode )
361 {
362 // nothing to do
a62848fd 363 return true;
b92fd37c
VZ
364 }
365
2e907fab
VZ
366 wxString mode;
367 switch ( transferMode )
368 {
369 default:
370 wxFAIL_MSG(_T("unknown FTP transfer mode"));
371 // fall through
372
373 case BINARY:
374 mode = _T('I');
375 break;
376
377 case ASCII:
378 mode = _T('A');
379 break;
380 }
381
382 if ( !DoSimpleCommand(_T("TYPE"), mode) )
383 {
86435b1a
OK
384 wxLogError(_("Failed to set FTP transfer mode to %s."), (const wxChar*)
385 (transferMode == ASCII ? _("ASCII") : _("binary")));
2e907fab 386
a62848fd 387 return false;
f4ada568 388 }
8e907a13 389
3f2bcf34
VZ
390 // If we get here the operation has been succesfully completed
391 // Set the status-member
392 m_currentTransfermode = transferMode;
2e907fab 393
a62848fd 394 return true;
2e907fab
VZ
395}
396
397bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
398{
399 wxString fullcmd = command;
400 if ( !arg.empty() )
8e907a13 401 {
2e907fab
VZ
402 fullcmd << _T(' ') << arg;
403 }
404
405 if ( !CheckCommand(fullcmd, '2') )
406 {
407 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
8e907a13 408
a62848fd 409 return false;
8e907a13
VZ
410 }
411
a62848fd 412 return true;
f4ada568
GL
413}
414
f4ada568
GL
415bool wxFTP::ChDir(const wxString& dir)
416{
2e907fab
VZ
417 // some servers might not understand ".." if they use different directory
418 // tree conventions, but they always understand CDUP - should we use it if
419 // dir == ".."? OTOH, do such servers (still) exist?
f4ada568 420
2e907fab 421 return DoSimpleCommand(_T("CWD"), dir);
f4ada568
GL
422}
423
424bool wxFTP::MkDir(const wxString& dir)
425{
2e907fab 426 return DoSimpleCommand(_T("MKD"), dir);
f4ada568
GL
427}
428
429bool wxFTP::RmDir(const wxString& dir)
430{
2e907fab 431 return DoSimpleCommand(_T("RMD"), dir);
f4ada568
GL
432}
433
434wxString wxFTP::Pwd()
435{
8e907a13
VZ
436 wxString path;
437
2e907fab 438 if ( CheckCommand(wxT("PWD"), '2') )
8e907a13 439 {
2e907fab 440 // the result is at least that long if CheckCommand() succeeded
8e907a13
VZ
441 const wxChar *p = m_lastResult.c_str() + LEN_CODE + 1;
442 if ( *p != _T('"') )
443 {
444 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p);
445 }
446 else
447 {
448 for ( p++; *p; p++ )
449 {
450 if ( *p == _T('"') )
451 {
452 // check if the quote is doubled
453 p++;
454 if ( !*p || *p != _T('"') )
455 {
456 // no, this is the end
457 break;
458 }
459 //else: yes, it is: this is an embedded quote in the
460 // filename, treat as normal char
461 }
462
463 path += *p;
464 }
465
466 if ( !*p )
467 {
468 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
469 m_lastResult.c_str() + LEN_CODE + 1);
470 }
471 }
472 }
2e907fab
VZ
473 else
474 {
475 wxLogDebug(_T("FTP PWD command failed."));
476 }
f4ada568 477
8e907a13 478 return path;
f4ada568
GL
479}
480
481bool wxFTP::Rename(const wxString& src, const wxString& dst)
482{
2e907fab
VZ
483 wxString str;
484
485 str = wxT("RNFR ") + src;
486 if ( !CheckCommand(str, '3') )
a62848fd 487 return false;
f4ada568 488
2e907fab 489 str = wxT("RNTO ") + dst;
f4ada568 490
2e907fab 491 return CheckCommand(str, '2');
f4ada568
GL
492}
493
494bool wxFTP::RmFile(const wxString& path)
495{
2e907fab
VZ
496 wxString str;
497 str = wxT("DELE ") + path;
f4ada568 498
2e907fab 499 return CheckCommand(str, '2');
f4ada568
GL
500}
501
2e907fab
VZ
502// ----------------------------------------------------------------------------
503// wxFTP download and upload
504// ----------------------------------------------------------------------------
f4ada568 505
2e907fab
VZ
506class wxInputFTPStream : public wxSocketInputStream
507{
f4ada568 508public:
445783de
VZ
509 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
510 : wxSocketInputStream(*sock)
2e907fab 511 {
445783de 512 m_ftp = ftp;
e2454588 513 // socket timeout automatically set in GetPort function
2e907fab
VZ
514 }
515
2e907fab
VZ
516 virtual ~wxInputFTPStream()
517 {
e2454588 518 delete m_i_socket; // keep at top
b0ad2006 519
e2454588
VZ
520 // when checking the result, the stream will
521 // almost always show an error, even if the file was
522 // properly transfered, thus, lets just grab the result
2e907fab 523
e2454588
VZ
524 // we are looking for "226 transfer completed"
525 char code = m_ftp->GetResult();
526 if ('2' == code)
527 {
528 // it was a good transfer.
529 // we're done!
530 m_ftp->m_streaming = false;
531 return;
2e907fab 532 }
e2454588
VZ
533 // did we timeout?
534 if (0 == code)
2e907fab 535 {
e2454588
VZ
536 // the connection is probably toast. issue an abort, and
537 // then a close. there won't be any more waiting
538 // for this connection
2e907fab 539 m_ftp->Abort();
e2454588
VZ
540 m_ftp->Close();
541 return;
2e907fab 542 }
e2454588
VZ
543 // There was a problem with the transfer and the server
544 // has acknowledged it. If we issue an "ABORT" now, the user
545 // would get the "226" for the abort and think the xfer was
546 // complete, thus, don't do anything here, just return
2e907fab
VZ
547 }
548
549 wxFTP *m_ftp;
22f3361e
VZ
550
551 DECLARE_NO_COPY_CLASS(wxInputFTPStream)
f4ada568
GL
552};
553
2e907fab
VZ
554class wxOutputFTPStream : public wxSocketOutputStream
555{
f4ada568 556public:
2e907fab
VZ
557 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
558 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
559 {
560 }
561
562 virtual ~wxOutputFTPStream(void)
563 {
564 if ( IsOk() )
565 {
566 // close data connection first, this will generate "transfer
567 // completed" reply
568 delete m_o_socket;
569
570 // read this reply
e2454588 571 m_ftp->GetResult(); // save result so user can get to it
2e907fab 572
a62848fd 573 m_ftp->m_streaming = false;
2e907fab
VZ
574 }
575 else
576 {
577 // abort data connection first
578 m_ftp->Abort();
579
580 // and close it after
581 delete m_o_socket;
582 }
583 }
584
585 wxFTP *m_ftp;
22f3361e
VZ
586
587 DECLARE_NO_COPY_CLASS(wxOutputFTPStream)
f4ada568
GL
588};
589
e2454588 590void wxFTP::SetDefaultTimeout(wxUint32 Value)
f4ada568 591{
e2454588
VZ
592 m_uiDefaultTimeout = Value;
593 SetTimeout(Value); // sets it for this socket
594}
2e907fab 595
e2454588
VZ
596
597wxSocketBase *wxFTP::GetPort()
598{
599 /*
600 PASSIVE: Client sends a "PASV" to the server. The server responds with
601 an address and port number which it will be listening on. Then
602 the client connects to the server at the specified address and
603 port.
604
605 ACTIVE: Client sends the server a PORT command which includes an
606 address and port number which the client will be listening on.
607 The server then connects to the client at that address and
608 port.
609 */
610
611 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
612 if ( !socket )
613 {
614 m_bEncounteredError = true;
615 return NULL;
616 }
617
618 // Now set the time for the new socket to the default or user selected
619 // timeout period
620 socket->SetTimeout(m_uiDefaultTimeout);
621
622 return socket;
623}
624
625wxSocketBase *wxFTP::AcceptIfActive(wxSocketBase *sock)
626{
627 if ( m_bPassive )
628 return sock;
629
630 // now wait for a connection from server
631 wxSocketServer *sockSrv = (wxSocketServer *)sock;
632 if ( !sockSrv->WaitForAccept() )
2e907fab 633 {
e2454588
VZ
634 m_lastError = wxPROTO_CONNERR;
635 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
636 delete sock;
637 sock = NULL;
638 }
639 else
640 {
641 sock = sockSrv->Accept(true);
642 delete sockSrv;
643 }
644
645 return sock;
646}
647
648wxString wxFTP::GetPortCmdArgument(wxIPV4address addrLocal,
649 wxIPV4address addrNew)
650{
651 // Just fills in the return value with the local IP
652 // address of the current socket. Also it fill in the
653 // PORT which the client will be listening on
2e907fab 654
e2454588
VZ
655 wxString addrIP = addrLocal.IPAddress();
656 int portNew = addrNew.Service();
657
658 // We need to break the PORT number in bytes
659 addrIP.Replace(_T("."), _T(","));
660 addrIP << _T(',')
661 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
662 << wxString::Format(_T("%d"), portNew & 0xff);
663
664 // Now we have a value like "10,0,0,1,5,23"
665 return addrIP;
666}
667
668wxSocketBase *wxFTP::GetActivePort()
669{
670 // we need an address to listen on
671 wxIPV4address addrNew, addrLocal;
672 GetLocal(addrLocal);
673 addrNew.AnyAddress();
674 addrNew.Service(0); // pick an open port number.
675
676 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
677 if (!sockSrv->Ok())
678 {
679 // We use Ok() here to see if everything is ok
680 m_lastError = wxPROTO_PROTERR;
681 delete sockSrv;
2e907fab
VZ
682 return NULL;
683 }
684
e2454588
VZ
685 //gets the new address, actually it is just the port number
686 sockSrv->GetLocal(addrNew);
687
688 // Now we create the argument of the PORT command, we send in both
689 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
690 // value in addrLocal
691 wxString port = GetPortCmdArgument(addrLocal, addrNew);
692 if ( !DoSimpleCommand(_T("PORT "), port) )
2e907fab
VZ
693 {
694 m_lastError = wxPROTO_PROTERR;
e2454588
VZ
695 delete sockSrv;
696 wxLogError(_("The FTP server doesn't support the PORT command."));
697 return NULL;
698 }
2e907fab 699
e2454588
VZ
700 sockSrv->Notify(false); // Don't send any events
701 return sockSrv;
702}
703
704wxSocketBase *wxFTP::GetPassivePort()
705{
706 if ( !DoSimpleCommand(_T("PASV")) )
707 {
708 wxLogError(_("The FTP server doesn't support passive mode."));
2e907fab
VZ
709 return NULL;
710 }
711
e2454588
VZ
712 const wxChar *addrStart = wxStrchr(m_lastResult, _T('('));
713 const wxChar *addrEnd = addrStart ? wxStrchr(addrStart, _T(')')) : NULL;
2e907fab
VZ
714 if ( !addrEnd )
715 {
716 m_lastError = wxPROTO_PROTERR;
717
718 return NULL;
719 }
720
e2454588
VZ
721 // get the port number and address
722 int a[6];
2e907fab 723 wxString straddr(addrStart + 1, addrEnd);
2e907fab
VZ
724 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
725 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
726
377f6397
SC
727 wxUint32 hostaddr = (wxUint16)a[2] << 24 |
728 (wxUint16)a[3] << 16 |
729 (wxUint16)a[4] << 8 |
730 a[5];
254a2129 731 wxUint16 port = (wxUint16)(a[0] << 8 | a[1]);
2e907fab
VZ
732
733 wxIPV4address addr;
734 addr.Hostname(hostaddr);
735 addr.Service(port);
736
737 wxSocketClient *client = new wxSocketClient();
738 if ( !client->Connect(addr) )
739 {
740 delete client;
741 return NULL;
742 }
743
a62848fd 744 client->Notify(false);
2e907fab
VZ
745
746 return client;
f4ada568
GL
747}
748
8e907a13 749bool wxFTP::Abort()
f4ada568 750{
8e907a13 751 if ( !m_streaming )
a62848fd 752 return true;
2e907fab 753
a62848fd 754 m_streaming = false;
2e907fab 755 if ( !CheckCommand(wxT("ABOR"), '4') )
a62848fd 756 return false;
8e907a13 757
2e907fab 758 return CheckResult('2');
f4ada568
GL
759}
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
2e907fab
VZ
774 wxString tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
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"));
a62848fd 827 if ( !wildcard.IsEmpty() )
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)
868 // displays this behaviour when queried on a non-existing file:
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;
3f2bcf34
VZ
957 for ( i = 0; !foundIt && i < fileList.Count(); i++ )
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