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