• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KIOSlave

  • kioslave
  • ftp
ftp.cpp
Go to the documentation of this file.
1// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; c-file-style: "stroustrup" -*-
2/* This file is part of the KDE libraries
3 Copyright (C) 2000-2006 David Faure <faure@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21/*
22 Recommended reading explaining FTP details and quirks:
23 http://cr.yp.to/ftp.html (by D.J. Bernstein)
24
25 RFC:
26 RFC 959 "File Transfer Protocol (FTP)"
27 RFC 1635 "How to Use Anonymous FTP"
28 RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV)
29*/
30
31
32#define KIO_FTP_PRIVATE_INCLUDE
33#include "ftp.h"
34
35#ifdef HAVE_SYS_TIME_H
36#include <sys/time.h>
37#endif
38
39#if TIME_WITH_SYS_TIME
40#include <ctime>
41#endif
42
43#include <cctype>
44#include <cerrno>
45#include <cstdlib>
46#include <cstring>
47
48#include <QtCore/QCoreApplication>
49#include <QtCore/QDir>
50#include <QtNetwork/QHostAddress>
51#include <QtNetwork/QTcpSocket>
52#include <QtNetwork/QTcpServer>
53#include <QtNetwork/QAuthenticator>
54
55#include <kdebug.h>
56#include <kglobal.h>
57#include <klocale.h>
58#include <kcomponentdata.h>
59#include <kmimetype.h>
60#include <kio/ioslave_defaults.h>
61#include <kio/slaveconfig.h>
62#include <kremoteencoding.h>
63#include <ksocketfactory.h>
64#include <kde_file.h>
65#include <kconfiggroup.h>
66
67#ifdef HAVE_STRTOLL
68 #define charToLongLong(a) strtoll(a, 0, 10)
69#else
70 #define charToLongLong(a) strtol(a, 0, 10)
71#endif
72
73#define FTP_LOGIN "anonymous"
74#define FTP_PASSWD "anonymous@"
75
76//#undef kDebug
77#define ENABLE_CAN_RESUME
78
79static QString ftpCleanPath(const QString& path)
80{
81 if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) ||
82 path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) ||
83 path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) {
84 return path.left((path.length() - qstrlen(";type=X")));
85 }
86
87 return path;
88}
89
90static char ftpModeFromPath(const QString& path, char defaultMode = '\0')
91{
92 const int index = path.lastIndexOf(QLatin1String(";type="));
93
94 if (index > -1 && (index+6) < path.size()) {
95 const QChar mode = path.at(index+6);
96 // kio_ftp supports only A (ASCII) and I(BINARY) modes.
97 if (mode == QLatin1Char('A') || mode == QLatin1Char('a') ||
98 mode == QLatin1Char('I') || mode == QLatin1Char('i')) {
99 return mode.toUpper().toLatin1();
100 }
101 }
102
103 return defaultMode;
104}
105
106static bool supportedProxyScheme(const QString& scheme)
107{
108 return (scheme == QLatin1String("ftp") || scheme == QLatin1String("socks"));
109}
110
111static bool isSocksProxy()
112{
113 return (QNetworkProxy::applicationProxy().type() == QNetworkProxy::Socks5Proxy);
114}
115
116
117// JPF: somebody should find a better solution for this or move this to KIO
118// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
119namespace KIO {
120 enum buffersizes
121 {
125 maximumIpcSize = 32 * 1024,
130 initialIpcSize = 2 * 1024,
134 minimumMimeSize = 1024
135 };
136
137 // JPF: this helper was derived from write_all in file.cc (FileProtocol).
138 static // JPF: in ftp.cc we make it static
146 int WriteToFile(int fd, const char *buf, size_t len)
147 {
148 while (len > 0)
149 { // JPF: shouldn't there be a KDE_write?
150 ssize_t written = write(fd, buf, len);
151 if (written >= 0)
152 { buf += written;
153 len -= written;
154 continue;
155 }
156 switch(errno)
157 { case EINTR: continue;
158 case EPIPE: return ERR_CONNECTION_BROKEN;
159 case ENOSPC: return ERR_DISK_FULL;
160 default: return ERR_COULD_NOT_WRITE;
161 }
162 }
163 return 0;
164 }
165}
166
167KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;
168
169using namespace KIO;
170
171extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
172{
173 QCoreApplication app(argc, argv);
174 KComponentData componentData( "kio_ftp", "kdelibs4" );
175 ( void ) KGlobal::locale();
176
177 kDebug(7102) << "Starting " << getpid();
178
179 if (argc != 4)
180 {
181 fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n");
182 exit(-1);
183 }
184
185 Ftp slave(argv[2], argv[3]);
186 slave.dispatchLoop();
187
188 kDebug(7102) << "Done";
189 return 0;
190}
191
192//===============================================================================
193// Ftp
194//===============================================================================
195
196Ftp::Ftp( const QByteArray &pool, const QByteArray &app )
197 : SlaveBase( "ftp", pool, app )
198{
199 // init the socket data
200 m_data = m_control = NULL;
201 m_server = NULL;
202 ftpCloseControlConnection();
203
204 // init other members
205 m_port = 0;
206 m_socketProxyAuth = 0;
207}
208
209
210Ftp::~Ftp()
211{
212 kDebug(7102);
213 closeConnection();
214}
215
219void Ftp::ftpCloseDataConnection()
220{
221 delete m_data;
222 m_data = NULL;
223 delete m_server;
224 m_server = NULL;
225}
226
231void Ftp::ftpCloseControlConnection()
232{
233 m_extControl = 0;
234 delete m_control;
235 m_control = NULL;
236 m_cDataMode = 0;
237 m_bLoggedOn = false; // logon needs control connction
238 m_bTextMode = false;
239 m_bBusy = false;
240}
241
246const char* Ftp::ftpResponse(int iOffset)
247{
248 Q_ASSERT(m_control != NULL); // must have control connection socket
249 const char *pTxt = m_lastControlLine.data();
250
251 // read the next line ...
252 if(iOffset < 0)
253 {
254 int iMore = 0;
255 m_iRespCode = 0;
256
257 if (!pTxt) return 0; // avoid using a NULL when calling atoi.
258
259 // If the server sends a multiline response starting with
260 // "nnn-text" we loop here until a final "nnn text" line is
261 // reached. Only data from the final line will be stored.
262 do {
263 while (!m_control->canReadLine() && m_control->waitForReadyRead((readTimeout() * 1000))) {}
264 m_lastControlLine = m_control->readLine();
265 pTxt = m_lastControlLine.data();
266 int iCode = atoi(pTxt);
267 if (iMore == 0) {
268 // first line
269 kDebug(7102) << " > " << pTxt;
270 if(iCode >= 100) {
271 m_iRespCode = iCode;
272 if (pTxt[3] == '-') {
273 // marker for a multiple line response
274 iMore = iCode;
275 }
276 } else {
277 kWarning(7102) << "Cannot parse valid code from line" << pTxt;
278 }
279 } else {
280 // multi-line
281 kDebug(7102) << " > " << pTxt;
282 if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') {
283 iMore = 0;
284 }
285 }
286 } while(iMore != 0);
287 kDebug(7102) << "resp> " << pTxt;
288
289 m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0;
290 }
291
292 // return text with offset ...
293 while(iOffset-- > 0 && pTxt[0])
294 pTxt++;
295 return pTxt;
296}
297
298
299void Ftp::closeConnection()
300{
301 if(m_control != NULL || m_data != NULL)
302 kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy;
303
304 if(m_bBusy) // ftpCloseCommand not called
305 {
306 kWarning(7102) << "Abandoned data stream";
307 ftpCloseDataConnection();
308 }
309
310 if(m_bLoggedOn) // send quit
311 {
312 if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) )
313 kWarning(7102) << "QUIT returned error: " << m_iRespCode;
314 }
315
316 // close the data and control connections ...
317 ftpCloseDataConnection();
318 ftpCloseControlConnection();
319}
320
321void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user,
322 const QString& _pass )
323{
324 kDebug(7102) << _host << "port=" << _port << "user=" << _user;
325
326 m_proxyURL.clear();
327 m_proxyUrls = config()->readEntry("ProxyUrls", QStringList());
328 kDebug(7102) << "proxy urls:" << m_proxyUrls;
329
330 if ( m_host != _host || m_port != _port ||
331 m_user != _user || m_pass != _pass )
332 closeConnection();
333
334 m_host = _host;
335 m_port = _port;
336 m_user = _user;
337 m_pass = _pass;
338}
339
340void Ftp::openConnection()
341{
342 ftpOpenConnection(loginExplicit);
343}
344
345bool Ftp::ftpOpenConnection (LoginMode loginMode)
346{
347 // check for implicit login if we are already logged on ...
348 if(loginMode == loginImplicit && m_bLoggedOn)
349 {
350 Q_ASSERT(m_control != NULL); // must have control connection socket
351 return true;
352 }
353
354 kDebug(7102) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]";
355
356 infoMessage( i18n("Opening connection to host %1", m_host) );
357
358 if ( m_host.isEmpty() )
359 {
360 error( ERR_UNKNOWN_HOST, QString() );
361 return false;
362 }
363
364 Q_ASSERT( !m_bLoggedOn );
365
366 m_initialPath.clear();
367 m_currentPath.clear();
368
369 if (!ftpOpenControlConnection() )
370 return false; // error emitted by ftpOpenControlConnection
371 infoMessage( i18n("Connected to host %1", m_host) );
372
373 bool userNameChanged = false;
374 if(loginMode != loginDefered)
375 {
376 m_bLoggedOn = ftpLogin(&userNameChanged);
377 if( !m_bLoggedOn )
378 return false; // error emitted by ftpLogin
379 }
380
381 m_bTextMode = config()->readEntry("textmode", false);
382 connected();
383
384 // Redirected due to credential change...
385 if (userNameChanged && m_bLoggedOn)
386 {
387 KUrl realURL;
388 realURL.setProtocol( "ftp" );
389 if (m_user != FTP_LOGIN)
390 realURL.setUser( m_user );
391 if (m_pass != FTP_PASSWD)
392 realURL.setPass( m_pass );
393 realURL.setHost( m_host );
394 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
395 realURL.setPort( m_port );
396 if ( m_initialPath.isEmpty() )
397 m_initialPath = '/';
398 realURL.setPath( m_initialPath );
399 kDebug(7102) << "User name changed! Redirecting to" << realURL.prettyUrl();
400 redirection( realURL );
401 finished();
402 return false;
403 }
404
405 return true;
406}
407
408
414bool Ftp::ftpOpenControlConnection()
415{
416 if (m_proxyUrls.isEmpty())
417 return ftpOpenControlConnection(m_host, m_port);
418
419 int errorCode = 0;
420 QString errorMessage;
421
422 Q_FOREACH (const QString& proxyUrl, m_proxyUrls) {
423 const KUrl url (proxyUrl);
424 const QString scheme (url.protocol());
425
426 if (!supportedProxyScheme(scheme)) {
427 // TODO: Need a new error code to indicate unsupported URL scheme.
428 errorCode = ERR_COULD_NOT_CONNECT;
429 errorMessage = url.url();
430 continue;
431 }
432
433 if (scheme == QLatin1String("socks")) {
434 kDebug(7102) << "Connecting to SOCKS proxy @" << url;
435 const int proxyPort = url.port();
436 QNetworkProxy proxy (QNetworkProxy::Socks5Proxy, url.host(), (proxyPort == -1 ? 0 : proxyPort));
437 QNetworkProxy::setApplicationProxy(proxy);
438 if (ftpOpenControlConnection(m_host, m_port)) {
439 return true;
440 }
441 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
442 } else {
443 if (ftpOpenControlConnection(url.host(), url.port())) {
444 m_proxyURL = url;
445 return true;
446 }
447 }
448 }
449
450 if (errorCode) {
451 error(errorCode, errorMessage);
452 }
453
454 return false;
455}
456
457bool Ftp::ftpOpenControlConnection( const QString &host, int port )
458{
459 // implicitly close, then try to open a new connection ...
460 closeConnection();
461 QString sErrorMsg;
462
463 // now connect to the server and read the login message ...
464 if (port == 0)
465 port = 21; // default FTP port
466 m_control = KSocketFactory::synchronousConnectToHost(QLatin1String("ftp"), host, port, connectTimeout() * 1000);
467 connect(m_control, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
468 this, SLOT(proxyAuthentication(QNetworkProxy,QAuthenticator*)));
469 int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT;
470
471 // on connect success try to read the server message...
472 if(iErrorCode == 0)
473 {
474 const char* psz = ftpResponse(-1);
475 if(m_iRespType != 2)
476 { // login not successful, do we have an message text?
477 if(psz[0])
478 sErrorMsg = i18n("%1.\n\nReason: %2", host, psz);
479 iErrorCode = ERR_COULD_NOT_CONNECT;
480 }
481 }
482 else
483 {
484 if (m_control->error() == QAbstractSocket::HostNotFoundError)
485 iErrorCode = ERR_UNKNOWN_HOST;
486
487 sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString());
488 }
489
490 // if there was a problem - report it ...
491 if(iErrorCode == 0) // OK, return success
492 return true;
493 closeConnection(); // clean-up on error
494 error(iErrorCode, sErrorMsg);
495 return false;
496}
497
505bool Ftp::ftpLogin(bool* userChanged)
506{
507 infoMessage( i18n("Sending login information") );
508
509 Q_ASSERT( !m_bLoggedOn );
510
511 QString user (m_user);
512 QString pass (m_pass);
513
514 if ( config()->readEntry("EnableAutoLogin", false) )
515 {
516 QString au = config()->readEntry("autoLoginUser");
517 if ( !au.isEmpty() )
518 {
519 user = au;
520 pass = config()->readEntry("autoLoginPass");
521 }
522 }
523
524 AuthInfo info;
525 info.url.setProtocol( "ftp" );
526 info.url.setHost( m_host );
527 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
528 info.url.setPort( m_port );
529 if (!user.isEmpty())
530 info.url.setUser(user);
531
532 // Check for cached authentication first and fallback to
533 // anonymous login when no stored credentials are found.
534 if (!config()->readEntry("TryAnonymousLoginFirst", false) &&
535 pass.isEmpty() && checkCachedAuthentication(info))
536 {
537 user = info.username;
538 pass = info.password;
539 }
540
541 // Try anonymous login if both username/password
542 // information is blank.
543 if (user.isEmpty() && pass.isEmpty())
544 {
545 user = FTP_LOGIN;
546 pass = FTP_PASSWD;
547 }
548
549 QByteArray tempbuf;
550 QString lastServerResponse;
551 int failedAuth = 0;
552 bool promptForRetry = false;
553
554 // Give the user the option to login anonymously...
555 info.setExtraField(QLatin1String("anonymous"), false);
556
557 do
558 {
559 // Check the cache and/or prompt user for password if 1st
560 // login attempt failed OR the user supplied a login name,
561 // but no password.
562 if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) )
563 {
564 QString errorMsg;
565 kDebug(7102) << "Prompting user for login info...";
566
567 // Ask user if we should retry after when login fails!
568 if( failedAuth > 0 && promptForRetry)
569 {
570 errorMsg = i18n("Message sent:\nLogin using username=%1 and "
571 "password=[hidden]\n\nServer replied:\n%2\n\n"
572 , user, lastServerResponse);
573 }
574
575 if ( user != FTP_LOGIN )
576 info.username = user;
577
578 info.prompt = i18n("You need to supply a username and a password "
579 "to access this site.");
580 info.commentLabel = i18n( "Site:" );
581 info.comment = i18n("<b>%1</b>", m_host );
582 info.keepPassword = true; // Prompt the user for persistence as well.
583 info.setModified(false); // Default the modified flag since we reuse authinfo.
584
585 bool disablePassDlg = config()->readEntry( "DisablePassDlg", false );
586 if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) )
587 {
588 error( ERR_USER_CANCELED, m_host );
589 return false;
590 }
591 else
592 {
593 // User can decide go anonymous using checkbox
594 if( info.getExtraField( "anonymous" ).toBool() )
595 {
596 user = FTP_LOGIN;
597 pass = FTP_PASSWD;
598 }
599 else
600 {
601 user = info.username;
602 pass = info.password;
603 }
604 promptForRetry = true;
605 }
606 }
607
608 tempbuf = "USER ";
609 tempbuf += user.toLatin1();
610 if ( m_proxyURL.isValid() )
611 {
612 tempbuf += '@';
613 tempbuf += m_host.toLatin1();
614 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
615 {
616 tempbuf += ':';
617 tempbuf += QString::number(m_port).toLatin1();
618 }
619 }
620
621 kDebug(7102) << "Sending Login name: " << tempbuf;
622
623 bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
624 bool needPass = (m_iRespCode == 331);
625 // Prompt user for login info if we do not
626 // get back a "230" or "331".
627 if ( !loggedIn && !needPass )
628 {
629 lastServerResponse = ftpResponse(0);
630 kDebug(7102) << "Login failed: " << lastServerResponse;
631 ++failedAuth;
632 continue; // Well we failed, prompt the user please!!
633 }
634
635 if( needPass )
636 {
637 tempbuf = "PASS ";
638 tempbuf += pass.toLatin1();
639 kDebug(7102) << "Sending Login password: " << "[protected]";
640 loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
641 }
642
643 if ( loggedIn )
644 {
645 // Make sure the user name changed flag is properly set.
646 if (userChanged)
647 *userChanged = (!m_user.isEmpty() && (m_user != user));
648
649 // Do not cache the default login!!
650 if( user != FTP_LOGIN && pass != FTP_PASSWD )
651 {
652 // Update the username in case it was changed during login.
653 if (!m_user.isEmpty()) {
654 info.url.setUser (user);
655 m_user = user;
656 }
657
658 // Cache the password if the user requested it.
659 if (info.keepPassword) {
660 cacheAuthentication(info);
661 }
662 }
663 failedAuth = -1;
664 }
665 else
666 {
667 // some servers don't let you login anymore
668 // if you fail login once, so restart the connection here
669 lastServerResponse = ftpResponse(0);
670 if (!ftpOpenControlConnection())
671 {
672 return false;
673 }
674 }
675 } while( ++failedAuth );
676
677
678 kDebug(7102) << "Login OK";
679 infoMessage( i18n("Login OK") );
680
681 // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix:
682 // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint
683 if( ftpSendCmd("SYST") && (m_iRespType == 2) )
684 {
685 if( !qstrncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version
686 {
687 ftpSendCmd( "site dirstyle" );
688 // Check if it was already in Unix style
689 // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk>
690 if( !qstrncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 ))
691 //It was in Unix style already!
692 ftpSendCmd( "site dirstyle" );
693 // windows won't support chmod before KDE konquers their desktop...
694 m_extControl |= chmodUnknown;
695 }
696 }
697 else
698 kWarning(7102) << "SYST failed";
699
700 if ( config()->readEntry ("EnableAutoLoginMacro", false) )
701 ftpAutoLoginMacro ();
702
703 // Get the current working directory
704 kDebug(7102) << "Searching for pwd";
705 if( !ftpSendCmd("PWD") || (m_iRespType != 2) )
706 {
707 kDebug(7102) << "Couldn't issue pwd command";
708 error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) ); // or anything better ?
709 return false;
710 }
711
712 QString sTmp = remoteEncoding()->decode( ftpResponse(3) );
713 int iBeg = sTmp.indexOf('"');
714 int iEnd = sTmp.lastIndexOf('"');
715 if(iBeg > 0 && iBeg < iEnd)
716 {
717 m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1);
718 if(m_initialPath[0] != '/') m_initialPath.prepend('/');
719 kDebug(7102) << "Initial path set to: " << m_initialPath;
720 m_currentPath = m_initialPath;
721 }
722 return true;
723}
724
725void Ftp::ftpAutoLoginMacro ()
726{
727 QString macro = metaData( "autoLoginMacro" );
728
729 if ( macro.isEmpty() )
730 return;
731
732 const QStringList list = macro.split('\n',QString::SkipEmptyParts);
733
734 for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
735 {
736 if ( (*it).startsWith(QLatin1String("init")) )
737 {
738 const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
739 it = list2.begin();
740 ++it; // ignore the macro name
741
742 for( ; it != list2.end() ; ++it )
743 {
744 // TODO: Add support for arbitrary commands
745 // besides simply changing directory!!
746 if ( (*it).startsWith( QLatin1String("cwd") ) )
747 (void)ftpFolder( (*it).mid(4), false );
748 }
749
750 break;
751 }
752 }
753}
754
755
765bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries )
766{
767 Q_ASSERT(m_control != NULL); // must have control connection socket
768
769 if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1)
770 {
771 kWarning(7102) << "Invalid command received (contains CR or LF):"
772 << cmd.data();
773 error( ERR_UNSUPPORTED_ACTION, m_host );
774 return false;
775 }
776
777 // Don't print out the password...
778 bool isPassCmd = (cmd.left(4).toLower() == "pass");
779 if ( !isPassCmd )
780 kDebug(7102) << "send> " << cmd.data();
781 else
782 kDebug(7102) << "send> pass [protected]";
783
784 // Send the message...
785 QByteArray buf = cmd;
786 buf += "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html
787 int num = m_control->write(buf);
788 while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {}
789
790 // If we were able to successfully send the command, then we will
791 // attempt to read the response. Otherwise, take action to re-attempt
792 // the login based on the maximum number of retries specified...
793 if( num > 0 )
794 ftpResponse(-1);
795 else
796 {
797 m_iRespType = m_iRespCode = 0;
798 }
799
800 // If respCh is NULL or the response is 421 (Timed-out), we try to re-send
801 // the command based on the value of maxretries.
802 if( (m_iRespType <= 0) || (m_iRespCode == 421) )
803 {
804 // We have not yet logged on...
805 if (!m_bLoggedOn)
806 {
807 // The command was sent from the ftpLogin function, i.e. we are actually
808 // attempting to login in. NOTE: If we already sent the username, we
809 // return false and let the user decide whether (s)he wants to start from
810 // the beginning...
811 if (maxretries > 0 && !isPassCmd)
812 {
813 closeConnection ();
814 if( ftpOpenConnection(loginDefered) )
815 ftpSendCmd ( cmd, maxretries - 1 );
816 }
817
818 return false;
819 }
820 else
821 {
822 if ( maxretries < 1 )
823 return false;
824 else
825 {
826 kDebug(7102) << "Was not able to communicate with " << m_host
827 << "Attempting to re-establish connection.";
828
829 closeConnection(); // Close the old connection...
830 openConnection(); // Attempt to re-establish a new connection...
831
832 if (!m_bLoggedOn)
833 {
834 if (m_control != NULL) // if openConnection succeeded ...
835 {
836 kDebug(7102) << "Login failure, aborting";
837 error (ERR_COULD_NOT_LOGIN, m_host);
838 closeConnection ();
839 }
840 return false;
841 }
842
843 kDebug(7102) << "Logged back in, re-issuing command";
844
845 // If we were able to login, resend the command...
846 if (maxretries)
847 maxretries--;
848
849 return ftpSendCmd( cmd, maxretries );
850 }
851 }
852 }
853
854 return true;
855}
856
857/*
858 * ftpOpenPASVDataConnection - set up data connection, using PASV mode
859 *
860 * return 0 if successful, ERR_INTERNAL otherwise
861 * doesn't set error message, since non-pasv mode will always be tried if
862 * this one fails
863 */
864int Ftp::ftpOpenPASVDataConnection()
865{
866 Q_ASSERT(m_control != NULL); // must have control connection socket
867 Q_ASSERT(m_data == NULL); // ... but no data connection
868
869 // Check that we can do PASV
870 QHostAddress address = m_control->peerAddress();
871 if (address.protocol() != QAbstractSocket::IPv4Protocol && !isSocksProxy())
872 return ERR_INTERNAL; // no PASV for non-PF_INET connections
873
874 if (m_extControl & pasvUnknown)
875 return ERR_INTERNAL; // already tried and got "unknown command"
876
877 m_bPasv = true;
878
879 /* Let's PASsiVe*/
880 if( !ftpSendCmd("PASV") || (m_iRespType != 2) )
881 {
882 kDebug(7102) << "PASV attempt failed";
883 // unknown command?
884 if( m_iRespType == 5 )
885 {
886 kDebug(7102) << "disabling use of PASV";
887 m_extControl |= pasvUnknown;
888 }
889 return ERR_INTERNAL;
890 }
891
892 // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)'
893 // but anonftpd gives '227 =160,39,200,55,6,245'
894 int i[6];
895 const char *start = strchr(ftpResponse(3), '(');
896 if ( !start )
897 start = strchr(ftpResponse(3), '=');
898 if ( !start ||
899 ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
900 sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
901 {
902 kError(7102) << "parsing IP and port numbers failed. String parsed: " << start;
903 return ERR_INTERNAL;
904 }
905
906 // we ignore the host part on purpose for two reasons
907 // a) it might be wrong anyway
908 // b) it would make us being suceptible to a port scanning attack
909
910 // now connect the data socket ...
911 quint16 port = i[4] << 8 | i[5];
912 const QString host = (isSocksProxy() ? m_host : address.toString());
913 m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, port, connectTimeout() * 1000);
914
915 return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL;
916}
917
918/*
919 * ftpOpenEPSVDataConnection - opens a data connection via EPSV
920 */
921int Ftp::ftpOpenEPSVDataConnection()
922{
923 Q_ASSERT(m_control != NULL); // must have control connection socket
924 Q_ASSERT(m_data == NULL); // ... but no data connection
925
926 QHostAddress address = m_control->peerAddress();
927 int portnum;
928
929 if (m_extControl & epsvUnknown)
930 return ERR_INTERNAL;
931
932 m_bPasv = true;
933 if( !ftpSendCmd("EPSV") || (m_iRespType != 2) )
934 {
935 // unknown command?
936 if( m_iRespType == 5 )
937 {
938 kDebug(7102) << "disabling use of EPSV";
939 m_extControl |= epsvUnknown;
940 }
941 return ERR_INTERNAL;
942 }
943
944 const char *start = strchr(ftpResponse(3), '|');
945 if ( !start || sscanf(start, "|||%d|", &portnum) != 1)
946 return ERR_INTERNAL;
947
948 const QString host = (isSocksProxy() ? m_host : address.toString());
949 m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, portnum, connectTimeout() * 1000);
950 return m_data->isOpen() ? 0 : ERR_INTERNAL;
951}
952
953/*
954 * ftpOpenDataConnection - set up data connection
955 *
956 * The routine calls several ftpOpenXxxxConnection() helpers to find
957 * the best connection mode. If a helper cannot connect if returns
958 * ERR_INTERNAL - so this is not really an error! All other error
959 * codes are treated as fatal, e.g. they are passed back to the caller
960 * who is responsible for calling error(). ftpOpenPortDataConnection
961 * can be called as last try and it does never return ERR_INTERNAL.
962 *
963 * @return 0 if successful, err code otherwise
964 */
965int Ftp::ftpOpenDataConnection()
966{
967 // make sure that we are logged on and have no data connection...
968 Q_ASSERT( m_bLoggedOn );
969 ftpCloseDataConnection();
970
971 int iErrCode = 0;
972 int iErrCodePASV = 0; // Remember error code from PASV
973
974 // First try passive (EPSV & PASV) modes
975 if( !config()->readEntry("DisablePassiveMode", false) )
976 {
977 iErrCode = ftpOpenPASVDataConnection();
978 if(iErrCode == 0)
979 return 0; // success
980 iErrCodePASV = iErrCode;
981 ftpCloseDataConnection();
982
983 if( !config()->readEntry("DisableEPSV", false) )
984 {
985 iErrCode = ftpOpenEPSVDataConnection();
986 if(iErrCode == 0)
987 return 0; // success
988 ftpCloseDataConnection();
989 }
990
991 // if we sent EPSV ALL already and it was accepted, then we can't
992 // use active connections any more
993 if (m_extControl & epsvAllSent)
994 return iErrCodePASV;
995 }
996
997 // fall back to port mode
998 iErrCode = ftpOpenPortDataConnection();
999 if(iErrCode == 0)
1000 return 0; // success
1001
1002 ftpCloseDataConnection();
1003 // prefer to return the error code from PASV if any, since that's what should have worked in the first place
1004 return iErrCodePASV ? iErrCodePASV : iErrCode;
1005}
1006
1007/*
1008 * ftpOpenPortDataConnection - set up data connection
1009 *
1010 * @return 0 if successful, err code otherwise (but never ERR_INTERNAL
1011 * because this is the last connection mode that is tried)
1012 */
1013int Ftp::ftpOpenPortDataConnection()
1014{
1015 Q_ASSERT(m_control != NULL); // must have control connection socket
1016 Q_ASSERT(m_data == NULL); // ... but no data connection
1017
1018 m_bPasv = false;
1019 if (m_extControl & eprtUnknown)
1020 return ERR_INTERNAL;
1021
1022 if (!m_server)
1023 m_server = KSocketFactory::listen("ftp-data");
1024
1025 if (!m_server->isListening()) {
1026 delete m_server;
1027 m_server = NULL;
1028 return ERR_COULD_NOT_LISTEN;
1029 }
1030
1031 m_server->setMaxPendingConnections(1);
1032
1033 QString command;
1034 QHostAddress localAddress = m_control->localAddress();
1035 if (localAddress.protocol() == QAbstractSocket::IPv4Protocol)
1036 {
1037 struct
1038 {
1039 quint32 ip4;
1040 quint16 port;
1041 } data;
1042 data.ip4 = localAddress.toIPv4Address();
1043 data.port = m_server->serverPort();
1044
1045 unsigned char *pData = reinterpret_cast<unsigned char*>(&data);
1046 command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]);
1047 }
1048 else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol)
1049 {
1050 command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort());
1051 }
1052
1053 if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) )
1054 {
1055 return 0;
1056 }
1057
1058 delete m_server;
1059 m_server = NULL;
1060 return ERR_INTERNAL;
1061}
1062
1063bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
1064 int errorcode, KIO::fileoffset_t _offset )
1065{
1066 int errCode = 0;
1067 if( !ftpDataMode(ftpModeFromPath(_path, _mode)) )
1068 errCode = ERR_COULD_NOT_CONNECT;
1069 else
1070 errCode = ftpOpenDataConnection();
1071
1072 if(errCode != 0)
1073 {
1074 error(errCode, m_host);
1075 return false;
1076 }
1077
1078 if ( _offset > 0 ) {
1079 // send rest command if offset > 0, this applies to retr and stor commands
1080 char buf[100];
1081 sprintf(buf, "rest %lld", _offset);
1082 if ( !ftpSendCmd( buf ) )
1083 return false;
1084 if( m_iRespType != 3 )
1085 {
1086 error( ERR_CANNOT_RESUME, _path ); // should never happen
1087 return false;
1088 }
1089 }
1090
1091 QByteArray tmp = _command;
1092 QString errormessage;
1093
1094 if ( !_path.isEmpty() ) {
1095 tmp += ' ';
1096 tmp += remoteEncoding()->encode(ftpCleanPath(_path));
1097 }
1098
1099 if( !ftpSendCmd( tmp ) || (m_iRespType != 1) )
1100 {
1101 if( _offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4) )
1102 errorcode = ERR_CANNOT_RESUME;
1103 // The error here depends on the command
1104 errormessage = _path;
1105 }
1106
1107 else
1108 {
1109 // Only now we know for sure that we can resume
1110 if ( _offset > 0 && qstrcmp(_command, "retr") == 0 )
1111 canResume();
1112
1113 if(m_server && !m_data) {
1114 kDebug(7102) << "waiting for connection from remote.";
1115 m_server->waitForNewConnection(connectTimeout() * 1000);
1116 m_data = m_server->nextPendingConnection();
1117 }
1118
1119 if(m_data) {
1120 kDebug(7102) << "connected with remote.";
1121 m_bBusy = true; // cleared in ftpCloseCommand
1122 return true;
1123 }
1124
1125 kDebug(7102) << "no connection received from remote.";
1126 errorcode=ERR_COULD_NOT_ACCEPT;
1127 errormessage=m_host;
1128 return false;
1129 }
1130
1131 error(errorcode, errormessage);
1132 return false;
1133}
1134
1135
1136bool Ftp::ftpCloseCommand()
1137{
1138 // first close data sockets (if opened), then read response that
1139 // we got for whatever was used in ftpOpenCommand ( should be 226 )
1140 ftpCloseDataConnection();
1141
1142 if(!m_bBusy)
1143 return true;
1144
1145 kDebug(7102) << "ftpCloseCommand: reading command result";
1146 m_bBusy = false;
1147
1148 if(!ftpResponse(-1) || (m_iRespType != 2) )
1149 {
1150 kDebug(7102) << "ftpCloseCommand: no transfer complete message";
1151 return false;
1152 }
1153 return true;
1154}
1155
1156void Ftp::mkdir( const KUrl & url, int permissions )
1157{
1158 if( !ftpOpenConnection(loginImplicit) )
1159 return;
1160
1161 const QByteArray encodedPath (remoteEncoding()->encode(url));
1162 const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size());
1163
1164 if( !ftpSendCmd( (QByteArray ("mkd ") + encodedPath) ) || (m_iRespType != 2) )
1165 {
1166 QString currentPath( m_currentPath );
1167
1168 // Check whether or not mkdir failed because
1169 // the directory already exists...
1170 if( ftpFolder( path, false ) )
1171 {
1172 error( ERR_DIR_ALREADY_EXIST, path );
1173 // Change the directory back to what it was...
1174 (void) ftpFolder( currentPath, false );
1175 return;
1176 }
1177
1178 error( ERR_COULD_NOT_MKDIR, path );
1179 return;
1180 }
1181
1182 if ( permissions != -1 )
1183 {
1184 // chmod the dir we just created, ignoring errors.
1185 (void) ftpChmod( path, permissions );
1186 }
1187
1188 finished();
1189}
1190
1191void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags )
1192{
1193 if( !ftpOpenConnection(loginImplicit) )
1194 return;
1195
1196 // The actual functionality is in ftpRename because put needs it
1197 if ( ftpRename( src.path(), dst.path(), flags ) )
1198 finished();
1199}
1200
1201bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags)
1202{
1203 Q_ASSERT(m_bLoggedOn);
1204
1205 // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793).
1206 if (!(jobFlags & KIO::Overwrite)) {
1207 if (ftpFileExists(dst)) {
1208 error(ERR_FILE_ALREADY_EXIST, dst);
1209 return false;
1210 }
1211 }
1212
1213 if (ftpFolder(dst, false)) {
1214 error(ERR_DIR_ALREADY_EXIST, dst);
1215 return false;
1216 }
1217
1218 // CD into parent folder
1219 const int pos = src.lastIndexOf('/');
1220 if (pos >= 0) {
1221 if(!ftpFolder(src.left(pos+1), false))
1222 return false;
1223 }
1224
1225 QByteArray from_cmd = "RNFR ";
1226 from_cmd += remoteEncoding()->encode(src.mid(pos+1));
1227 if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) {
1228 error( ERR_CANNOT_RENAME, src );
1229 return false;
1230 }
1231
1232 QByteArray to_cmd = "RNTO ";
1233 to_cmd += remoteEncoding()->encode(dst);
1234 if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) {
1235 error( ERR_CANNOT_RENAME, src );
1236 return false;
1237 }
1238
1239 return true;
1240}
1241
1242void Ftp::del( const KUrl& url, bool isfile )
1243{
1244 if( !ftpOpenConnection(loginImplicit) )
1245 return;
1246
1247 // When deleting a directory, we must exit from it first
1248 // The last command probably went into it (to stat it)
1249 if ( !isfile )
1250 ftpFolder(remoteEncoding()->directory(url), false); // ignore errors
1251
1252 QByteArray cmd = isfile ? "DELE " : "RMD ";
1253 cmd += remoteEncoding()->encode(url);
1254
1255 if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
1256 error( ERR_CANNOT_DELETE, url.path() );
1257 else
1258 finished();
1259}
1260
1261bool Ftp::ftpChmod( const QString & path, int permissions )
1262{
1263 Q_ASSERT( m_bLoggedOn );
1264
1265 if(m_extControl & chmodUnknown) // previous errors?
1266 return false;
1267
1268 // we need to do bit AND 777 to get permissions, in case
1269 // we were sent a full mode (unlikely)
1270 QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 /*octal*/ ) + ' ';
1271 cmd += path;
1272
1273 ftpSendCmd(remoteEncoding()->encode(cmd));
1274 if(m_iRespType == 2)
1275 return true;
1276
1277 if(m_iRespCode == 500)
1278 {
1279 m_extControl |= chmodUnknown;
1280 kDebug(7102) << "ftpChmod: CHMOD not supported - disabling";
1281 }
1282 return false;
1283}
1284
1285void Ftp::chmod( const KUrl & url, int permissions )
1286{
1287 if( !ftpOpenConnection(loginImplicit) )
1288 return;
1289
1290 if ( !ftpChmod( url.path(), permissions ) )
1291 error( ERR_CANNOT_CHMOD, url.path() );
1292 else
1293 finished();
1294}
1295
1296void Ftp::ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, UDSEntry& entry, bool isDir )
1297{
1298 Q_ASSERT(entry.count() == 0); // by contract :-)
1299
1300 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1301 entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size );
1302 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date );
1303 entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access );
1304 entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner );
1305 if ( !ftpEnt.group.isEmpty() )
1306 {
1307 entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group );
1308 }
1309
1310 if ( !ftpEnt.link.isEmpty() )
1311 {
1312 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link );
1313
1314 KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) );
1315 // Links on ftp sites are often links to dirs, and we have no way to check
1316 // that. Let's do like Netscape : assume dirs generally.
1317 // But we do this only when the mimetype can't be known from the filename.
1318 // --> we do better than Netscape :-)
1319 if ( mime->name() == KMimeType::defaultMimeType() )
1320 {
1321 kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename;
1322 entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) );
1323 isDir = true;
1324 }
1325 }
1326
1327 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type );
1328 // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime);
1329 // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime);
1330}
1331
1332
1333void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir )
1334{
1335 UDSEntry entry;
1336
1337
1338 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1339 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG );
1340 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1341 if (isDir) {
1342 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
1343 }
1344 // No details about size, ownership, group, etc.
1345
1346 statEntry(entry);
1347 finished();
1348}
1349
1350void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename )
1351{
1352 // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source")
1353 // When e.g. uploading a file, we still need stat() to return "not found"
1354 // when the file doesn't exist.
1355 QString statSide = metaData("statSide");
1356 kDebug(7102) << "statSide=" << statSide;
1357 if ( statSide == "source" )
1358 {
1359 kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing";
1360 // MS Server is incapable of handling "list <blah>" in a case insensitive way
1361 // But "retr <blah>" works. So lie in stat(), to get going...
1362 //
1363 // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run
1364 // where listing permissions are denied, but downloading is still possible.
1365 ftpShortStatAnswer( filename, false /*file, not dir*/ );
1366
1367 return;
1368 }
1369
1370 error( ERR_DOES_NOT_EXIST, path );
1371}
1372
1373void Ftp::stat(const KUrl &url)
1374{
1375 kDebug(7102) << "path=" << url.path();
1376 if( !ftpOpenConnection(loginImplicit) )
1377 return;
1378
1379 const QString path = ftpCleanPath( QDir::cleanPath( url.path() ) );
1380 kDebug(7102) << "cleaned path=" << path;
1381
1382 // We can't stat root, but we know it's a dir.
1383 if( path.isEmpty() || path == "/" )
1384 {
1385 UDSEntry entry;
1386 //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) );
1387 entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) );
1388 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
1389 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
1390 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1391 entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) );
1392 entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) );
1393 // no size
1394
1395 statEntry( entry );
1396 finished();
1397 return;
1398 }
1399
1400 KUrl tempurl( url );
1401 tempurl.setPath( path ); // take the clean one
1402 QString listarg; // = tempurl.directory(KUrl::ObeyTrailingSlash);
1403 QString parentDir;
1404 QString filename = tempurl.fileName();
1405 Q_ASSERT(!filename.isEmpty());
1406 QString search = filename;
1407
1408 // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info)
1409 // if it doesn't work, it's a file (and then we'll use dir filename)
1410 bool isDir = ftpFolder(path, false);
1411
1412 // if we're only interested in "file or directory", we should stop here
1413 QString sDetails = metaData("details");
1414 int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
1415 kDebug(7102) << "details=" << details;
1416 if ( details == 0 )
1417 {
1418 if ( !isDir && !ftpFileExists(path) ) // ok, not a dir -> is it a file ?
1419 { // no -> it doesn't exist at all
1420 ftpStatAnswerNotFound( path, filename );
1421 return;
1422 }
1423 ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done
1424 return;
1425 }
1426
1427 if (!isDir)
1428 {
1429 // It is a file or it doesn't exist, try going to parent directory
1430 parentDir = tempurl.directory(KUrl::AppendTrailingSlash);
1431 // With files we can do "LIST <filename>" to avoid listing the whole dir
1432 listarg = filename;
1433 }
1434 else
1435 {
1436 // --- New implementation:
1437 // Don't list the parent dir. Too slow, might not show it, etc.
1438 // Just return that it's a dir.
1439 UDSEntry entry;
1440 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1441 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
1442 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1443 // No clue about size, ownership, group, etc.
1444
1445 statEntry(entry);
1446 finished();
1447 return;
1448 }
1449
1450 // Now cwd the parent dir, to prepare for listing
1451 if( !ftpFolder(parentDir, true) )
1452 return;
1453
1454 if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) )
1455 {
1456 kError(7102) << "COULD NOT LIST";
1457 return;
1458 }
1459 kDebug(7102) << "Starting of list was ok";
1460
1461 Q_ASSERT( !search.isEmpty() && search != "/" );
1462
1463 bool bFound = false;
1464 KUrl linkURL;
1465 FtpEntry ftpEnt;
1466 QList<FtpEntry> ftpValidateEntList;
1467 while (ftpReadDir(ftpEnt)) {
1468 if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) {
1469 ftpValidateEntList.append(ftpEnt);
1470 continue;
1471 }
1472
1473 // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at)
1474 // return only the filename when doing "dir /full/path/to/file"
1475 if (!bFound) {
1476 bFound = maybeEmitStatEntry(ftpEnt, search, filename, isDir);
1477 }
1478 // kDebug(7102) << ftpEnt.name;
1479 }
1480
1481 for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
1482 FtpEntry& ftpEnt = ftpValidateEntList[i];
1483 fixupEntryName(&ftpEnt);
1484 if (maybeEmitStatEntry(ftpEnt, search, filename, isDir)) {
1485 break;
1486 }
1487 }
1488
1489 ftpCloseCommand(); // closes the data connection only
1490
1491 if ( !bFound )
1492 {
1493 ftpStatAnswerNotFound( path, filename );
1494 return;
1495 }
1496
1497 if ( !linkURL.isEmpty() )
1498 {
1499 if ( linkURL == url || linkURL == tempurl )
1500 {
1501 error( ERR_CYCLIC_LINK, linkURL.prettyUrl() );
1502 return;
1503 }
1504 Ftp::stat( linkURL );
1505 return;
1506 }
1507
1508 kDebug(7102) << "stat : finished successfully";
1509 finished();
1510}
1511
1512bool Ftp::maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir)
1513{
1514 if ((search == ftpEnt.name || filename == ftpEnt.name) && !filename.isEmpty()) {
1515 UDSEntry entry;
1516 ftpCreateUDSEntry( filename, ftpEnt, entry, isDir );
1517 statEntry( entry );
1518 return true;
1519 }
1520
1521 return false;
1522}
1523
1524void Ftp::listDir( const KUrl &url )
1525{
1526 kDebug(7102) << url;
1527 if( !ftpOpenConnection(loginImplicit) )
1528 return;
1529
1530 // No path specified ?
1531 QString path = url.path();
1532 if ( path.isEmpty() )
1533 {
1534 KUrl realURL;
1535 realURL.setProtocol( "ftp" );
1536 realURL.setUser( m_user );
1537 realURL.setPass( m_pass );
1538 realURL.setHost( m_host );
1539 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
1540 realURL.setPort( m_port );
1541 if ( m_initialPath.isEmpty() )
1542 m_initialPath = '/';
1543 realURL.setPath( m_initialPath );
1544 kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl();
1545 redirection( realURL );
1546 finished();
1547 return;
1548 }
1549
1550 kDebug(7102) << "hunting for path" << path;
1551
1552 if (!ftpOpenDir(path)) {
1553 if (ftpFileExists(path)) {
1554 error(ERR_IS_FILE, path);
1555 } else {
1556 // not sure which to emit
1557 //error( ERR_DOES_NOT_EXIST, path );
1558 error( ERR_CANNOT_ENTER_DIRECTORY, path );
1559 }
1560 return;
1561 }
1562
1563 UDSEntry entry;
1564 FtpEntry ftpEnt;
1565 QList<FtpEntry> ftpValidateEntList;
1566 while( ftpReadDir(ftpEnt) )
1567 {
1568 //kDebug(7102) << ftpEnt.name;
1569 //Q_ASSERT( !ftpEnt.name.isEmpty() );
1570 if (!ftpEnt.name.isEmpty()) {
1571 if (ftpEnt.name.at(0).isSpace()) {
1572 ftpValidateEntList.append(ftpEnt);
1573 continue;
1574 }
1575
1576 //if ( S_ISDIR( (mode_t)ftpEnt.type ) )
1577 // kDebug(7102) << "is a dir";
1578 //if ( !ftpEnt.link.isEmpty() )
1579 // kDebug(7102) << "is a link to " << ftpEnt.link;
1580 ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
1581 listEntry( entry, false );
1582 entry.clear();
1583 }
1584 }
1585
1586 for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
1587 FtpEntry& ftpEnt = ftpValidateEntList[i];
1588 fixupEntryName(&ftpEnt);
1589 ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
1590 listEntry( entry, false );
1591 entry.clear();
1592 }
1593
1594 listEntry( entry, true ); // ready
1595 ftpCloseCommand(); // closes the data connection only
1596 finished();
1597}
1598
1599void Ftp::slave_status()
1600{
1601 kDebug(7102) << "Got slave_status host = " << (!m_host.toLatin1().isEmpty() ? m_host.toAscii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]";
1602 slaveStatus( m_host, m_bLoggedOn );
1603}
1604
1605bool Ftp::ftpOpenDir( const QString & path )
1606{
1607 //QString path( _url.path(KUrl::RemoveTrailingSlash) );
1608
1609 // We try to change to this directory first to see whether it really is a directory.
1610 // (And also to follow symlinks)
1611 QString tmp = path.isEmpty() ? QString("/") : path;
1612
1613 // We get '550', whether it's a file or doesn't exist...
1614 if( !ftpFolder(tmp, false) )
1615 return false;
1616
1617 // Don't use the path in the list command:
1618 // We changed into this directory anyway - so it's enough just to send "list".
1619 // We use '-a' because the application MAY be interested in dot files.
1620 // The only way to really know would be to have a metadata flag for this...
1621 // Since some windows ftp server seems not to support the -a argument, we use a fallback here.
1622 // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com)
1623 if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
1624 {
1625 if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
1626 {
1627 kWarning(7102) << "Can't open for listing";
1628 return false;
1629 }
1630 }
1631 kDebug(7102) << "Starting of list was ok";
1632 return true;
1633}
1634
1635bool Ftp::ftpReadDir(FtpEntry& de)
1636{
1637 Q_ASSERT(m_data != NULL);
1638
1639 // get a line from the data connecetion ...
1640 while( true )
1641 {
1642 while (!m_data->canReadLine() && m_data->waitForReadyRead((readTimeout() * 1000))) {}
1643 QByteArray data = m_data->readLine();
1644 if (data.size() == 0)
1645 break;
1646
1647 const char* buffer = data.data();
1648 kDebug(7102) << "dir > " << buffer;
1649
1650 //Normally the listing looks like
1651 // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log
1652 // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442)
1653 // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI
1654
1655 // we should always get the following 5 fields ...
1656 const char *p_access, *p_junk, *p_owner, *p_group, *p_size;
1657 if( (p_access = strtok((char*)buffer," ")) == 0) continue;
1658 if( (p_junk = strtok(NULL," ")) == 0) continue;
1659 if( (p_owner = strtok(NULL," ")) == 0) continue;
1660 if( (p_group = strtok(NULL," ")) == 0) continue;
1661 if( (p_size = strtok(NULL," ")) == 0) continue;
1662
1663 //kDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size;
1664
1665 de.access = 0;
1666 if ( qstrlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware
1667 de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions
1668 }
1669
1670 const char *p_date_1, *p_date_2, *p_date_3, *p_name;
1671
1672 // A special hack for "/dev". A listing may look like this:
1673 // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero
1674 // So we just ignore the number in front of the ",". Ok, it is a hack :-)
1675 if ( strchr( p_size, ',' ) != 0L )
1676 {
1677 //kDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)";
1678 if ((p_size = strtok(NULL," ")) == 0)
1679 continue;
1680 }
1681
1682 // Check whether the size we just read was really the size
1683 // or a month (this happens when the server lists no group)
1684 // Used to be the case on sunsite.uio.no, but not anymore
1685 // This is needed for the Netware case, too.
1686 if ( !isdigit( *p_size ) )
1687 {
1688 p_date_1 = p_size;
1689 p_size = p_group;
1690 p_group = 0;
1691 //kDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1;
1692 }
1693 else
1694 {
1695 p_date_1 = strtok(NULL," ");
1696 //kDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1;
1697 }
1698
1699 if ( p_date_1 != 0 &&
1700 (p_date_2 = strtok(NULL," ")) != 0 &&
1701 (p_date_3 = strtok(NULL," ")) != 0 &&
1702 (p_name = strtok(NULL,"\r\n")) != 0 )
1703 {
1704 {
1705 QByteArray tmp( p_name );
1706 if ( p_access[0] == 'l' )
1707 {
1708 int i = tmp.lastIndexOf( " -> " );
1709 if ( i != -1 ) {
1710 de.link = remoteEncoding()->decode(p_name + i + 4);
1711 tmp.truncate( i );
1712 }
1713 else
1714 de.link.clear();
1715 }
1716 else
1717 de.link.clear();
1718
1719 if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/'
1720 tmp.remove( 0, 1 );
1721
1722 if (tmp.indexOf('/') != -1)
1723 continue; // Don't trick us!
1724
1725 de.name = remoteEncoding()->decode(tmp);
1726 }
1727
1728 de.type = S_IFREG;
1729 switch ( p_access[0] ) {
1730 case 'd':
1731 de.type = S_IFDIR;
1732 break;
1733 case 's':
1734 de.type = S_IFSOCK;
1735 break;
1736 case 'b':
1737 de.type = S_IFBLK;
1738 break;
1739 case 'c':
1740 de.type = S_IFCHR;
1741 break;
1742 case 'l':
1743 de.type = S_IFREG;
1744 // we don't set S_IFLNK here. de.link says it.
1745 break;
1746 default:
1747 break;
1748 }
1749
1750 if ( p_access[1] == 'r' )
1751 de.access |= S_IRUSR;
1752 if ( p_access[2] == 'w' )
1753 de.access |= S_IWUSR;
1754 if ( p_access[3] == 'x' || p_access[3] == 's' )
1755 de.access |= S_IXUSR;
1756 if ( p_access[4] == 'r' )
1757 de.access |= S_IRGRP;
1758 if ( p_access[5] == 'w' )
1759 de.access |= S_IWGRP;
1760 if ( p_access[6] == 'x' || p_access[6] == 's' )
1761 de.access |= S_IXGRP;
1762 if ( p_access[7] == 'r' )
1763 de.access |= S_IROTH;
1764 if ( p_access[8] == 'w' )
1765 de.access |= S_IWOTH;
1766 if ( p_access[9] == 'x' || p_access[9] == 't' )
1767 de.access |= S_IXOTH;
1768 if ( p_access[3] == 's' || p_access[3] == 'S' )
1769 de.access |= S_ISUID;
1770 if ( p_access[6] == 's' || p_access[6] == 'S' )
1771 de.access |= S_ISGID;
1772 if ( p_access[9] == 't' || p_access[9] == 'T' )
1773 de.access |= S_ISVTX;
1774
1775 de.owner = remoteEncoding()->decode(p_owner);
1776 de.group = remoteEncoding()->decode(p_group);
1777 de.size = charToLongLong(p_size);
1778
1779 // Parsing the date is somewhat tricky
1780 // Examples : "Oct 6 22:49", "May 13 1999"
1781
1782 // First get current time - we need the current month and year
1783 time_t currentTime = time( 0L );
1784 struct tm * tmptr = gmtime( &currentTime );
1785 int currentMonth = tmptr->tm_mon;
1786 //kDebug(7102) << "Current time :" << asctime( tmptr );
1787 // Reset time fields
1788 tmptr->tm_isdst = -1; // We do not anything about day saving time
1789 tmptr->tm_sec = 0;
1790 tmptr->tm_min = 0;
1791 tmptr->tm_hour = 0;
1792 // Get day number (always second field)
1793 if (p_date_2)
1794 tmptr->tm_mday = atoi( p_date_2 );
1795 // Get month from first field
1796 // NOTE : no, we don't want to use KLocale here
1797 // It seems all FTP servers use the English way
1798 //kDebug(7102) << "Looking for month " << p_date_1;
1799 static const char * const s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1800 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1801 for ( int c = 0 ; c < 12 ; c ++ )
1802 if ( !qstrcmp( p_date_1, s_months[c]) )
1803 {
1804 //kDebug(7102) << "Found month " << c << " for " << p_date_1;
1805 tmptr->tm_mon = c;
1806 break;
1807 }
1808
1809 // Parse third field
1810 if ( qstrlen( p_date_3 ) == 4 ) // 4 digits, looks like a year
1811 tmptr->tm_year = atoi( p_date_3 ) - 1900;
1812 else
1813 {
1814 // otherwise, the year is implicit
1815 // according to man ls, this happens when it is between than 6 months
1816 // old and 1 hour in the future.
1817 // So the year is : current year if tm_mon <= currentMonth+1
1818 // otherwise current year minus one
1819 // (The +1 is a security for the "+1 hour" at the end of the month issue)
1820 if ( tmptr->tm_mon > currentMonth + 1 )
1821 tmptr->tm_year--;
1822
1823 // and p_date_3 contains probably a time
1824 char * semicolon;
1825 if ( p_date_3 && ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
1826 {
1827 *semicolon = '\0';
1828 tmptr->tm_min = atoi( semicolon + 1 );
1829 tmptr->tm_hour = atoi( p_date_3 );
1830 }
1831 else
1832 kWarning(7102) << "Can't parse third field " << p_date_3;
1833 }
1834
1835 //kDebug(7102) << asctime( tmptr );
1836 de.date = mktime( tmptr );
1837 return true;
1838 }
1839 } // line invalid, loop to get another line
1840 return false;
1841}
1842
1843//===============================================================================
1844// public: get download file from server
1845// helper: ftpGet called from get() and copy()
1846//===============================================================================
1847void Ftp::get( const KUrl & url )
1848{
1849 kDebug(7102) << url;
1850
1851 int iError = 0;
1852 const StatusCode cs = ftpGet(iError, -1, url, 0);
1853 ftpCloseCommand(); // must close command!
1854
1855 if (cs == statusSuccess) {
1856 finished();
1857 return;
1858 }
1859
1860 if (iError) { // can have only server side errs
1861 error(iError, url.path());
1862 }
1863}
1864
1865Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset)
1866{
1867 // Calls error() by itself!
1868 if( !ftpOpenConnection(loginImplicit) )
1869 return statusServerError;
1870
1871 // Try to find the size of the file (and check that it exists at
1872 // the same time). If we get back a 550, "File does not exist"
1873 // or "not a plain file", check if it is a directory. If it is a
1874 // directory, return an error; otherwise simply try to retrieve
1875 // the request...
1876 if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) &&
1877 ftpFolder(url.path(), false) )
1878 {
1879 // Ok it's a dir in fact
1880 kDebug(7102) << "it is a directory in fact";
1881 iError = ERR_IS_DIRECTORY;
1882 return statusServerError;
1883 }
1884
1885 QString resumeOffset = metaData("resume");
1886 if ( !resumeOffset.isEmpty() )
1887 {
1888 llOffset = resumeOffset.toLongLong();
1889 kDebug(7102) << "got offset from metadata : " << llOffset;
1890 }
1891
1892 if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) )
1893 {
1894 kWarning(7102) << "Can't open for reading";
1895 return statusServerError;
1896 }
1897
1898 // Read the size from the response string
1899 if(m_size == UnknownSize)
1900 {
1901 const char* psz = strrchr( ftpResponse(4), '(' );
1902 if(psz) m_size = charToLongLong(psz+1);
1903 if (!m_size) m_size = UnknownSize;
1904 }
1905
1906 // Send the mime-type...
1907 if (iCopyFile == -1) {
1908 StatusCode status = ftpSendMimeType(iError, url);
1909 if (status != statusSuccess) {
1910 return status;
1911 }
1912 }
1913
1914 KIO::filesize_t bytesLeft = 0;
1915 if ( m_size != UnknownSize ) {
1916 bytesLeft = m_size - llOffset;
1917 totalSize( m_size ); // emit the total size...
1918 }
1919
1920 kDebug(7102) << "starting with offset=" << llOffset;
1921 KIO::fileoffset_t processed_size = llOffset;
1922
1923 QByteArray array;
1924 char buffer[maximumIpcSize];
1925 // start with small data chunks in case of a slow data source (modem)
1926 // - unfortunately this has a negative impact on performance for large
1927 // - files - so we will increase the block size after a while ...
1928 int iBlockSize = initialIpcSize;
1929 int iBufferCur = 0;
1930
1931 while(m_size == UnknownSize || bytesLeft > 0)
1932 { // let the buffer size grow if the file is larger 64kByte ...
1933 if(processed_size-llOffset > 1024 * 64)
1934 iBlockSize = maximumIpcSize;
1935
1936 // read the data and detect EOF or error ...
1937 if(iBlockSize+iBufferCur > (int)sizeof(buffer))
1938 iBlockSize = sizeof(buffer) - iBufferCur;
1939 if (m_data->bytesAvailable() == 0)
1940 m_data->waitForReadyRead((readTimeout() * 1000));
1941 int n = m_data->read( buffer+iBufferCur, iBlockSize );
1942 if(n <= 0)
1943 { // this is how we detect EOF in case of unknown size
1944 if( m_size == UnknownSize && n == 0 )
1945 break;
1946 // unexpected eof. Happens when the daemon gets killed.
1947 iError = ERR_COULD_NOT_READ;
1948 return statusServerError;
1949 }
1950 processed_size += n;
1951
1952 // collect very small data chunks in buffer before processing ...
1953 if(m_size != UnknownSize)
1954 {
1955 bytesLeft -= n;
1956 iBufferCur += n;
1957 if(iBufferCur < minimumMimeSize && bytesLeft > 0)
1958 {
1959 processedSize( processed_size );
1960 continue;
1961 }
1962 n = iBufferCur;
1963 iBufferCur = 0;
1964 }
1965
1966 // write output file or pass to data pump ...
1967 if(iCopyFile == -1)
1968 {
1969 array = QByteArray::fromRawData(buffer, n);
1970 data( array );
1971 array.clear();
1972 }
1973 else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0)
1974 return statusClientError; // client side error
1975 processedSize( processed_size );
1976 }
1977
1978 kDebug(7102) << "done";
1979 if(iCopyFile == -1) // must signal EOF to data pump ...
1980 data(array); // array is empty and must be empty!
1981
1982 processedSize( m_size == UnknownSize ? processed_size : m_size );
1983 return statusSuccess;
1984}
1985
1986#if 0
1987 void Ftp::mimetype( const KUrl& url )
1988 {
1989 if( !ftpOpenConnection(loginImplicit) )
1990 return;
1991
1992 if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
1993 kWarning(7102) << "Can't open for reading";
1994 return;
1995 }
1996 char buffer[ 2048 ];
1997 QByteArray array;
1998 // Get one chunk of data only and send it, KIO::Job will determine the
1999 // mimetype from it using KMimeMagic
2000 int n = m_data->read( buffer, 2048 );
2001 array.setRawData(buffer, n);
2002 data( array );
2003 array.resetRawData(buffer, n);
2004
2005 kDebug(7102) << "aborting";
2006 ftpAbortTransfer();
2007
2008 kDebug(7102) << "finished";
2009 finished();
2010 kDebug(7102) << "after finished";
2011 }
2012
2013 void Ftp::ftpAbortTransfer()
2014 {
2015 // RFC 959, page 34-35
2016 // IAC (interpret as command) = 255 ; IP (interrupt process) = 254
2017 // DM = 242 (data mark)
2018 char msg[4];
2019 // 1. User system inserts the Telnet "Interrupt Process" (IP) signal
2020 // in the Telnet stream.
2021 msg[0] = (char) 255; //IAC
2022 msg[1] = (char) 254; //IP
2023 (void) send(sControl, msg, 2, 0);
2024 // 2. User system sends the Telnet "Sync" signal.
2025 msg[0] = (char) 255; //IAC
2026 msg[1] = (char) 242; //DM
2027 if (send(sControl, msg, 2, MSG_OOB) != 2)
2028 ; // error...
2029
2030 // Send ABOR
2031 kDebug(7102) << "send ABOR";
2032 QCString buf = "ABOR\r\n";
2033 if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) {
2034 error( ERR_COULD_NOT_WRITE, QString() );
2035 return;
2036 }
2037
2038 //
2039 kDebug(7102) << "read resp";
2040 if ( readresp() != '2' )
2041 {
2042 error( ERR_COULD_NOT_READ, QString() );
2043 return;
2044 }
2045
2046 kDebug(7102) << "close sockets";
2047 closeSockets();
2048 }
2049#endif
2050
2051//===============================================================================
2052// public: put upload file to server
2053// helper: ftpPut called from put() and copy()
2054//===============================================================================
2055void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags)
2056{
2057 kDebug(7102) << url;
2058
2059 int iError = 0; // iError gets status
2060 const StatusCode cs = ftpPut(iError, -1, url, permissions, flags);
2061 ftpCloseCommand(); // must close command!
2062
2063 if (cs == statusSuccess) {
2064 finished();
2065 return;
2066 }
2067
2068 if (iError) { // can have only server side errs
2069 error(iError, url.path());
2070 }
2071}
2072
2073Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url,
2074 int permissions, KIO::JobFlags flags)
2075{
2076 if( !ftpOpenConnection(loginImplicit) )
2077 return statusServerError;
2078
2079 // Don't use mark partial over anonymous FTP.
2080 // My incoming dir allows put but not rename...
2081 bool bMarkPartial;
2082 if (m_user.isEmpty () || m_user == FTP_LOGIN)
2083 bMarkPartial = false;
2084 else
2085 bMarkPartial = config()->readEntry("MarkPartial", true);
2086
2087 QString dest_orig = dest_url.path();
2088 QString dest_part( dest_orig );
2089 dest_part += ".part";
2090
2091 if ( ftpSize( dest_orig, 'I' ) )
2092 {
2093 if ( m_size == 0 )
2094 { // delete files with zero size
2095 QByteArray cmd = "DELE ";
2096 cmd += remoteEncoding()->encode(dest_orig);
2097 if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
2098 {
2099 iError = ERR_CANNOT_DELETE_PARTIAL;
2100 return statusServerError;
2101 }
2102 }
2103 else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
2104 {
2105 iError = ERR_FILE_ALREADY_EXIST;
2106 return statusServerError;
2107 }
2108 else if ( bMarkPartial )
2109 { // when using mark partial, append .part extension
2110 if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) )
2111 {
2112 iError = ERR_CANNOT_RENAME_PARTIAL;
2113 return statusServerError;
2114 }
2115 }
2116 // Don't chmod an existing file
2117 permissions = -1;
2118 }
2119 else if ( bMarkPartial && ftpSize( dest_part, 'I' ) )
2120 { // file with extension .part exists
2121 if ( m_size == 0 )
2122 { // delete files with zero size
2123 QByteArray cmd = "DELE ";
2124 cmd += remoteEncoding()->encode(dest_part);
2125 if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
2126 {
2127 iError = ERR_CANNOT_DELETE_PARTIAL;
2128 return statusServerError;
2129 }
2130 }
2131 else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
2132 {
2133 flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags;
2134 if (!(flags & KIO::Resume))
2135 {
2136 iError = ERR_FILE_ALREADY_EXIST;
2137 return statusServerError;
2138 }
2139 }
2140 }
2141 else
2142 m_size = 0;
2143
2144 QString dest;
2145
2146 // if we are using marking of partial downloads -> add .part extension
2147 if ( bMarkPartial ) {
2148 kDebug(7102) << "Adding .part extension to " << dest_orig;
2149 dest = dest_part;
2150 } else
2151 dest = dest_orig;
2152
2153 KIO::fileoffset_t offset = 0;
2154
2155 // set the mode according to offset
2156 if( (flags & KIO::Resume) && m_size > 0 )
2157 {
2158 offset = m_size;
2159 if(iCopyFile != -1)
2160 {
2161 if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 )
2162 {
2163 iError = ERR_CANNOT_RESUME;
2164 return statusClientError;
2165 }
2166 }
2167 }
2168
2169 if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) )
2170 return statusServerError;
2171
2172 kDebug(7102) << "ftpPut: starting with offset=" << offset;
2173 KIO::fileoffset_t processed_size = offset;
2174
2175 QByteArray buffer;
2176 int result;
2177 int iBlockSize = initialIpcSize;
2178 // Loop until we got 'dataEnd'
2179 do
2180 {
2181 if(iCopyFile == -1)
2182 {
2183 dataReq(); // Request for data
2184 result = readData( buffer );
2185 }
2186 else
2187 { // let the buffer size grow if the file is larger 64kByte ...
2188 if(processed_size-offset > 1024 * 64)
2189 iBlockSize = maximumIpcSize;
2190 buffer.resize(iBlockSize);
2191 result = ::read(iCopyFile, buffer.data(), buffer.size());
2192 if(result < 0)
2193 iError = ERR_COULD_NOT_WRITE;
2194 else
2195 buffer.resize(result);
2196 }
2197
2198 if (result > 0)
2199 {
2200 m_data->write( buffer );
2201 while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {}
2202 processed_size += result;
2203 processedSize (processed_size);
2204 }
2205 }
2206 while ( result > 0 );
2207
2208 if (result != 0) // error
2209 {
2210 ftpCloseCommand(); // don't care about errors
2211 kDebug(7102) << "Error during 'put'. Aborting.";
2212 if (bMarkPartial)
2213 {
2214 // Remove if smaller than minimum size
2215 if ( ftpSize( dest, 'I' ) &&
2216 ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
2217 {
2218 QByteArray cmd = "DELE ";
2219 cmd += remoteEncoding()->encode(dest);
2220 (void) ftpSendCmd( cmd );
2221 }
2222 }
2223 return statusServerError;
2224 }
2225
2226 if ( !ftpCloseCommand() )
2227 {
2228 iError = ERR_COULD_NOT_WRITE;
2229 return statusServerError;
2230 }
2231
2232 // after full download rename the file back to original name
2233 if ( bMarkPartial )
2234 {
2235 kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")";
2236 if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) )
2237 {
2238 iError = ERR_CANNOT_RENAME_PARTIAL;
2239 return statusServerError;
2240 }
2241 }
2242
2243 // set final permissions
2244 if ( permissions != -1 )
2245 {
2246 if ( m_user == FTP_LOGIN )
2247 kDebug(7102) << "Trying to chmod over anonymous FTP ???";
2248 // chmod the file we just put
2249 if ( ! ftpChmod( dest_orig, permissions ) )
2250 {
2251 // To be tested
2252 //if ( m_user != FTP_LOGIN )
2253 // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) );
2254 }
2255 }
2256
2257 return statusSuccess;
2258}
2259
2260const char* Ftp::ftpSendSizeCmd(const QString& path)
2261{
2262 // Some servers do not allow absolute path for SIZE; so we use
2263 // relative paths whenever possible. #326292
2264 QString currentPath(m_currentPath);
2265 if (!currentPath.endsWith(QLatin1Char('/'))) {
2266 currentPath += QLatin1Char('/');
2267 }
2268
2269 QByteArray buf;
2270 buf = "SIZE ";
2271 if (path.startsWith(currentPath)) {
2272 buf += remoteEncoding()->encode(path.mid(currentPath.length()));
2273 } else {
2274 buf += remoteEncoding()->encode(path);
2275 }
2276
2277 if (!ftpSendCmd(buf) || m_iRespType != 2) {
2278 return 0;
2279 }
2280
2281 // skip leading "213 " (response code)
2282 return ftpResponse(4);
2283}
2284
2285
2288bool Ftp::ftpSize(const QString & path, char mode)
2289{
2290 m_size = UnknownSize;
2291 if (!ftpDataMode(mode)) {
2292 return false;
2293 }
2294
2295 const QByteArray psz(ftpSendSizeCmd(path));
2296 if (psz.isEmpty()) {
2297 return false;
2298 }
2299
2300 bool ok = false;
2301 m_size = psz.trimmed().toLongLong(&ok);
2302 if (!ok) {
2303 m_size = UnknownSize;
2304 }
2305
2306 return true;
2307}
2308
2309bool Ftp::ftpFileExists(const QString& path)
2310{
2311 return ftpSendSizeCmd(path) != 0;
2312}
2313
2314// Today the differences between ASCII and BINARY are limited to
2315// CR or CR/LF line terminators. Many servers ignore ASCII (like
2316// win2003 -or- vsftp with default config). In the early days of
2317// computing, when even text-files had structure, this stuff was
2318// more important.
2319// Theoretically "list" could return different results in ASCII
2320// and BINARY mode. But again, most servers ignore ASCII here.
2321bool Ftp::ftpDataMode(char cMode)
2322{
2323 if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I';
2324 else if(cMode == 'a') cMode = 'A';
2325 else if(cMode != 'A') cMode = 'I';
2326
2327 kDebug(7102) << "want" << cMode << "has" << m_cDataMode;
2328 if(m_cDataMode == cMode)
2329 return true;
2330
2331 QByteArray buf = "TYPE ";
2332 buf += cMode;
2333 if( !ftpSendCmd(buf) || (m_iRespType != 2) )
2334 return false;
2335 m_cDataMode = cMode;
2336 return true;
2337}
2338
2339
2340bool Ftp::ftpFolder(const QString& path, bool bReportError)
2341{
2342 QString newPath = path;
2343 int iLen = newPath.length();
2344 if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1);
2345
2346 //kDebug(7102) << "want" << newPath << "has" << m_currentPath;
2347 if(m_currentPath == newPath)
2348 return true;
2349
2350 QByteArray tmp = "cwd ";
2351 tmp += remoteEncoding()->encode(newPath);
2352 if( !ftpSendCmd(tmp) )
2353 return false; // connection failure
2354 if(m_iRespType != 2)
2355 {
2356 if(bReportError)
2357 error(ERR_CANNOT_ENTER_DIRECTORY, path);
2358 return false; // not a folder
2359 }
2360 m_currentPath = newPath;
2361 return true;
2362}
2363
2364
2365//===============================================================================
2366// public: copy don't use kio data pump if one side is a local file
2367// helper: ftpCopyPut called from copy() on upload
2368// helper: ftpCopyGet called from copy() on download
2369//===============================================================================
2370void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags )
2371{
2372 int iError = 0;
2373 int iCopyFile = -1;
2374 StatusCode cs = statusSuccess;
2375 bool bSrcLocal = src.isLocalFile();
2376 bool bDestLocal = dest.isLocalFile();
2377 QString sCopyFile;
2378
2379 if(bSrcLocal && !bDestLocal) { // File -> Ftp
2380 sCopyFile = src.toLocalFile();
2381 kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path();
2382 cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags);
2383 if(cs == statusServerError) {
2384 sCopyFile = dest.url();
2385 }
2386 } else if(!bSrcLocal && bDestLocal) { // Ftp -> File
2387 sCopyFile = dest.toLocalFile();
2388 kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile;
2389 cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags);
2390 if(cs == statusServerError) {
2391 sCopyFile = src.url();
2392 }
2393 } else {
2394 error(ERR_UNSUPPORTED_ACTION, QString());
2395 return;
2396 }
2397
2398 // perform clean-ups and report error (if any)
2399 if(iCopyFile != -1) {
2400 ::close(iCopyFile);
2401 }
2402
2403 ftpCloseCommand(); // must close command!
2404
2405 if (cs == statusSuccess) {
2406 finished();
2407 return;
2408 }
2409
2410 if(iError) {
2411 error(iError, sCopyFile);
2412 }
2413}
2414
2415
2416Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile,
2417 const KUrl& url, int permissions, KIO::JobFlags flags)
2418{
2419 // check if source is ok ...
2420 KDE_struct_stat buff;
2421 bool bSrcExists = (KDE::stat( sCopyFile, &buff ) != -1);
2422 if(bSrcExists)
2423 { if(S_ISDIR(buff.st_mode))
2424 {
2425 iError = ERR_IS_DIRECTORY;
2426 return statusClientError;
2427 }
2428 }
2429 else
2430 {
2431 iError = ERR_DOES_NOT_EXIST;
2432 return statusClientError;
2433 }
2434
2435 iCopyFile = KDE::open( sCopyFile, O_RDONLY );
2436 if(iCopyFile == -1)
2437 {
2438 iError = ERR_CANNOT_OPEN_FOR_READING;
2439 return statusClientError;
2440 }
2441
2442 // delegate the real work (iError gets status) ...
2443 totalSize(buff.st_size);
2444#ifdef ENABLE_CAN_RESUME
2445 return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume);
2446#else
2447 return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume);
2448#endif
2449}
2450
2451
2452Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile,
2453 const KUrl& url, int permissions, KIO::JobFlags flags)
2454{
2455 // check if destination is ok ...
2456 KDE_struct_stat buff;
2457 const bool bDestExists = (KDE::stat( sCopyFile, &buff ) != -1);
2458 if(bDestExists)
2459 { if(S_ISDIR(buff.st_mode))
2460 {
2461 iError = ERR_IS_DIRECTORY;
2462 return statusClientError;
2463 }
2464 if(!(flags & KIO::Overwrite))
2465 {
2466 iError = ERR_FILE_ALREADY_EXIST;
2467 return statusClientError;
2468 }
2469 }
2470
2471 // do we have a ".part" file?
2472 const QString sPart = sCopyFile + QLatin1String(".part");
2473 bool bResume = false;
2474 const bool bPartExists = (KDE::stat( sPart, &buff ) != -1);
2475 const bool bMarkPartial = config()->readEntry("MarkPartial", true);
2476 const QString dest = bMarkPartial ? sPart : sCopyFile;
2477 if (bMarkPartial && bPartExists && buff.st_size > 0)
2478 { // must not be a folder! please fix a similar bug in kio_file!!
2479 if(S_ISDIR(buff.st_mode))
2480 {
2481 iError = ERR_DIR_ALREADY_EXIST;
2482 return statusClientError; // client side error
2483 }
2484 //doesn't work for copy? -> design flaw?
2485#ifdef ENABLE_CAN_RESUME
2486 bResume = canResume( buff.st_size );
2487#else
2488 bResume = true;
2489#endif
2490 }
2491
2492 if (bPartExists && !bResume) // get rid of an unwanted ".part" file
2493 QFile::remove(sPart);
2494
2495 // WABA: Make sure that we keep writing permissions ourselves,
2496 // otherwise we can be in for a surprise on NFS.
2497 mode_t initialMode;
2498 if (permissions != -1)
2499 initialMode = permissions | S_IWUSR;
2500 else
2501 initialMode = 0666;
2502
2503 // open the output file ...
2504 KIO::fileoffset_t hCopyOffset = 0;
2505 if (bResume) {
2506 iCopyFile = KDE::open( sPart, O_RDWR ); // append if resuming
2507 hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END);
2508 if(hCopyOffset < 0)
2509 {
2510 iError = ERR_CANNOT_RESUME;
2511 return statusClientError; // client side error
2512 }
2513 kDebug(7102) << "resuming at " << hCopyOffset;
2514 }
2515 else {
2516 iCopyFile = KDE::open(dest, O_CREAT | O_TRUNC | O_WRONLY, initialMode);
2517 }
2518
2519 if(iCopyFile == -1)
2520 {
2521 kDebug(7102) << "### COULD NOT WRITE " << sCopyFile;
2522 iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED
2523 : ERR_CANNOT_OPEN_FOR_WRITING;
2524 return statusClientError;
2525 }
2526
2527 // delegate the real work (iError gets status) ...
2528 StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset);
2529 if( ::close(iCopyFile) && iRes == statusSuccess )
2530 {
2531 iError = ERR_COULD_NOT_WRITE;
2532 iRes = statusClientError;
2533 }
2534 iCopyFile = -1;
2535
2536 // handle renaming or deletion of a partial file ...
2537 if(bMarkPartial)
2538 {
2539 if(iRes == statusSuccess)
2540 { // rename ".part" on success
2541 if ( KDE::rename( sPart, sCopyFile ) )
2542 {
2543 // If rename fails, try removing the destination first if it exists.
2544 if (!bDestExists || !(QFile::remove(sCopyFile) && KDE::rename(sPart, sCopyFile) == 0)) {
2545 kDebug(7102) << "cannot rename " << sPart << " to " << sCopyFile;
2546 iError = ERR_CANNOT_RENAME_PARTIAL;
2547 iRes = statusClientError;
2548 }
2549 }
2550 }
2551 else if(KDE::stat( sPart, &buff ) == 0)
2552 { // should a very small ".part" be deleted?
2553 int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
2554 if (buff.st_size < size)
2555 QFile::remove(sPart);
2556 }
2557 }
2558
2559 if (iRes == statusSuccess) {
2560 const QString mtimeStr = metaData("modified");
2561 if (!mtimeStr.isEmpty()) {
2562 QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
2563 if (dt.isValid()) {
2564 kDebug(7102) << "Updating modified timestamp to" << mtimeStr;
2565 struct utimbuf utbuf;
2566 utbuf.actime = buff.st_atime; // access time, unchanged
2567 utbuf.modtime = dt.toTime_t(); // modification time
2568 KDE::utime(sCopyFile, &utbuf);
2569 }
2570 }
2571 }
2572
2573 return iRes;
2574}
2575
2576Ftp::StatusCode Ftp::ftpSendMimeType(int& iError, const KUrl& url)
2577{
2578 // Emit proper mimetype for zero sized files. #323491
2579 if (m_size == 0) {
2580 mimeType(QLatin1String("application/x-zerosize"));
2581 return statusSuccess;
2582 }
2583
2584 const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : m_size);
2585 QByteArray buffer(totalSize, '\0');
2586
2587 while (true) {
2588 // Wait for content to be available...
2589 if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((readTimeout() * 1000))) {
2590 iError = ERR_COULD_NOT_READ;
2591 return statusServerError;
2592 }
2593
2594 const int bytesRead = m_data->peek(buffer.data(), totalSize);
2595
2596 // If we got a -1, it must be an error so return an error.
2597 if (bytesRead == -1) {
2598 iError = ERR_COULD_NOT_READ;
2599 return statusServerError;
2600 }
2601
2602 // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size
2603 // equal to the size we want, then break.
2604 if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) {
2605 break;
2606 }
2607 }
2608
2609 if (!buffer.isEmpty()) {
2610 KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), buffer);
2611 kDebug(7102) << "Emitting mimetype" << mime->name();
2612 mimeType( mime->name() ); // emit the mime type...
2613 }
2614
2615 return statusSuccess;
2616}
2617
2618void Ftp::proxyAuthentication(const QNetworkProxy& proxy, QAuthenticator* authenticator)
2619{
2620 Q_UNUSED(proxy);
2621 kDebug(7102) << "Authenticator received -- realm:" << authenticator->realm() << "user:"
2622 << authenticator->user();
2623
2624 AuthInfo info;
2625 info.url = m_proxyURL;
2626 info.realmValue = authenticator->realm();
2627 info.verifyPath = true; //### whatever
2628 info.username = authenticator->user();
2629
2630 const bool haveCachedCredentials = checkCachedAuthentication(info);
2631
2632 // if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
2633 // and it was not successful. see below and saveProxyAuthenticationForSocket().
2634 if (!haveCachedCredentials || m_socketProxyAuth) {
2635 // Save authentication info if the connection succeeds. We need to disconnect
2636 // this after saving the auth data (or an error) so we won't save garbage afterwards!
2637 connect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
2638 //### fillPromptInfo(&info);
2639 info.prompt = i18n("You need to supply a username and a password for "
2640 "the proxy server listed below before you are allowed "
2641 "to access any sites.");
2642 info.keepPassword = true;
2643 info.commentLabel = i18n("Proxy:");
2644 info.comment = i18n("<b>%1</b> at <b>%2</b>", info.realmValue, m_proxyURL.host());
2645 const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
2646 if (!dataEntered) {
2647 kDebug(7102) << "looks like the user canceled proxy authentication.";
2648 error(ERR_USER_CANCELED, m_proxyURL.host());
2649 return;
2650 }
2651 }
2652 authenticator->setUser(info.username);
2653 authenticator->setPassword(info.password);
2654 authenticator->setOption(QLatin1String("keepalive"), info.keepPassword);
2655
2656 if (m_socketProxyAuth) {
2657 *m_socketProxyAuth = *authenticator;
2658 } else {
2659 m_socketProxyAuth = new QAuthenticator(*authenticator);
2660 }
2661
2662 m_proxyURL.setUser(info.username);
2663 m_proxyURL.setPassword(info.password);
2664}
2665
2666void Ftp::saveProxyAuthentication()
2667{
2668 kDebug(7102);
2669 disconnect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
2670 Q_ASSERT(m_socketProxyAuth);
2671 if (m_socketProxyAuth) {
2672 kDebug(7102) << "-- realm:" << m_socketProxyAuth->realm() << "user:" << m_socketProxyAuth->user();
2673 KIO::AuthInfo a;
2674 a.verifyPath = true;
2675 a.url = m_proxyURL;
2676 a.realmValue = m_socketProxyAuth->realm();
2677 a.username = m_socketProxyAuth->user();
2678 a.password = m_socketProxyAuth->password();
2679 a.keepPassword = m_socketProxyAuth->option(QLatin1String("keepalive")).toBool();
2680 cacheAuthentication(a);
2681 }
2682 delete m_socketProxyAuth;
2683 m_socketProxyAuth = 0;
2684}
2685
2686void Ftp::fixupEntryName(FtpEntry* e)
2687{
2688 Q_ASSERT(e);
2689 if (e->type == S_IFDIR) {
2690 if (!ftpFolder(e->name, false)) {
2691 QString name (e->name.trimmed());
2692 if (ftpFolder(name, false)) {
2693 e->name = name;
2694 kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
2695 } else {
2696 int index = 0;
2697 while (e->name.at(index).isSpace()) {
2698 index++;
2699 name = e->name.mid(index);
2700 if (ftpFolder(name, false)) {
2701 kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
2702 e->name = name;
2703 break;
2704 }
2705 }
2706 }
2707 }
2708 } else {
2709 if (!ftpFileExists(e->name)) {
2710 QString name (e->name.trimmed());
2711 if (ftpFileExists(name)) {
2712 e->name = name;
2713 kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
2714 } else {
2715 int index = 0;
2716 while (e->name.at(index).isSpace()) {
2717 index++;
2718 name = e->name.mid(index);
2719 if (ftpFileExists(name)) {
2720 kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
2721 e->name = name;
2722 break;
2723 }
2724 }
2725 }
2726 }
2727 }
2728}
Ftp
Definition: ftp.h:54
Ftp::rename
virtual void rename(const KUrl &src, const KUrl &dst, KIO::JobFlags flags)
Definition: ftp.cpp:1191
Ftp::del
virtual void del(const KUrl &url, bool isfile)
Definition: ftp.cpp:1242
Ftp::~Ftp
virtual ~Ftp()
Definition: ftp.cpp:210
Ftp::chmod
virtual void chmod(const KUrl &url, int permissions)
Definition: ftp.cpp:1285
Ftp::put
virtual void put(const KUrl &url, int permissions, KIO::JobFlags flags)
Definition: ftp.cpp:2055
Ftp::setHost
virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
Definition: ftp.cpp:321
Ftp::mkdir
virtual void mkdir(const KUrl &url, int permissions)
Definition: ftp.cpp:1156
Ftp::listDir
virtual void listDir(const KUrl &url)
Definition: ftp.cpp:1524
Ftp::closeConnection
virtual void closeConnection()
Closes the connection.
Definition: ftp.cpp:299
Ftp::copy
virtual void copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags)
Handles the case that one side of the job is a local file.
Definition: ftp.cpp:2370
Ftp::openConnection
virtual void openConnection()
Connects to a ftp server and logs us in m_bLoggedOn is set to true if logging on was successful.
Definition: ftp.cpp:340
Ftp::Ftp
Ftp(const QByteArray &pool, const QByteArray &app)
Definition: ftp.cpp:196
Ftp::get
virtual void get(const KUrl &url)
Definition: ftp.cpp:1847
Ftp::stat
virtual void stat(const KUrl &url)
Definition: ftp.cpp:1373
Ftp::slave_status
virtual void slave_status()
Definition: ftp.cpp:1599
KComponentData
KConfigGroup::readEntry
QString readEntry(const char *key, const char *aDefault=0) const
KIO::AuthInfo
KIO::AuthInfo::realmValue
QString realmValue
KIO::AuthInfo::setModified
void setModified(bool flag)
KIO::AuthInfo::verifyPath
bool verifyPath
KIO::AuthInfo::keepPassword
bool keepPassword
KIO::AuthInfo::getExtraField
QVariant getExtraField(const QString &fieldName) const
KIO::AuthInfo::comment
QString comment
KIO::AuthInfo::setExtraField
void setExtraField(const QString &fieldName, const QVariant &value)
KIO::AuthInfo::url
KUrl url
KIO::AuthInfo::username
QString username
KIO::AuthInfo::password
QString password
KIO::AuthInfo::prompt
QString prompt
KIO::AuthInfo::commentLabel
QString commentLabel
KIO::SlaveBase
KIO::SlaveBase::mimeType
void mimeType(const QString &_type)
KIO::SlaveBase::metaData
QString metaData(const QString &key) const
KIO::SlaveBase::infoMessage
void infoMessage(const QString &msg)
KIO::SlaveBase::processedSize
void processedSize(KIO::filesize_t _bytes)
KIO::SlaveBase::mimetype
virtual void mimetype(const KUrl &url)
KIO::SlaveBase::finished
void finished()
KIO::SlaveBase::connectTimeout
int connectTimeout()
KIO::SlaveBase::slaveStatus
void slaveStatus(const QString &host, bool connected)
KIO::SlaveBase::cacheAuthentication
bool cacheAuthentication(const AuthInfo &info)
KIO::SlaveBase::dataReq
void dataReq()
KIO::SlaveBase::write
virtual void write(const QByteArray &data)
KIO::SlaveBase::redirection
void redirection(const KUrl &_url)
KIO::SlaveBase::error
void error(int _errid, const QString &_text)
KIO::SlaveBase::statEntry
void statEntry(const UDSEntry &_entry)
KIO::SlaveBase::openPasswordDialog
bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg=QString())
KIO::SlaveBase::connected
void connected()
KIO::SlaveBase::remoteEncoding
KRemoteEncoding * remoteEncoding()
KIO::SlaveBase::dispatchLoop
void dispatchLoop()
KIO::SlaveBase::readData
int readData(QByteArray &buffer)
KIO::SlaveBase::data
void data(const QByteArray &data)
KIO::SlaveBase::readTimeout
int readTimeout()
KIO::SlaveBase::listEntry
void listEntry(const UDSEntry &_entry, bool ready)
KIO::SlaveBase::totalSize
void totalSize(KIO::filesize_t _bytes)
KIO::SlaveBase::read
virtual void read(KIO::filesize_t size)
KIO::SlaveBase::canResume
void canResume()
KIO::SlaveBase::checkCachedAuthentication
bool checkCachedAuthentication(AuthInfo &info)
KIO::SlaveBase::config
KConfigGroup * config()
KIO::SlaveBase::close
virtual void close()
KIO::UDSEntry
KIO::UDSEntry::UDS_GROUP
UDS_GROUP
KIO::UDSEntry::UDS_LINK_DEST
UDS_LINK_DEST
KIO::UDSEntry::UDS_MIME_TYPE
UDS_MIME_TYPE
KIO::UDSEntry::UDS_FILE_TYPE
UDS_FILE_TYPE
KIO::UDSEntry::UDS_MODIFICATION_TIME
UDS_MODIFICATION_TIME
KIO::UDSEntry::UDS_SIZE
UDS_SIZE
KIO::UDSEntry::UDS_NAME
UDS_NAME
KIO::UDSEntry::UDS_GUESSED_MIME_TYPE
UDS_GUESSED_MIME_TYPE
KIO::UDSEntry::UDS_USER
UDS_USER
KIO::UDSEntry::UDS_ACCESS
UDS_ACCESS
KIO::UDSEntry::insert
void insert(uint field, const QString &value)
KIO::UDSEntry::clear
void clear()
KIO::UDSEntry::count
int count() const
KMimeType::defaultMimeType
static QString defaultMimeType()
KMimeType::findByNameAndContent
static Ptr findByNameAndContent(const QString &name, const QByteArray &data, mode_t mode=0, int *accuracy=0)
KMimeType::findByUrl
static Ptr findByUrl(const KUrl &url, mode_t mode=0, bool is_local_file=false, bool fast_mode=false, int *accuracy=0)
KRemoteEncoding::decode
QString decode(const QByteArray &name) const
KRemoteEncoding::encode
QByteArray encode(const KUrl &url) const
KSharedPtr
KUrl
KUrl::prettyUrl
QString prettyUrl(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::AppendTrailingSlash
AppendTrailingSlash
KUrl::url
QString url(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::path
QString path(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::setProtocol
void setProtocol(const QString &proto)
KUrl::directory
QString directory(const DirectoryOptions &options=IgnoreTrailingSlash) const
KUrl::isLocalFile
bool isLocalFile() const
KUrl::setPath
void setPath(const QString &path)
KUrl::setPass
void setPass(const QString &pass)
KUrl::fileName
QString fileName(const DirectoryOptions &options=IgnoreTrailingSlash) const
KUrl::toLocalFile
QString toLocalFile(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::setUser
void setUser(const QString &user)
QList
isdigit
#define isdigit(c)
isSocksProxy
static bool isSocksProxy()
Definition: ftp.cpp:111
FTP_LOGIN
#define FTP_LOGIN
Definition: ftp.cpp:73
FTP_PASSWD
#define FTP_PASSWD
Definition: ftp.cpp:74
kdemain
int kdemain(int argc, char **argv)
Definition: ftp.cpp:171
ftpCleanPath
static QString ftpCleanPath(const QString &path)
Definition: ftp.cpp:79
supportedProxyScheme
static bool supportedProxyScheme(const QString &scheme)
Definition: ftp.cpp:106
ftpModeFromPath
static char ftpModeFromPath(const QString &path, char defaultMode='\0')
Definition: ftp.cpp:90
charToLongLong
#define charToLongLong(a)
Definition: ftp.cpp:70
ftp.h
kDebug
#define kDebug
kWarning
#define kWarning
ioslave_defaults.h
DEFAULT_FTP_PORT
#define DEFAULT_FTP_PORT
readEntry
KAutostart::StartPhase readEntry(const KConfigGroup &group, const char *key, const KAutostart::StartPhase &aDefault)
kcomponentdata.h
kconfiggroup.h
kdebug.h
kglobal.h
klocale.h
i18n
QString i18n(const char *text)
kmimetype.h
kremoteencoding.h
ksocketfactory.h
KDE::utime
int utime(const QString &filename, struct utimbuf *buf)
KDE::open
int open(const QString &pathname, int flags, mode_t mode)
KDE::stat
int stat(const QString &path, KDE_struct_stat *buf)
KDE::rename
int rename(const QString &in, const QString &out)
KGlobal::locale
KLocale * locale()
KIO
KIO::WriteToFile
static int WriteToFile(int fd, const char *buf, size_t len)
This helper handles some special issues (blocking and interrupted system call) when writing to a file...
Definition: ftp.cpp:146
KIO::buffersizes
buffersizes
Definition: ftp.cpp:121
KIO::maximumIpcSize
@ maximumIpcSize
largest buffer size that should be used to transfer data between KIO slaves using the data() function
Definition: ftp.cpp:125
KIO::minimumMimeSize
@ minimumMimeSize
recommended size of a data block passed to findBufferFileType()
Definition: ftp.cpp:134
KIO::initialIpcSize
@ initialIpcSize
this is a reasonable value for an initial read() that a KIO slave can do to obtain data via a slow ne...
Definition: ftp.cpp:130
KIO::Resume
Resume
KIO::DefaultFlags
DefaultFlags
KIO::Overwrite
Overwrite
KIO::fileoffset_t
qlonglong fileoffset_t
KIO::filesize_t
qulonglong filesize_t
ERR_CANNOT_DELETE_PARTIAL
ERR_CANNOT_DELETE_PARTIAL
ERR_COULD_NOT_LISTEN
ERR_COULD_NOT_LISTEN
ERR_DOES_NOT_EXIST
ERR_DOES_NOT_EXIST
KIO::ERR_COULD_NOT_WRITE
ERR_COULD_NOT_WRITE
ERR_DIR_ALREADY_EXIST
ERR_DIR_ALREADY_EXIST
ERR_INTERNAL
ERR_INTERNAL
KIO::ERR_CONNECTION_BROKEN
ERR_CONNECTION_BROKEN
ERR_COULD_NOT_CONNECT
ERR_COULD_NOT_CONNECT
ERR_FILE_ALREADY_EXIST
ERR_FILE_ALREADY_EXIST
ERR_CANNOT_OPEN_FOR_READING
ERR_CANNOT_OPEN_FOR_READING
ERR_CANNOT_ENTER_DIRECTORY
ERR_CANNOT_ENTER_DIRECTORY
ERR_COULD_NOT_READ
ERR_COULD_NOT_READ
ERR_CANNOT_RESUME
ERR_CANNOT_RESUME
ERR_COULD_NOT_ACCEPT
ERR_COULD_NOT_ACCEPT
ERR_CYCLIC_LINK
ERR_CYCLIC_LINK
ERR_CANNOT_CHMOD
ERR_CANNOT_CHMOD
ERR_IS_FILE
ERR_IS_FILE
ERR_CANNOT_RENAME_PARTIAL
ERR_CANNOT_RENAME_PARTIAL
ERR_IS_DIRECTORY
ERR_IS_DIRECTORY
KIO::ERR_DISK_FULL
ERR_DISK_FULL
ERR_CANNOT_DELETE
ERR_CANNOT_DELETE
ERR_UNSUPPORTED_ACTION
ERR_UNSUPPORTED_ACTION
ERR_COULD_NOT_MKDIR
ERR_COULD_NOT_MKDIR
ERR_UNKNOWN_HOST
ERR_UNKNOWN_HOST
list
QStringList list(const QString &fileClass)
KSocketFactory::synchronousConnectToHost
QTcpSocket * synchronousConnectToHost(const QString &protocol, const QString &host, quint16 port, int msecs=30000, QObject *parent=0)
KSocketFactory::listen
QTcpServer * listen(const QString &protocol, const QHostAddress &address=QHostAddress::Any, quint16 port=0, QObject *parent=0)
name
const char * name(StandardAction id)
ok
KGuiItem ok()
slaveconfig.h
FtpEntry
Definition: ftp.h:38
FtpEntry::owner
QString owner
Definition: ftp.h:40
FtpEntry::group
QString group
Definition: ftp.h:41
FtpEntry::name
QString name
Definition: ftp.h:39
FtpEntry::date
time_t date
Definition: ftp.h:47
FtpEntry::access
mode_t access
Definition: ftp.h:46
FtpEntry::link
QString link
Definition: ftp.h:42
FtpEntry::type
mode_t type
Definition: ftp.h:45
FtpEntry::size
KIO::filesize_t size
Definition: ftp.h:44
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal