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