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