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