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