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