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