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