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

KIOSlave

ftp.cpp
Go to the documentation of this file.
00001 // -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; c-file-style: "stroustrup" -*-
00002 /*  This file is part of the KDE libraries
00003     Copyright (C) 2000-2006 David Faure <faure@kde.org>
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018     Boston, MA 02110-1301, USA.
00019 */
00020 
00021 /*
00022     Recommended reading explaining FTP details and quirks:
00023       http://cr.yp.to/ftp.html  (by D.J. Bernstein)
00024 
00025     RFC:
00026       RFC  959 "File Transfer Protocol (FTP)"
00027       RFC 1635 "How to Use Anonymous FTP"
00028       RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV)
00029 */
00030 
00031 
00032 #define  KIO_FTP_PRIVATE_INCLUDE
00033 #include "ftp.h"
00034 
00035 #ifdef HAVE_SYS_TIME_H
00036 #include <sys/time.h>
00037 #endif
00038 
00039 #if TIME_WITH_SYS_TIME
00040 #include <ctime>
00041 #endif
00042 
00043 #include <cctype>
00044 #include <cerrno>
00045 #include <cstdlib>
00046 #include <cstring>
00047 
00048 #include <QtCore/QCoreApplication>
00049 #include <QtCore/QDir>
00050 #include <QtNetwork/QHostAddress>
00051 #include <QtNetwork/QTcpSocket>
00052 #include <QtNetwork/QTcpServer>
00053 #include <QtNetwork/QAuthenticator>
00054 
00055 #include <kdebug.h>
00056 #include <kglobal.h>
00057 #include <klocale.h>
00058 #include <kcomponentdata.h>
00059 #include <kmimetype.h>
00060 #include <kio/ioslave_defaults.h>
00061 #include <kio/slaveconfig.h>
00062 #include <kremoteencoding.h>
00063 #include <ksocketfactory.h>
00064 #include <kde_file.h>
00065 #include <kconfiggroup.h>
00066 
00067 #ifdef HAVE_STRTOLL
00068   #define charToLongLong(a) strtoll(a, 0, 10)
00069 #else
00070   #define charToLongLong(a) strtol(a, 0, 10)
00071 #endif
00072 
00073 #define FTP_LOGIN   "anonymous"
00074 #define FTP_PASSWD  "anonymous@"
00075 
00076 //#undef  kDebug
00077 #define ENABLE_CAN_RESUME
00078 
00079 static QString ftpCleanPath(const QString& path)
00080 {
00081     if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) ||
00082         path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) ||
00083         path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) {
00084         return path.left((path.length() - qstrlen(";type=X")));
00085     }
00086 
00087     return path;
00088 }
00089 
00090 static char ftpModeFromPath(const QString& path, char defaultMode = '\0')
00091 {
00092     const int index = path.lastIndexOf(QLatin1String(";type="));
00093 
00094     if (index > -1 && (index+6) < path.size()) {
00095         const QChar mode = path.at(index+6);
00096         // kio_ftp supports only A (ASCII) and I(BINARY) modes.
00097         if (mode == QLatin1Char('A') || mode == QLatin1Char('a') ||
00098             mode == QLatin1Char('I') || mode == QLatin1Char('i')) {
00099             return mode.toUpper().toLatin1();
00100         }
00101     }
00102 
00103     return defaultMode;
00104 }
00105 
00106 static bool supportedProxyScheme(const QString& scheme)
00107 {
00108     return (scheme == QLatin1String("ftp") || scheme == QLatin1String("socks"));
00109 }
00110 
00111 static bool isSocksProxy()
00112 {
00113     return (QNetworkProxy::applicationProxy().type() == QNetworkProxy::Socks5Proxy);
00114 }
00115 
00116 
00117 // JPF: somebody should find a better solution for this or move this to KIO
00118 // JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
00119 namespace KIO {
00120     enum buffersizes
00121     {  
00125         maximumIpcSize = 32 * 1024,
00130         initialIpcSize =  2 * 1024,
00134         minimumMimeSize =     1024
00135     };
00136 
00137     // JPF: this helper was derived from write_all in file.cc (FileProtocol).
00138     static // JPF: in ftp.cc we make it static
00146    int WriteToFile(int fd, const char *buf, size_t len)
00147    {
00148       while (len > 0)
00149       {  // JPF: shouldn't there be a KDE_write?
00150          ssize_t written = write(fd, buf, len);
00151          if (written >= 0)
00152          {   buf += written;
00153              len -= written;
00154              continue;
00155          }
00156          switch(errno)
00157          {   case EINTR:   continue;
00158              case EPIPE:   return ERR_CONNECTION_BROKEN;
00159              case ENOSPC:  return ERR_DISK_FULL;
00160              default:      return ERR_COULD_NOT_WRITE;
00161          }
00162       }
00163       return 0;
00164    }
00165 }
00166 
00167 KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;
00168 
00169 using namespace KIO;
00170 
00171 extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
00172 {
00173   QCoreApplication app(argc, argv);
00174   KComponentData componentData( "kio_ftp", "kdelibs4" );
00175   ( void ) KGlobal::locale();
00176 
00177   kDebug(7102) << "Starting " << getpid();
00178 
00179   if (argc != 4)
00180   {
00181      fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n");
00182      exit(-1);
00183   }
00184 
00185   Ftp slave(argv[2], argv[3]);
00186   slave.dispatchLoop();
00187 
00188   kDebug(7102) << "Done";
00189   return 0;
00190 }
00191 
00192 //===============================================================================
00193 // Ftp
00194 //===============================================================================
00195 
00196 Ftp::Ftp( const QByteArray &pool, const QByteArray &app )
00197     : SlaveBase( "ftp", pool, app )
00198 {
00199   // init the socket data
00200   m_data = m_control = NULL;
00201   m_server = NULL;
00202   ftpCloseControlConnection();
00203 
00204   // init other members
00205   m_port = 0;
00206   m_socketProxyAuth = 0;
00207 }
00208 
00209 
00210 Ftp::~Ftp()
00211 {
00212   kDebug(7102);
00213   closeConnection();
00214 }
00215 
00219 void Ftp::ftpCloseDataConnection()
00220 {
00221     delete m_data;
00222     m_data = NULL;
00223     delete m_server;
00224     m_server = NULL;
00225 }
00226 
00231 void Ftp::ftpCloseControlConnection()
00232 {
00233   m_extControl = 0;
00234   delete m_control;
00235   m_control = NULL;
00236   m_cDataMode = 0;
00237   m_bLoggedOn = false;    // logon needs control connction
00238   m_bTextMode = false;
00239   m_bBusy = false;
00240 }
00241 
00246 const char* Ftp::ftpResponse(int iOffset)
00247 {
00248   Q_ASSERT(m_control != NULL);    // must have control connection socket
00249   const char *pTxt = m_lastControlLine.data();
00250 
00251   // read the next line ...
00252   if(iOffset < 0)
00253   {
00254     int  iMore = 0;
00255     m_iRespCode = 0;
00256 
00257     if (!pTxt) return 0; // avoid using a NULL when calling atoi.
00258 
00259     // If the server sends a multiline response starting with
00260     // "nnn-text" we loop here until a final "nnn text" line is
00261     // reached. Only data from the final line will be stored.
00262     do {
00263       while (!m_control->canReadLine() && m_control->waitForReadyRead((readTimeout() * 1000))) {}
00264       m_lastControlLine = m_control->readLine();
00265       pTxt = m_lastControlLine.data();
00266       int iCode  = atoi(pTxt);
00267       if (iMore == 0) {
00268           // first line
00269           kDebug(7102) << "    > " << pTxt;
00270           if(iCode >= 100) {
00271               m_iRespCode = iCode;
00272               if (pTxt[3] == '-') {
00273                   // marker for a multiple line response
00274                   iMore = iCode;
00275               }
00276           } else {
00277               kWarning(7102) << "Cannot parse valid code from line" << pTxt;
00278           }
00279       } else {
00280           // multi-line
00281           kDebug(7102) << "    > " << pTxt;
00282           if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') {
00283               iMore = 0;
00284           }
00285       }
00286     } while(iMore != 0);
00287     kDebug(7102) << "resp> " << pTxt;
00288 
00289     m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0;
00290   }
00291 
00292   // return text with offset ...
00293   while(iOffset-- > 0 && pTxt[0])
00294     pTxt++;
00295   return pTxt;
00296 }
00297 
00298 
00299 void Ftp::closeConnection()
00300 {
00301   if(m_control != NULL || m_data != NULL)
00302     kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy;
00303 
00304   if(m_bBusy)              // ftpCloseCommand not called
00305   {
00306     kWarning(7102) << "Abandoned data stream";
00307     ftpCloseDataConnection();
00308   }
00309 
00310   if(m_bLoggedOn)           // send quit
00311   {
00312     if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) )
00313       kWarning(7102) << "QUIT returned error: " << m_iRespCode;
00314   }
00315 
00316   // close the data and control connections ...
00317   ftpCloseDataConnection();
00318   ftpCloseControlConnection();
00319 }
00320 
00321 void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user,
00322                    const QString& _pass )
00323 {
00324   kDebug(7102) << _host << "port=" << _port << "user=" << _user;
00325 
00326   m_proxyURL.clear();
00327   m_proxyUrls = config()->readEntry("ProxyUrls", QStringList());
00328   kDebug(7102) << "proxy urls:" << m_proxyUrls;
00329 
00330   if ( m_host != _host || m_port != _port ||
00331        m_user != _user || m_pass != _pass )
00332     closeConnection();
00333 
00334   m_host = _host;
00335   m_port = _port;
00336   m_user = _user;
00337   m_pass = _pass;
00338 }
00339 
00340 void Ftp::openConnection()
00341 {
00342   ftpOpenConnection(loginExplicit);
00343 }
00344 
00345 bool Ftp::ftpOpenConnection (LoginMode loginMode)
00346 {
00347   // check for implicit login if we are already logged on ...
00348   if(loginMode == loginImplicit && m_bLoggedOn)
00349   {
00350     Q_ASSERT(m_control != NULL);    // must have control connection socket
00351     return true;
00352   }
00353 
00354   kDebug(7102) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]";
00355 
00356   infoMessage( i18n("Opening connection to host %1", m_host) );
00357 
00358   if ( m_host.isEmpty() )
00359   {
00360     error( ERR_UNKNOWN_HOST, QString() );
00361     return false;
00362   }
00363 
00364   Q_ASSERT( !m_bLoggedOn );
00365 
00366   m_initialPath.clear();
00367   m_currentPath.clear();
00368 
00369   if (!ftpOpenControlConnection() )
00370     return false;          // error emitted by ftpOpenControlConnection
00371   infoMessage( i18n("Connected to host %1", m_host) );
00372 
00373   bool userNameChanged = false;
00374   if(loginMode != loginDefered)
00375   {
00376     m_bLoggedOn = ftpLogin(&userNameChanged);
00377     if( !m_bLoggedOn )
00378       return false;       // error emitted by ftpLogin
00379   }
00380 
00381   m_bTextMode = config()->readEntry("textmode", false);
00382   connected();
00383 
00384   // Redirected due to credential change...
00385   if (userNameChanged && m_bLoggedOn)
00386   {
00387     KUrl realURL;
00388     realURL.setProtocol( "ftp" );
00389     if (m_user != FTP_LOGIN)
00390       realURL.setUser( m_user );
00391     if (m_pass != FTP_PASSWD)
00392       realURL.setPass( m_pass );
00393     realURL.setHost( m_host );
00394     if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00395       realURL.setPort( m_port );
00396     if ( m_initialPath.isEmpty() )
00397       m_initialPath = '/';
00398     realURL.setPath( m_initialPath );
00399     kDebug(7102) << "User name changed! Redirecting to" << realURL.prettyUrl();
00400     redirection( realURL );
00401     finished();
00402     return false;
00403   }
00404 
00405   return true;
00406 }
00407 
00408 
00414 bool Ftp::ftpOpenControlConnection()
00415 {
00416   if (m_proxyUrls.isEmpty())
00417       return ftpOpenControlConnection(m_host, m_port);
00418 
00419   int errorCode = 0;
00420   QString errorMessage;
00421 
00422   Q_FOREACH (const QString& proxyUrl, m_proxyUrls) {
00423     const KUrl url (proxyUrl);
00424     const QString scheme (url.protocol());
00425 
00426     if (!supportedProxyScheme(scheme)) {
00427       // TODO: Need a new error code to indicate unsupported URL scheme.
00428       errorCode = ERR_COULD_NOT_CONNECT;
00429       errorMessage = url.url();
00430       continue;
00431     }
00432 
00433     if (scheme == QLatin1String("socks")) {
00434       kDebug(7102) << "Connecting to SOCKS proxy @" << url;
00435       const int proxyPort = url.port();
00436       QNetworkProxy proxy (QNetworkProxy::Socks5Proxy, url.host(), (proxyPort == -1 ? 0 : proxyPort));
00437       QNetworkProxy::setApplicationProxy(proxy);
00438       if (ftpOpenControlConnection(m_host, m_port)) {
00439         return true;
00440       }
00441       QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
00442     } else {
00443       if (ftpOpenControlConnection(url.host(), url.port())) {
00444         m_proxyURL = url;
00445         return true;
00446       }
00447     }
00448   }
00449 
00450   if (errorCode) {
00451     error(errorCode, errorMessage);
00452   }
00453 
00454   return false;
00455 }
00456 
00457 bool Ftp::ftpOpenControlConnection( const QString &host, int port )
00458 {
00459   // implicitly close, then try to open a new connection ...
00460   closeConnection();
00461   QString sErrorMsg;
00462 
00463   // now connect to the server and read the login message ...
00464   if (port == 0)
00465     port = 21;                  // default FTP port
00466   m_control = KSocketFactory::synchronousConnectToHost(QLatin1String("ftp"), host, port, connectTimeout() * 1000);
00467   connect(m_control, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
00468           this, SLOT(proxyAuthentication(QNetworkProxy,QAuthenticator*)));
00469   int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT;
00470 
00471   // on connect success try to read the server message...
00472   if(iErrorCode == 0)
00473   {
00474     const char* psz = ftpResponse(-1);
00475     if(m_iRespType != 2)
00476     { // login not successful, do we have an message text?
00477       if(psz[0])
00478         sErrorMsg = i18n("%1.\n\nReason: %2", host, psz);
00479       iErrorCode = ERR_COULD_NOT_CONNECT;
00480     }
00481   }
00482   else
00483   {
00484     if (m_control->error() == QAbstractSocket::HostNotFoundError)
00485       iErrorCode = ERR_UNKNOWN_HOST;
00486 
00487     sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString());
00488   }
00489 
00490   // if there was a problem - report it ...
00491   if(iErrorCode == 0)             // OK, return success
00492     return true;
00493   closeConnection();              // clean-up on error
00494   error(iErrorCode, sErrorMsg);
00495   return false;
00496 }
00497 
00505 bool Ftp::ftpLogin(bool* userChanged)
00506 {
00507   infoMessage( i18n("Sending login information") );
00508 
00509   Q_ASSERT( !m_bLoggedOn );
00510 
00511   QString user (m_user);
00512   QString pass (m_pass);
00513 
00514   if ( config()->readEntry("EnableAutoLogin", false) )
00515   {
00516     QString au = config()->readEntry("autoLoginUser");
00517     if ( !au.isEmpty() )
00518     {
00519         user = au;
00520         pass = config()->readEntry("autoLoginPass");
00521     }
00522   }
00523 
00524   AuthInfo info;
00525   info.url.setProtocol( "ftp" );
00526   info.url.setHost( m_host );
00527   if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00528       info.url.setPort( m_port );
00529   if (!user.isEmpty())
00530       info.url.setUser(user);
00531 
00532   // Check for cached authentication first and fallback to
00533   // anonymous login when no stored credentials are found.
00534   if (!config()->readEntry("TryAnonymousLoginFirst", false) &&
00535       pass.isEmpty() && checkCachedAuthentication(info))
00536   {
00537       user = info.username;
00538       pass = info.password;
00539   }
00540 
00541   // Try anonymous login if both username/password
00542   // information is blank.
00543   if (user.isEmpty() && pass.isEmpty())
00544   {
00545       user = FTP_LOGIN;
00546       pass = FTP_PASSWD;
00547   }
00548 
00549   QByteArray tempbuf;
00550   QString lastServerResponse;
00551   int failedAuth = 0;
00552   bool promptForRetry = false;
00553 
00554   // Give the user the option to login anonymously...
00555   info.setExtraField(QLatin1String("anonymous"), false);
00556 
00557   do
00558   {
00559     // Check the cache and/or prompt user for password if 1st
00560     // login attempt failed OR the user supplied a login name,
00561     // but no password.
00562     if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) )
00563     {
00564       QString errorMsg;
00565       kDebug(7102) << "Prompting user for login info...";
00566 
00567       // Ask user if we should retry after when login fails!
00568       if( failedAuth > 0 && promptForRetry)
00569       {
00570         errorMsg = i18n("Message sent:\nLogin using username=%1 and "
00571                         "password=[hidden]\n\nServer replied:\n%2\n\n"
00572                         , user, lastServerResponse);
00573       }
00574 
00575       if ( user != FTP_LOGIN )
00576         info.username = user;
00577 
00578       info.prompt = i18n("You need to supply a username and a password "
00579                          "to access this site.");
00580       info.commentLabel = i18n( "Site:" );
00581       info.comment = i18n("<b>%1</b>",  m_host );
00582       info.keepPassword = true; // Prompt the user for persistence as well.
00583       info.setModified(false);  // Default the modified flag since we reuse authinfo.
00584 
00585       bool disablePassDlg = config()->readEntry( "DisablePassDlg", false );
00586       if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) )
00587       {
00588         error( ERR_USER_CANCELED, m_host );
00589         return false;
00590       }
00591       else
00592       {
00593         // User can decide go anonymous using checkbox
00594         if( info.getExtraField( "anonymous" ).toBool() )
00595         {
00596           user = FTP_LOGIN;
00597           pass = FTP_PASSWD;
00598         }
00599         else
00600         {
00601           user = info.username;
00602           pass = info.password;
00603         }        
00604         promptForRetry = true;
00605       }
00606     }
00607 
00608     tempbuf = "USER ";
00609     tempbuf += user.toLatin1();
00610     if ( m_proxyURL.isValid() )
00611     {
00612       tempbuf += '@';
00613       tempbuf += m_host.toLatin1();
00614       if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00615       {
00616         tempbuf += ':';
00617         tempbuf += QString::number(m_port).toLatin1();
00618       }
00619     }
00620 
00621     kDebug(7102) << "Sending Login name: " << tempbuf;
00622 
00623     bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00624     bool needPass = (m_iRespCode == 331);
00625     // Prompt user for login info if we do not
00626     // get back a "230" or "331".
00627     if ( !loggedIn && !needPass )
00628     {
00629       lastServerResponse = ftpResponse(0);
00630       kDebug(7102) << "Login failed: " << lastServerResponse;
00631       ++failedAuth;
00632       continue;  // Well we failed, prompt the user please!!
00633     }
00634 
00635     if( needPass )
00636     {
00637       tempbuf = "PASS ";
00638       tempbuf += pass.toLatin1();
00639       kDebug(7102) << "Sending Login password: " << "[protected]";
00640       loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00641     }
00642 
00643     if ( loggedIn )
00644     {
00645       // Make sure the user name changed flag is properly set.
00646       if (userChanged)
00647         *userChanged = (!m_user.isEmpty() && (m_user != user));
00648 
00649       // Do not cache the default login!!
00650       if( user != FTP_LOGIN && pass != FTP_PASSWD )
00651       {
00652         // Update the username in case it was changed during login.
00653         if (!m_user.isEmpty()) {
00654             info.url.setUser (user);
00655             m_user = user;
00656         }
00657 
00658         // Cache the password if the user requested it.
00659         if (info.keepPassword) {
00660             cacheAuthentication(info);
00661         }
00662       }
00663       failedAuth = -1;
00664     }
00665     else
00666     {
00667         // some servers don't let you login anymore
00668         // if you fail login once, so restart the connection here
00669         lastServerResponse = ftpResponse(0);
00670         if (!ftpOpenControlConnection())
00671         {
00672             return false;
00673         }
00674     }
00675   } while( ++failedAuth );
00676 
00677 
00678   kDebug(7102) << "Login OK";
00679   infoMessage( i18n("Login OK") );
00680 
00681   // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix:
00682   // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint
00683   if( ftpSendCmd("SYST") && (m_iRespType == 2) )
00684   {
00685     if( !qstrncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version
00686     {
00687       ftpSendCmd( "site dirstyle" );
00688       // Check if it was already in Unix style
00689       // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk>
00690       if( !qstrncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 ))
00691          //It was in Unix style already!
00692          ftpSendCmd( "site dirstyle" );
00693       // windows won't support chmod before KDE konquers their desktop...
00694       m_extControl |= chmodUnknown;
00695     }
00696   }
00697   else
00698     kWarning(7102) << "SYST failed";
00699 
00700   if ( config()->readEntry ("EnableAutoLoginMacro", false) )
00701     ftpAutoLoginMacro ();
00702 
00703   // Get the current working directory
00704   kDebug(7102) << "Searching for pwd";
00705   if( !ftpSendCmd("PWD") || (m_iRespType != 2) )
00706   {
00707     kDebug(7102) << "Couldn't issue pwd command";
00708     error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) ); // or anything better ?
00709     return false;
00710   }
00711 
00712   QString sTmp = remoteEncoding()->decode( ftpResponse(3) );
00713   int iBeg = sTmp.indexOf('"');
00714   int iEnd = sTmp.lastIndexOf('"');
00715   if(iBeg > 0 && iBeg < iEnd)
00716   {
00717     m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1);
00718     if(m_initialPath[0] != '/') m_initialPath.prepend('/');
00719     kDebug(7102) << "Initial path set to: " << m_initialPath;
00720     m_currentPath = m_initialPath;
00721   }
00722   return true;
00723 }
00724 
00725 void Ftp::ftpAutoLoginMacro ()
00726 {
00727   QString macro = metaData( "autoLoginMacro" );
00728 
00729   if ( macro.isEmpty() )
00730     return;
00731 
00732   const QStringList list = macro.split('\n',QString::SkipEmptyParts);
00733 
00734   for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
00735   {
00736     if ( (*it).startsWith(QLatin1String("init")) )
00737     {
00738       const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
00739       it = list2.begin();
00740       ++it;  // ignore the macro name
00741 
00742       for( ; it != list2.end() ; ++it )
00743       {
00744         // TODO: Add support for arbitrary commands
00745         // besides simply changing directory!!
00746         if ( (*it).startsWith( QLatin1String("cwd") ) )
00747           ftpFolder( (*it).mid(4), false );
00748       }
00749 
00750       break;
00751     }
00752   }
00753 }
00754 
00755 
00765 bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries )
00766 {
00767   Q_ASSERT(m_control != NULL);    // must have control connection socket
00768 
00769   if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1)
00770   {
00771     kWarning(7102) << "Invalid command received (contains CR or LF):"
00772                     << cmd.data();
00773     error( ERR_UNSUPPORTED_ACTION, m_host );
00774     return false;
00775   }
00776 
00777   // Don't print out the password...
00778   bool isPassCmd = (cmd.left(4).toLower() == "pass");
00779   if ( !isPassCmd )
00780     kDebug(7102) << "send> " << cmd.data();
00781   else
00782     kDebug(7102) << "send> pass [protected]";
00783 
00784   // Send the message...
00785   QByteArray buf = cmd;
00786   buf += "\r\n";      // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html
00787   int num = m_control->write(buf);
00788   while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {}
00789 
00790   // If we were able to successfully send the command, then we will
00791   // attempt to read the response. Otherwise, take action to re-attempt
00792   // the login based on the maximum number of retries specified...
00793   if( num > 0 )
00794     ftpResponse(-1);
00795   else
00796   {
00797     m_iRespType = m_iRespCode = 0;
00798   }
00799 
00800   // If respCh is NULL or the response is 421 (Timed-out), we try to re-send
00801   // the command based on the value of maxretries.
00802   if( (m_iRespType <= 0) || (m_iRespCode == 421) )
00803   {
00804     // We have not yet logged on...
00805     if (!m_bLoggedOn)
00806     {
00807       // The command was sent from the ftpLogin function, i.e. we are actually
00808       // attempting to login in. NOTE: If we already sent the username, we
00809       // return false and let the user decide whether (s)he wants to start from
00810       // the beginning...
00811       if (maxretries > 0 && !isPassCmd)
00812       {
00813         closeConnection ();
00814         if( ftpOpenConnection(loginDefered) )
00815           ftpSendCmd ( cmd, maxretries - 1 );
00816       }
00817 
00818       return false;
00819     }
00820     else
00821     {
00822       if ( maxretries < 1 )
00823         return false;
00824       else
00825       {
00826         kDebug(7102) << "Was not able to communicate with " << m_host
00827                       << "Attempting to re-establish connection.";
00828 
00829         closeConnection(); // Close the old connection...
00830         openConnection();  // Attempt to re-establish a new connection...
00831 
00832         if (!m_bLoggedOn)
00833         {
00834           if (m_control != NULL)  // if openConnection succeeded ...
00835           {
00836             kDebug(7102) << "Login failure, aborting";
00837             error (ERR_COULD_NOT_LOGIN, m_host);
00838             closeConnection ();
00839           }
00840           return false;
00841         }
00842 
00843         kDebug(7102) << "Logged back in, re-issuing command";
00844 
00845         // If we were able to login, resend the command...
00846         if (maxretries)
00847           maxretries--;
00848 
00849         return ftpSendCmd( cmd, maxretries );
00850       }
00851     }
00852   }
00853 
00854   return true;
00855 }
00856 
00857 /*
00858  * ftpOpenPASVDataConnection - set up data connection, using PASV mode
00859  *
00860  * return 0 if successful, ERR_INTERNAL otherwise
00861  * doesn't set error message, since non-pasv mode will always be tried if
00862  * this one fails
00863  */
00864 int Ftp::ftpOpenPASVDataConnection()
00865 {
00866   Q_ASSERT(m_control != NULL);    // must have control connection socket
00867   Q_ASSERT(m_data == NULL);       // ... but no data connection
00868 
00869   // Check that we can do PASV
00870   QHostAddress address = m_control->peerAddress();
00871   if (address.protocol() != QAbstractSocket::IPv4Protocol)
00872     return ERR_INTERNAL;       // no PASV for non-PF_INET connections
00873 
00874  if (m_extControl & pasvUnknown)
00875     return ERR_INTERNAL;       // already tried and got "unknown command"
00876 
00877   m_bPasv = true;
00878 
00879   /* Let's PASsiVe*/
00880   if( !ftpSendCmd("PASV") || (m_iRespType != 2) )
00881   {
00882     kDebug(7102) << "PASV attempt failed";
00883     // unknown command?
00884     if( m_iRespType == 5 )
00885     {
00886         kDebug(7102) << "disabling use of PASV";
00887         m_extControl |= pasvUnknown;
00888     }
00889     return ERR_INTERNAL;
00890   }
00891 
00892   // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)'
00893   // but anonftpd gives '227 =160,39,200,55,6,245'
00894   int i[6];
00895   const char *start = strchr(ftpResponse(3), '(');
00896   if ( !start )
00897     start = strchr(ftpResponse(3), '=');
00898   if ( !start ||
00899        ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
00900          sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
00901   {
00902     kError(7102) << "parsing IP and port numbers failed. String parsed: " << start;
00903     return ERR_INTERNAL;
00904   }
00905 
00906   // we ignore the host part on purpose for two reasons
00907   // a) it might be wrong anyway
00908   // b) it would make us being suceptible to a port scanning attack
00909 
00910   // now connect the data socket ...
00911   quint16 port = i[4] << 8 | i[5];
00912   const QString host = (isSocksProxy() ? m_host : address.toString());
00913   m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, port, connectTimeout() * 1000);
00914 
00915   return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL;
00916 }
00917 
00918 /*
00919  * ftpOpenEPSVDataConnection - opens a data connection via EPSV
00920  */
00921 int Ftp::ftpOpenEPSVDataConnection()
00922 {
00923   Q_ASSERT(m_control != NULL);    // must have control connection socket
00924   Q_ASSERT(m_data == NULL);       // ... but no data connection
00925 
00926   QHostAddress address = m_control->peerAddress();
00927   int portnum;
00928 
00929   if (m_extControl & epsvUnknown)
00930     return ERR_INTERNAL;
00931 
00932   m_bPasv = true;
00933   if( !ftpSendCmd("EPSV") || (m_iRespType != 2) )
00934   {
00935     // unknown command?
00936     if( m_iRespType == 5 )
00937     {
00938        kDebug(7102) << "disabling use of EPSV";
00939        m_extControl |= epsvUnknown;
00940     }
00941     return ERR_INTERNAL;
00942   }
00943 
00944   const char *start = strchr(ftpResponse(3), '|');
00945   if ( !start || sscanf(start, "|||%d|", &portnum) != 1)
00946     return ERR_INTERNAL;
00947 
00948   const QString host = (isSocksProxy() ? m_host : address.toString());
00949   m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, portnum, connectTimeout() * 1000);
00950   return m_data->isOpen() ? 0 : ERR_INTERNAL;
00951 }
00952 
00953 /*
00954  * ftpOpenDataConnection - set up data connection
00955  *
00956  * The routine calls several ftpOpenXxxxConnection() helpers to find
00957  * the best connection mode. If a helper cannot connect if returns
00958  * ERR_INTERNAL - so this is not really an error! All other error
00959  * codes are treated as fatal, e.g. they are passed back to the caller
00960  * who is responsible for calling error(). ftpOpenPortDataConnection
00961  * can be called as last try and it does never return ERR_INTERNAL.
00962  *
00963  * @return 0 if successful, err code otherwise
00964  */
00965 int Ftp::ftpOpenDataConnection()
00966 {
00967   // make sure that we are logged on and have no data connection...
00968   Q_ASSERT( m_bLoggedOn );
00969   ftpCloseDataConnection();
00970 
00971   int  iErrCode = 0;
00972   int  iErrCodePASV = 0;  // Remember error code from PASV
00973 
00974   // First try passive (EPSV & PASV) modes
00975   if( !config()->readEntry("DisablePassiveMode", false) )
00976   {
00977     iErrCode = ftpOpenPASVDataConnection();
00978     if(iErrCode == 0)
00979       return 0; // success
00980     iErrCodePASV = iErrCode;
00981     ftpCloseDataConnection();
00982 
00983     if( !config()->readEntry("DisableEPSV", false) )
00984     {
00985       iErrCode = ftpOpenEPSVDataConnection();
00986       if(iErrCode == 0)
00987         return 0; // success
00988       ftpCloseDataConnection();
00989     }
00990 
00991     // if we sent EPSV ALL already and it was accepted, then we can't
00992     // use active connections any more
00993     if (m_extControl & epsvAllSent)
00994       return iErrCodePASV ? iErrCodePASV : iErrCode;
00995   }
00996 
00997   // fall back to port mode
00998   iErrCode = ftpOpenPortDataConnection();
00999   if(iErrCode == 0)
01000     return 0; // success
01001 
01002   ftpCloseDataConnection();
01003   // prefer to return the error code from PASV if any, since that's what should have worked in the first place
01004   return iErrCodePASV ? iErrCodePASV : iErrCode;
01005 }
01006 
01007 /*
01008  * ftpOpenPortDataConnection - set up data connection
01009  *
01010  * @return 0 if successful, err code otherwise (but never ERR_INTERNAL
01011  *         because this is the last connection mode that is tried)
01012  */
01013 int Ftp::ftpOpenPortDataConnection()
01014 {
01015   Q_ASSERT(m_control != NULL);    // must have control connection socket
01016   Q_ASSERT(m_data == NULL);       // ... but no data connection
01017 
01018   m_bPasv = false;
01019   if (m_extControl & eprtUnknown)
01020     return ERR_INTERNAL;
01021 
01022   if (!m_server)
01023     m_server = KSocketFactory::listen("ftp-data");
01024 
01025   if (!m_server->isListening()) {
01026     delete m_server;
01027     m_server = NULL;
01028     return ERR_COULD_NOT_LISTEN;
01029   }
01030 
01031   m_server->setMaxPendingConnections(1);
01032 
01033   QString command;
01034   QHostAddress localAddress = m_control->localAddress();
01035   if (localAddress.protocol() == QAbstractSocket::IPv4Protocol)
01036   {
01037     struct
01038     {
01039       quint32 ip4;
01040       quint16 port;
01041     } data;
01042     data.ip4 = localAddress.toIPv4Address();
01043     data.port = m_server->serverPort();
01044 
01045     unsigned char *pData = reinterpret_cast<unsigned char*>(&data);
01046     command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]);
01047   }
01048   else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol)
01049   {
01050     command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort());
01051   }
01052 
01053   if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) )
01054   {
01055     return 0;
01056   }
01057 
01058   delete m_server;
01059   m_server = NULL;
01060   return ERR_INTERNAL;
01061 }
01062 
01063 bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
01064                           int errorcode, KIO::fileoffset_t _offset )
01065 {
01066   int errCode = 0;
01067   if( !ftpDataMode(ftpModeFromPath(_path, _mode)) )
01068     errCode = ERR_COULD_NOT_CONNECT;
01069   else
01070     errCode = ftpOpenDataConnection();
01071 
01072   if(errCode != 0)
01073   {
01074     error(errCode, m_host);
01075     return false;
01076   }
01077 
01078   if ( _offset > 0 ) {
01079     // send rest command if offset > 0, this applies to retr and stor commands
01080     char buf[100];
01081     sprintf(buf, "rest %lld", _offset);
01082     if ( !ftpSendCmd( buf ) )
01083        return false;
01084     if( m_iRespType != 3 )
01085     {
01086       error( ERR_CANNOT_RESUME, _path ); // should never happen
01087       return false;
01088     }
01089   }
01090 
01091   QByteArray tmp = _command;
01092   QString errormessage;
01093 
01094   if ( !_path.isEmpty() ) {
01095     tmp += ' ';
01096     tmp += remoteEncoding()->encode(ftpCleanPath(_path));
01097   }
01098 
01099   if( !ftpSendCmd( tmp ) || (m_iRespType != 1) )
01100   {
01101     if( _offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4) )
01102       errorcode = ERR_CANNOT_RESUME;
01103     // The error here depends on the command
01104     errormessage = _path;
01105   }
01106 
01107   else
01108   {
01109     // Only now we know for sure that we can resume
01110     if ( _offset > 0 && qstrcmp(_command, "retr") == 0 )
01111       canResume();
01112 
01113     if(m_server && !m_data) {
01114       kDebug(7102) << "waiting for connection from remote.";
01115       m_server->waitForNewConnection(connectTimeout() * 1000);
01116       m_data = m_server->nextPendingConnection();
01117     }
01118 
01119     if(m_data) {
01120       kDebug(7102) << "connected with remote.";
01121       m_bBusy = true;              // cleared in ftpCloseCommand
01122       return true;
01123     }
01124 
01125     kDebug(7102) << "no connection received from remote.";
01126     errorcode=ERR_COULD_NOT_ACCEPT;
01127     errormessage=m_host;
01128     return false;
01129   }
01130 
01131   error(errorcode, errormessage);
01132   return false;
01133 }
01134 
01135 
01136 bool Ftp::ftpCloseCommand()
01137 {
01138   // first close data sockets (if opened), then read response that
01139   // we got for whatever was used in ftpOpenCommand ( should be 226 )
01140   ftpCloseDataConnection();
01141 
01142   if(!m_bBusy)
01143     return true;
01144 
01145   kDebug(7102) << "ftpCloseCommand: reading command result";
01146   m_bBusy = false;
01147 
01148   if(!ftpResponse(-1) || (m_iRespType != 2) )
01149   {
01150     kDebug(7102) << "ftpCloseCommand: no transfer complete message";
01151     return false;
01152   }
01153   return true;
01154 }
01155 
01156 void Ftp::mkdir( const KUrl & url, int permissions )
01157 {
01158   if( !ftpOpenConnection(loginImplicit) )
01159         return;
01160 
01161   const QByteArray encodedPath (remoteEncoding()->encode(url));
01162   const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size());
01163 
01164   if( !ftpSendCmd( (QByteArray ("mkd ") + encodedPath) ) || (m_iRespType != 2) )
01165   {
01166     QString currentPath( m_currentPath );
01167 
01168     // Check whether or not mkdir failed because
01169     // the directory already exists...
01170     if( ftpFolder( path, false ) )
01171     {
01172       error( ERR_DIR_ALREADY_EXIST, path );
01173       // Change the directory back to what it was...
01174       (void) ftpFolder( currentPath, false );
01175       return;
01176     }
01177 
01178     error( ERR_COULD_NOT_MKDIR, path );
01179     return;
01180   }
01181 
01182   if ( permissions != -1 )
01183   {
01184     // chmod the dir we just created, ignoring errors.
01185     (void) ftpChmod( path, permissions );
01186   }
01187 
01188   finished();
01189 }
01190 
01191 void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags )
01192 {
01193   if( !ftpOpenConnection(loginImplicit) )
01194         return;
01195 
01196   // The actual functionality is in ftpRename because put needs it
01197   if ( ftpRename( src.path(), dst.path(), flags ) )
01198     finished();
01199 }
01200 
01201 bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags)
01202 {
01203     Q_ASSERT(m_bLoggedOn);
01204 
01205     // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793).
01206     if (!(jobFlags & KIO::Overwrite)) {
01207         if (ftpFileExists(dst)) {
01208             error(ERR_FILE_ALREADY_EXIST, dst);
01209             return false;
01210         }
01211     }
01212 
01213     if (ftpFolder(dst, false)) {
01214         error(ERR_DIR_ALREADY_EXIST, dst);
01215         return false;
01216     }
01217 
01218     // CD into parent folder
01219     const int pos = src.lastIndexOf('/');
01220     if (pos > 0) {
01221         if(!ftpFolder(src.left(pos+1), false))
01222             return false;
01223     }
01224 
01225     QByteArray from_cmd = "RNFR ";
01226     from_cmd += remoteEncoding()->encode(src.mid(pos+1));
01227     if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) {
01228         error( ERR_CANNOT_RENAME, src );
01229         return false;
01230     }
01231 
01232     QByteArray to_cmd = "RNTO ";
01233     to_cmd += remoteEncoding()->encode(dst);
01234     if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) {
01235         error( ERR_CANNOT_RENAME, src );
01236         return false;
01237     }
01238 
01239     return true;
01240 }
01241 
01242 void Ftp::del( const KUrl& url, bool isfile )
01243 {
01244   if( !ftpOpenConnection(loginImplicit) )
01245         return;
01246 
01247   // When deleting a directory, we must exit from it first
01248   // The last command probably went into it (to stat it)
01249   if ( !isfile )
01250     ftpFolder(remoteEncoding()->directory(url), false); // ignore errors
01251 
01252   QByteArray cmd = isfile ? "DELE " : "RMD ";
01253   cmd += remoteEncoding()->encode(url);
01254 
01255   if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01256     error( ERR_CANNOT_DELETE, url.path() );
01257   else
01258     finished();
01259 }
01260 
01261 bool Ftp::ftpChmod( const QString & path, int permissions )
01262 {
01263   Q_ASSERT( m_bLoggedOn );
01264 
01265   if(m_extControl & chmodUnknown)      // previous errors?
01266     return false;
01267 
01268   // we need to do bit AND 777 to get permissions, in case
01269   // we were sent a full mode (unlikely)
01270   QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 /*octal*/ ) + ' ';
01271   cmd += path;
01272 
01273   ftpSendCmd(remoteEncoding()->encode(cmd));
01274   if(m_iRespType == 2)
01275      return true;
01276 
01277   if(m_iRespCode == 500)
01278   {
01279     m_extControl |= chmodUnknown;
01280     kDebug(7102) << "ftpChmod: CHMOD not supported - disabling";
01281   }
01282   return false;
01283 }
01284 
01285 void Ftp::chmod( const KUrl & url, int permissions )
01286 {
01287   if( !ftpOpenConnection(loginImplicit) )
01288         return;
01289 
01290   if ( !ftpChmod( url.path(), permissions ) )
01291     error( ERR_CANNOT_CHMOD, url.path() );
01292   else
01293     finished();
01294 }
01295 
01296 void Ftp::ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, UDSEntry& entry, bool isDir )
01297 {
01298   Q_ASSERT(entry.count() == 0); // by contract :-)
01299 
01300   entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01301   entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size );
01302   entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date );
01303   entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access );
01304   entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner );
01305   if ( !ftpEnt.group.isEmpty() )
01306   {
01307     entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group );
01308   }
01309 
01310   if ( !ftpEnt.link.isEmpty() )
01311   {
01312     entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link );
01313 
01314     KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) );
01315     // Links on ftp sites are often links to dirs, and we have no way to check
01316     // that. Let's do like Netscape : assume dirs generally.
01317     // But we do this only when the mimetype can't be known from the filename.
01318     // --> we do better than Netscape :-)
01319     if ( mime->name() == KMimeType::defaultMimeType() )
01320     {
01321       kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename;
01322       entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) );
01323       isDir = true;
01324     }
01325   }
01326 
01327   entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type );
01328   // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime);
01329   // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime);
01330 }
01331 
01332 
01333 void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir )
01334 {
01335     UDSEntry entry;
01336 
01337 
01338     entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01339     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG );
01340     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01341     if (isDir) {
01342       entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
01343     }
01344     // No details about size, ownership, group, etc.
01345 
01346     statEntry(entry);
01347     finished();
01348 }
01349 
01350 void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename )
01351 {
01352     // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source")
01353     // When e.g. uploading a file, we still need stat() to return "not found"
01354     // when the file doesn't exist.
01355     QString statSide = metaData("statSide");
01356     kDebug(7102) << "statSide=" << statSide;
01357     if ( statSide == "source" )
01358     {
01359         kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing";
01360         // MS Server is incapable of handling "list <blah>" in a case insensitive way
01361         // But "retr <blah>" works. So lie in stat(), to get going...
01362         //
01363         // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run
01364         // where listing permissions are denied, but downloading is still possible.
01365         ftpShortStatAnswer( filename, false /*file, not dir*/ );
01366 
01367         return;
01368     }
01369 
01370     error( ERR_DOES_NOT_EXIST, path );
01371 }
01372 
01373 void Ftp::stat(const KUrl &url)
01374 {
01375   kDebug(7102) << "path=" << url.path();
01376   if( !ftpOpenConnection(loginImplicit) )
01377         return;
01378 
01379   const QString path = ftpCleanPath( QDir::cleanPath( url.path() ) );
01380   kDebug(7102) << "cleaned path=" << path;
01381 
01382   // We can't stat root, but we know it's a dir.
01383   if( path.isEmpty() || path == "/" )
01384   {
01385     UDSEntry entry;
01386     //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) );
01387     entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) );
01388     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01389     entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
01390     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01391     entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) );
01392     entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) );
01393     // no size
01394 
01395     statEntry( entry );
01396     finished();
01397     return;
01398   }
01399 
01400   KUrl tempurl( url );
01401   tempurl.setPath( path ); // take the clean one
01402   QString listarg; // = tempurl.directory(KUrl::ObeyTrailingSlash);
01403   QString parentDir;
01404   QString filename = tempurl.fileName();
01405   Q_ASSERT(!filename.isEmpty());
01406   QString search = filename;
01407 
01408   // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info)
01409   // if it doesn't work, it's a file (and then we'll use dir filename)
01410   bool isDir = ftpFolder(path, false);
01411 
01412   // if we're only interested in "file or directory", we should stop here
01413   QString sDetails = metaData("details");
01414   int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
01415   kDebug(7102) << "details=" << details;
01416   if ( details == 0 )
01417   {
01418      if ( !isDir && !ftpFileExists(path) ) // ok, not a dir -> is it a file ?
01419      {  // no -> it doesn't exist at all
01420         ftpStatAnswerNotFound( path, filename );
01421         return;
01422      }
01423      ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done
01424      return;
01425   }
01426 
01427   if (!isDir)
01428   {
01429     // It is a file or it doesn't exist, try going to parent directory
01430     parentDir = tempurl.directory(KUrl::AppendTrailingSlash);
01431     // With files we can do "LIST <filename>" to avoid listing the whole dir
01432     listarg = filename;
01433   }
01434   else
01435   {
01436     // --- New implementation:
01437     // Don't list the parent dir. Too slow, might not show it, etc.
01438     // Just return that it's a dir.
01439     UDSEntry entry;
01440     entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01441     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01442     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01443     // No clue about size, ownership, group, etc.
01444 
01445     statEntry(entry);
01446     finished();
01447     return;
01448   }
01449 
01450   // Now cwd the parent dir, to prepare for listing
01451   if( !ftpFolder(parentDir, true) )
01452     return;
01453 
01454   if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) )
01455   {
01456     kError(7102) << "COULD NOT LIST";
01457     return;
01458   }
01459   kDebug(7102) << "Starting of list was ok";
01460 
01461   Q_ASSERT( !search.isEmpty() && search != "/" );
01462 
01463   bool bFound = false;
01464   KUrl linkURL;
01465   FtpEntry ftpEnt;
01466   QList<FtpEntry> ftpValidateEntList;
01467   while (ftpReadDir(ftpEnt)) {
01468     if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) {
01469       ftpValidateEntList.append(ftpEnt);
01470       continue;
01471     }
01472 
01473     // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at)
01474     // return only the filename when doing "dir /full/path/to/file"
01475     if (!bFound) {
01476       bFound = maybeEmitStatEntry(ftpEnt, search, filename, isDir);
01477     }
01478     // kDebug(7102) << ftpEnt.name;
01479   }
01480 
01481   for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
01482     FtpEntry& ftpEnt = ftpValidateEntList[i];
01483     fixupEntryName(&ftpEnt);
01484     if (maybeEmitStatEntry(ftpEnt, search, filename, isDir)) {
01485       break;
01486     }
01487   }
01488 
01489   ftpCloseCommand();        // closes the data connection only
01490 
01491   if ( !bFound )
01492   {
01493     ftpStatAnswerNotFound( path, filename );
01494     return;
01495   }
01496 
01497   if ( !linkURL.isEmpty() )
01498   {
01499       if ( linkURL == url || linkURL == tempurl )
01500       {
01501           error( ERR_CYCLIC_LINK, linkURL.prettyUrl() );
01502           return;
01503       }
01504       Ftp::stat( linkURL );
01505       return;
01506   }
01507 
01508   kDebug(7102) << "stat : finished successfully";
01509   finished();
01510 }
01511 
01512 bool Ftp::maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir)
01513 {
01514     if ((search == ftpEnt.name || filename == ftpEnt.name) && !filename.isEmpty()) {
01515         UDSEntry entry;
01516         ftpCreateUDSEntry( filename, ftpEnt, entry, isDir );
01517         statEntry( entry );
01518         return true;
01519     }
01520 
01521     return false;
01522 }
01523 
01524 void Ftp::listDir( const KUrl &url )
01525 {
01526   kDebug(7102) << url;
01527   if( !ftpOpenConnection(loginImplicit) )
01528         return;
01529 
01530   // No path specified ?
01531   QString path = url.path();
01532   if ( path.isEmpty() )
01533   {
01534     KUrl realURL;
01535     realURL.setProtocol( "ftp" );
01536     realURL.setUser( m_user );
01537     realURL.setPass( m_pass );
01538     realURL.setHost( m_host );
01539     if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
01540         realURL.setPort( m_port );
01541     if ( m_initialPath.isEmpty() )
01542         m_initialPath = '/';
01543     realURL.setPath( m_initialPath );
01544     kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl();
01545     redirection( realURL );
01546     finished();
01547     return;
01548   }
01549 
01550     kDebug(7102) << "hunting for path" << path;
01551 
01552     if (!ftpOpenDir(path)) {
01553         if (ftpFileExists(path)) {
01554             error(ERR_IS_FILE, path);
01555         } else {
01556             // not sure which to emit
01557             //error( ERR_DOES_NOT_EXIST, path );
01558             error( ERR_CANNOT_ENTER_DIRECTORY, path );
01559         }
01560         return;
01561     }
01562 
01563   UDSEntry entry;
01564   FtpEntry  ftpEnt;
01565   QList<FtpEntry> ftpValidateEntList;
01566   while( ftpReadDir(ftpEnt) )
01567   {
01568     //kDebug(7102) << ftpEnt.name;
01569     //Q_ASSERT( !ftpEnt.name.isEmpty() );
01570     if (!ftpEnt.name.isEmpty()) {
01571       if (ftpEnt.name.at(0).isSpace()) {
01572         ftpValidateEntList.append(ftpEnt);
01573         continue;
01574       }
01575 
01576       //if ( S_ISDIR( (mode_t)ftpEnt.type ) )
01577       //   kDebug(7102) << "is a dir";
01578       //if ( !ftpEnt.link.isEmpty() )
01579       //   kDebug(7102) << "is a link to " << ftpEnt.link;
01580       ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
01581       listEntry( entry, false );
01582       entry.clear();
01583     }
01584   }
01585 
01586   for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
01587     FtpEntry& ftpEnt = ftpValidateEntList[i];
01588     fixupEntryName(&ftpEnt);
01589     ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
01590     listEntry( entry, false );
01591     entry.clear();
01592   }
01593 
01594   listEntry( entry, true ); // ready
01595   ftpCloseCommand();        // closes the data connection only
01596   finished();
01597 }
01598 
01599 void Ftp::slave_status()
01600 {
01601   kDebug(7102) << "Got slave_status host = " << (!m_host.toAscii().isEmpty() ? m_host.toAscii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]";
01602   slaveStatus( m_host, m_bLoggedOn );
01603 }
01604 
01605 bool Ftp::ftpOpenDir( const QString & path )
01606 {
01607   //QString path( _url.path(KUrl::RemoveTrailingSlash) );
01608 
01609   // We try to change to this directory first to see whether it really is a directory.
01610   // (And also to follow symlinks)
01611   QString tmp = path.isEmpty() ? QString("/") : path;
01612 
01613   // We get '550', whether it's a file or doesn't exist...
01614   if( !ftpFolder(tmp, false) )
01615       return false;
01616 
01617   // Don't use the path in the list command:
01618   // We changed into this directory anyway - so it's enough just to send "list".
01619   // We use '-a' because the application MAY be interested in dot files.
01620   // The only way to really know would be to have a metadata flag for this...
01621   // Since some windows ftp server seems not to support the -a argument, we use a fallback here.
01622   // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com)
01623   if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01624   {
01625     if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01626     {
01627       kWarning(7102) << "Can't open for listing";
01628       return false;
01629     }
01630   }
01631   kDebug(7102) << "Starting of list was ok";
01632   return true;
01633 }
01634 
01635 bool Ftp::ftpReadDir(FtpEntry& de)
01636 {
01637   Q_ASSERT(m_data != NULL);
01638 
01639   // get a line from the data connecetion ...
01640   while( true )
01641   {
01642     while (!m_data->canReadLine() && m_data->waitForReadyRead((readTimeout() * 1000))) {}
01643     QByteArray data = m_data->readLine();
01644     if (data.size() == 0)
01645       break;
01646 
01647     const char* buffer = data.data();
01648     kDebug(7102) << "dir > " << buffer;
01649 
01650     //Normally the listing looks like
01651     // -rw-r--r--   1 dfaure   dfaure        102 Nov  9 12:30 log
01652     // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442)
01653     // d [RWCEAFMS] Admin                     512 Oct 13  2004 PSI
01654 
01655     // we should always get the following 5 fields ...
01656     const char *p_access, *p_junk, *p_owner, *p_group, *p_size;
01657     if( (p_access = strtok((char*)buffer," ")) == 0) continue;
01658     if( (p_junk  = strtok(NULL," ")) == 0) continue;
01659     if( (p_owner = strtok(NULL," ")) == 0) continue;
01660     if( (p_group = strtok(NULL," ")) == 0) continue;
01661     if( (p_size  = strtok(NULL," ")) == 0) continue;
01662 
01663     //kDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size;
01664 
01665     de.access = 0;
01666     if ( qstrlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware
01667       de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions
01668     }
01669 
01670     const char *p_date_1, *p_date_2, *p_date_3, *p_name;
01671 
01672     // A special hack for "/dev". A listing may look like this:
01673     // crw-rw-rw-   1 root     root       1,   5 Jun 29  1997 zero
01674     // So we just ignore the number in front of the ",". Ok, it is a hack :-)
01675     if ( strchr( p_size, ',' ) != 0L )
01676     {
01677       //kDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)";
01678       if ((p_size = strtok(NULL," ")) == 0)
01679         continue;
01680     }
01681 
01682     // Check whether the size we just read was really the size
01683     // or a month (this happens when the server lists no group)
01684     // Used to be the case on sunsite.uio.no, but not anymore
01685     // This is needed for the Netware case, too.
01686     if ( !isdigit( *p_size ) )
01687     {
01688       p_date_1 = p_size;
01689       p_size = p_group;
01690       p_group = 0;
01691       //kDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1;
01692     }
01693     else
01694     {
01695       p_date_1 = strtok(NULL," ");
01696       //kDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1;
01697     }
01698 
01699     if ( p_date_1 != 0 &&
01700          (p_date_2 = strtok(NULL," ")) != 0 &&
01701          (p_date_3 = strtok(NULL," ")) != 0 &&
01702          (p_name = strtok(NULL,"\r\n")) != 0 )
01703     {
01704       {
01705         QByteArray tmp( p_name );
01706         if ( p_access[0] == 'l' )
01707         {
01708           int i = tmp.lastIndexOf( " -> " );
01709           if ( i != -1 ) {
01710             de.link = remoteEncoding()->decode(p_name + i + 4);
01711             tmp.truncate( i );
01712           }
01713           else
01714             de.link.clear();
01715         }
01716         else
01717           de.link.clear();
01718 
01719         if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/'
01720           tmp.remove( 0, 1 );
01721 
01722         if (tmp.indexOf('/') != -1)
01723           continue; // Don't trick us!
01724 
01725         de.name     = remoteEncoding()->decode(tmp);
01726       }
01727 
01728       de.type = S_IFREG;
01729       switch ( p_access[0] ) {
01730       case 'd':
01731         de.type = S_IFDIR;
01732         break;
01733       case 's':
01734         de.type = S_IFSOCK;
01735         break;
01736       case 'b':
01737         de.type = S_IFBLK;
01738         break;
01739       case 'c':
01740         de.type = S_IFCHR;
01741         break;
01742       case 'l':
01743         de.type = S_IFREG;
01744         // we don't set S_IFLNK here.  de.link says it.
01745         break;
01746       default:
01747         break;
01748       }
01749 
01750       if ( p_access[1] == 'r' )
01751         de.access |= S_IRUSR;
01752       if ( p_access[2] == 'w' )
01753         de.access |= S_IWUSR;
01754       if ( p_access[3] == 'x' || p_access[3] == 's' )
01755         de.access |= S_IXUSR;
01756       if ( p_access[4] == 'r' )
01757         de.access |= S_IRGRP;
01758       if ( p_access[5] == 'w' )
01759         de.access |= S_IWGRP;
01760       if ( p_access[6] == 'x' || p_access[6] == 's' )
01761         de.access |= S_IXGRP;
01762       if ( p_access[7] == 'r' )
01763         de.access |= S_IROTH;
01764       if ( p_access[8] == 'w' )
01765         de.access |= S_IWOTH;
01766       if ( p_access[9] == 'x' || p_access[9] == 't' )
01767         de.access |= S_IXOTH;
01768       if ( p_access[3] == 's' || p_access[3] == 'S' )
01769         de.access |= S_ISUID;
01770       if ( p_access[6] == 's' || p_access[6] == 'S' )
01771         de.access |= S_ISGID;
01772       if ( p_access[9] == 't' || p_access[9] == 'T' )
01773         de.access |= S_ISVTX;
01774 
01775       de.owner    = remoteEncoding()->decode(p_owner);
01776       de.group    = remoteEncoding()->decode(p_group);
01777       de.size     = charToLongLong(p_size);
01778 
01779       // Parsing the date is somewhat tricky
01780       // Examples : "Oct  6 22:49", "May 13  1999"
01781 
01782       // First get current time - we need the current month and year
01783       time_t currentTime = time( 0L );
01784       struct tm * tmptr = gmtime( &currentTime );
01785       int currentMonth = tmptr->tm_mon;
01786       //kDebug(7102) << "Current time :" << asctime( tmptr );
01787       // Reset time fields
01788       tmptr->tm_isdst = -1; // We do not anything about day saving time
01789       tmptr->tm_sec = 0;
01790       tmptr->tm_min = 0;
01791       tmptr->tm_hour = 0;
01792       // Get day number (always second field)
01793       if (p_date_2)
01794         tmptr->tm_mday = atoi( p_date_2 );
01795       // Get month from first field
01796       // NOTE : no, we don't want to use KLocale here
01797       // It seems all FTP servers use the English way
01798       //kDebug(7102) << "Looking for month " << p_date_1;
01799       static const char * const s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
01800                                                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
01801       for ( int c = 0 ; c < 12 ; c ++ )
01802         if ( !qstrcmp( p_date_1, s_months[c]) )
01803         {
01804           //kDebug(7102) << "Found month " << c << " for " << p_date_1;
01805           tmptr->tm_mon = c;
01806           break;
01807         }
01808 
01809       // Parse third field
01810       if ( qstrlen( p_date_3 ) == 4 ) // 4 digits, looks like a year
01811         tmptr->tm_year = atoi( p_date_3 ) - 1900;
01812       else
01813       {
01814         // otherwise, the year is implicit
01815         // according to man ls, this happens when it is between than 6 months
01816         // old and 1 hour in the future.
01817         // So the year is : current year if tm_mon <= currentMonth+1
01818         // otherwise current year minus one
01819         // (The +1 is a security for the "+1 hour" at the end of the month issue)
01820         if ( tmptr->tm_mon > currentMonth + 1 )
01821           tmptr->tm_year--;
01822 
01823         // and p_date_3 contains probably a time
01824         char * semicolon;
01825         if ( p_date_3 && ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
01826         {
01827           *semicolon = '\0';
01828           tmptr->tm_min = atoi( semicolon + 1 );
01829           tmptr->tm_hour = atoi( p_date_3 );
01830         }
01831         else
01832           kWarning(7102) << "Can't parse third field " << p_date_3;
01833       }
01834 
01835       //kDebug(7102) << asctime( tmptr );
01836       de.date = mktime( tmptr );
01837       return true;
01838     }
01839   } // line invalid, loop to get another line
01840   return false;
01841 }
01842 
01843 //===============================================================================
01844 // public: get           download file from server
01845 // helper: ftpGet        called from get() and copy()
01846 //===============================================================================
01847 void Ftp::get( const KUrl & url )
01848 {
01849   kDebug(7102) << url;
01850 
01851   int iError = 0;
01852   const StatusCode cs = ftpGet(iError, -1, url, 0);
01853   ftpCloseCommand();                        // must close command!
01854 
01855   if (cs == statusSuccess) {
01856      finished();
01857      return;
01858   }
01859 
01860   if (iError) {                            // can have only server side errs
01861      error(iError, url.path());
01862   }
01863 }
01864 
01865 Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset)
01866 {
01867   // Calls error() by itself!
01868   if( !ftpOpenConnection(loginImplicit) )
01869     return statusServerError;
01870 
01871   // Try to find the size of the file (and check that it exists at
01872   // the same time). If we get back a 550, "File does not exist"
01873   // or "not a plain file", check if it is a directory. If it is a
01874   // directory, return an error; otherwise simply try to retrieve
01875   // the request...
01876   if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) &&
01877        ftpFolder(url.path(), false) )
01878   {
01879     // Ok it's a dir in fact
01880     kDebug(7102) << "it is a directory in fact";
01881     iError = ERR_IS_DIRECTORY;
01882     return statusServerError;
01883   }
01884 
01885   QString resumeOffset = metaData("resume");
01886   if ( !resumeOffset.isEmpty() )
01887   {
01888     llOffset = resumeOffset.toLongLong();
01889     kDebug(7102) << "got offset from metadata : " << llOffset;
01890   }
01891 
01892   if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) )
01893   {
01894     kWarning(7102) << "Can't open for reading";
01895     return statusServerError;
01896   }
01897 
01898   // Read the size from the response string
01899   if(m_size == UnknownSize)
01900   {
01901     const char* psz = strrchr( ftpResponse(4), '(' );
01902     if(psz) m_size = charToLongLong(psz+1);
01903     if (!m_size) m_size = UnknownSize;
01904   }
01905 
01906   // Send the mime-type...
01907   if (iCopyFile == -1) {
01908     StatusCode status = ftpSendMimeType(iError, url);
01909     if (status != statusSuccess) {
01910         return status;
01911     }
01912   }
01913 
01914   KIO::filesize_t bytesLeft = 0;
01915   if ( m_size != UnknownSize ) {
01916     bytesLeft = m_size - llOffset;
01917     totalSize( m_size );  // emit the total size...
01918   }
01919 
01920   kDebug(7102) << "starting with offset=" << llOffset;
01921   KIO::fileoffset_t processed_size = llOffset;
01922 
01923   QByteArray array;
01924   char buffer[maximumIpcSize];
01925   // start with small data chunks in case of a slow data source (modem)
01926   // - unfortunately this has a negative impact on performance for large
01927   // - files - so we will increase the block size after a while ...
01928   int iBlockSize = initialIpcSize;
01929   int iBufferCur = 0;
01930 
01931   while(m_size == UnknownSize || bytesLeft > 0)
01932   {  // let the buffer size grow if the file is larger 64kByte ...
01933     if(processed_size-llOffset > 1024 * 64)
01934       iBlockSize = maximumIpcSize;
01935 
01936     // read the data and detect EOF or error ...
01937     if(iBlockSize+iBufferCur > (int)sizeof(buffer))
01938       iBlockSize = sizeof(buffer) - iBufferCur;
01939     if (m_data->bytesAvailable() == 0)
01940       m_data->waitForReadyRead((readTimeout() * 1000));
01941     int n = m_data->read( buffer+iBufferCur, iBlockSize );
01942     if(n <= 0)
01943     {   // this is how we detect EOF in case of unknown size
01944       if( m_size == UnknownSize && n == 0 )
01945         break;
01946       // unexpected eof. Happens when the daemon gets killed.
01947       iError = ERR_COULD_NOT_READ;
01948       return statusServerError;
01949     }
01950     processed_size += n;
01951 
01952     // collect very small data chunks in buffer before processing ...
01953     if(m_size != UnknownSize)
01954     {
01955       bytesLeft -= n;
01956       iBufferCur += n;
01957       if(iBufferCur < minimumMimeSize && bytesLeft > 0)
01958       {
01959         processedSize( processed_size );
01960         continue;
01961       }
01962       n = iBufferCur;
01963       iBufferCur = 0;
01964     }
01965 
01966     // write output file or pass to data pump ...
01967     if(iCopyFile == -1)
01968     {
01969         array = QByteArray::fromRawData(buffer, n);
01970         data( array );
01971         array.clear();
01972     }
01973     else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0)
01974        return statusClientError;              // client side error
01975     processedSize( processed_size );
01976   }
01977 
01978   kDebug(7102) << "done";
01979   if(iCopyFile == -1)          // must signal EOF to data pump ...
01980     data(array);               // array is empty and must be empty!
01981 
01982   processedSize( m_size == UnknownSize ? processed_size : m_size );
01983   return statusSuccess;
01984 }
01985 
01986 #if 0
01987   void Ftp::mimetype( const KUrl& url )
01988   {
01989     if( !ftpOpenConnection(loginImplicit) )
01990           return;
01991 
01992     if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
01993       kWarning(7102) << "Can't open for reading";
01994       return;
01995     }
01996     char buffer[ 2048 ];
01997     QByteArray array;
01998     // Get one chunk of data only and send it, KIO::Job will determine the
01999     // mimetype from it using KMimeMagic
02000     int n = m_data->read( buffer, 2048 );
02001     array.setRawData(buffer, n);
02002     data( array );
02003     array.resetRawData(buffer, n);
02004 
02005     kDebug(7102) << "aborting";
02006     ftpAbortTransfer();
02007 
02008     kDebug(7102) << "finished";
02009     finished();
02010     kDebug(7102) << "after finished";
02011   }
02012 
02013   void Ftp::ftpAbortTransfer()
02014   {
02015     // RFC 959, page 34-35
02016     // IAC (interpret as command) = 255 ; IP (interrupt process) = 254
02017     // DM = 242 (data mark)
02018      char msg[4];
02019      // 1. User system inserts the Telnet "Interrupt Process" (IP) signal
02020      //   in the Telnet stream.
02021      msg[0] = (char) 255; //IAC
02022      msg[1] = (char) 254; //IP
02023      (void) send(sControl, msg, 2, 0);
02024      // 2. User system sends the Telnet "Sync" signal.
02025      msg[0] = (char) 255; //IAC
02026      msg[1] = (char) 242; //DM
02027      if (send(sControl, msg, 2, MSG_OOB) != 2)
02028        ; // error...
02029 
02030      // Send ABOR
02031      kDebug(7102) << "send ABOR";
02032      QCString buf = "ABOR\r\n";
02033      if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 )  {
02034        error( ERR_COULD_NOT_WRITE, QString() );
02035        return;
02036      }
02037 
02038      //
02039      kDebug(7102) << "read resp";
02040      if ( readresp() != '2' )
02041      {
02042        error( ERR_COULD_NOT_READ, QString() );
02043        return;
02044      }
02045 
02046     kDebug(7102) << "close sockets";
02047     closeSockets();
02048   }
02049 #endif
02050 
02051 //===============================================================================
02052 // public: put           upload file to server
02053 // helper: ftpPut        called from put() and copy()
02054 //===============================================================================
02055 void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags)
02056 {
02057   kDebug(7102) << url;
02058 
02059   int iError = 0;                           // iError gets status
02060   const StatusCode cs = ftpPut(iError, -1, url, permissions, flags);
02061   ftpCloseCommand();                        // must close command!
02062 
02063   if (cs == statusSuccess) {
02064     finished();
02065     return;
02066   }
02067 
02068   if (iError) {                             // can have only server side errs
02069     error(iError, url.path());
02070   }
02071 }
02072 
02073 Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url,
02074                             int permissions, KIO::JobFlags flags)
02075 {
02076   if( !ftpOpenConnection(loginImplicit) )
02077     return statusServerError;
02078 
02079   // Don't use mark partial over anonymous FTP.
02080   // My incoming dir allows put but not rename...
02081   bool bMarkPartial;
02082   if (m_user.isEmpty () || m_user == FTP_LOGIN)
02083     bMarkPartial = false;
02084   else
02085     bMarkPartial = config()->readEntry("MarkPartial", true);
02086 
02087   QString dest_orig = dest_url.path();
02088   QString dest_part( dest_orig );
02089   dest_part += ".part";
02090 
02091   if ( ftpSize( dest_orig, 'I' ) )
02092   {
02093     if ( m_size == 0 )
02094     { // delete files with zero size
02095       QByteArray cmd = "DELE ";
02096       cmd += remoteEncoding()->encode(dest_orig);
02097       if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
02098       {
02099         iError = ERR_CANNOT_DELETE_PARTIAL;
02100         return statusServerError;
02101       }
02102     }
02103     else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
02104     {
02105        iError = ERR_FILE_ALREADY_EXIST;
02106        return statusServerError;
02107     }
02108     else if ( bMarkPartial )
02109     { // when using mark partial, append .part extension
02110       if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) )
02111       {
02112         iError = ERR_CANNOT_RENAME_PARTIAL;
02113         return statusServerError;
02114       }
02115     }
02116     // Don't chmod an existing file
02117     permissions = -1;
02118   }
02119   else if ( bMarkPartial && ftpSize( dest_part, 'I' ) )
02120   { // file with extension .part exists
02121     if ( m_size == 0 )
02122     {  // delete files with zero size
02123       QByteArray cmd = "DELE ";
02124       cmd += remoteEncoding()->encode(dest_part);
02125       if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
02126       {
02127         iError = ERR_CANNOT_DELETE_PARTIAL;
02128         return statusServerError;
02129       }
02130     }
02131     else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
02132     {
02133       flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags;
02134       if (!(flags & KIO::Resume))
02135       {
02136         iError = ERR_FILE_ALREADY_EXIST;
02137         return statusServerError;
02138       }
02139     }
02140   }
02141   else
02142     m_size = 0;
02143 
02144   QString dest;
02145 
02146   // if we are using marking of partial downloads -> add .part extension
02147   if ( bMarkPartial ) {
02148     kDebug(7102) << "Adding .part extension to " << dest_orig;
02149     dest = dest_part;
02150   } else
02151     dest = dest_orig;
02152 
02153   KIO::fileoffset_t offset = 0;
02154 
02155   // set the mode according to offset
02156   if( (flags & KIO::Resume) && m_size > 0 )
02157   {
02158     offset = m_size;
02159     if(iCopyFile != -1)
02160     {
02161       if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 )
02162       {
02163         iError = ERR_CANNOT_RESUME;
02164         return statusClientError;
02165       }
02166     }
02167   }
02168 
02169   if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) )
02170      return statusServerError;
02171 
02172   kDebug(7102) << "ftpPut: starting with offset=" << offset;
02173   KIO::fileoffset_t processed_size = offset;
02174 
02175   QByteArray buffer;
02176   int result;
02177   int iBlockSize = initialIpcSize;
02178   // Loop until we got 'dataEnd'
02179   do
02180   {
02181     if(iCopyFile == -1)
02182     {
02183       dataReq(); // Request for data
02184       result = readData( buffer );
02185     }
02186     else
02187     { // let the buffer size grow if the file is larger 64kByte ...
02188       if(processed_size-offset > 1024 * 64)
02189         iBlockSize = maximumIpcSize;
02190       buffer.resize(iBlockSize);
02191       result = ::read(iCopyFile, buffer.data(), buffer.size());
02192       if(result < 0)
02193         iError = ERR_COULD_NOT_WRITE;
02194       else
02195         buffer.resize(result);
02196     }
02197 
02198     if (result > 0)
02199     {
02200       m_data->write( buffer );
02201       while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {}
02202       processed_size += result;
02203       processedSize (processed_size);
02204     }
02205   }
02206   while ( result > 0 );
02207 
02208   if (result != 0) // error
02209   {
02210     ftpCloseCommand();               // don't care about errors
02211     kDebug(7102) << "Error during 'put'. Aborting.";
02212     if (bMarkPartial)
02213     {
02214       // Remove if smaller than minimum size
02215       if ( ftpSize( dest, 'I' ) &&
02216            ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
02217       {
02218         QByteArray cmd = "DELE ";
02219         cmd += remoteEncoding()->encode(dest);
02220         (void) ftpSendCmd( cmd );
02221       }
02222     }
02223     return statusServerError;
02224   }
02225 
02226   if ( !ftpCloseCommand() )
02227   {
02228     iError = ERR_COULD_NOT_WRITE;
02229     return statusServerError;
02230   }
02231 
02232   // after full download rename the file back to original name
02233   if ( bMarkPartial )
02234   {
02235     kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")";
02236     if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) )
02237     {
02238       iError = ERR_CANNOT_RENAME_PARTIAL;
02239       return statusServerError;
02240     }
02241   }
02242 
02243   // set final permissions
02244   if ( permissions != -1 )
02245   {
02246     if ( m_user == FTP_LOGIN )
02247       kDebug(7102) << "Trying to chmod over anonymous FTP ???";
02248     // chmod the file we just put
02249     if ( ! ftpChmod( dest_orig, permissions ) )
02250     {
02251         // To be tested
02252         //if ( m_user != FTP_LOGIN )
02253         //    warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) );
02254     }
02255   }
02256 
02257   return statusSuccess;
02258 }
02259 
02262 bool Ftp::ftpSize( const QString & path, char mode )
02263 {
02264   m_size = UnknownSize;
02265   if( !ftpDataMode(mode) )
02266       return false;
02267 
02268   QByteArray buf;
02269   buf = "SIZE ";
02270   buf += remoteEncoding()->encode(path);
02271   if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02272     return false;
02273 
02274   // skip leading "213 " (response code)
02275   QByteArray psz (ftpResponse(4));
02276   if(psz.isEmpty())
02277     return false;
02278   bool ok = false;
02279   m_size = psz.trimmed().toLongLong(&ok);
02280   if (!ok) m_size = UnknownSize;
02281   return true;
02282 }
02283 
02284 bool Ftp::ftpFileExists(const QString& path)
02285 {
02286   QByteArray buf;
02287   buf = "SIZE ";
02288   buf += remoteEncoding()->encode(path);
02289   if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02290     return false;
02291 
02292   // skip leading "213 " (response code)
02293   const char* psz = ftpResponse(4);
02294   return psz != 0;
02295 }
02296 
02297 // Today the differences between ASCII and BINARY are limited to
02298 // CR or CR/LF line terminators. Many servers ignore ASCII (like
02299 // win2003 -or- vsftp with default config). In the early days of
02300 // computing, when even text-files had structure, this stuff was
02301 // more important.
02302 // Theoretically "list" could return different results in ASCII
02303 // and BINARY mode. But again, most servers ignore ASCII here.
02304 bool Ftp::ftpDataMode(char cMode)
02305 {
02306   if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I';
02307   else if(cMode == 'a') cMode = 'A';
02308   else if(cMode != 'A') cMode = 'I';
02309 
02310   kDebug(7102) << "want" << cMode << "has" << m_cDataMode;
02311   if(m_cDataMode == cMode)
02312     return true;
02313 
02314   QByteArray buf = "TYPE ";
02315   buf += cMode;
02316   if( !ftpSendCmd(buf) || (m_iRespType != 2) )
02317       return false;
02318   m_cDataMode = cMode;
02319   return true;
02320 }
02321 
02322 
02323 bool Ftp::ftpFolder(const QString& path, bool bReportError)
02324 {
02325   QString newPath = path;
02326   int iLen = newPath.length();
02327   if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1);
02328 
02329   //kDebug(7102) << "want" << newPath << "has" << m_currentPath;
02330   if(m_currentPath == newPath)
02331     return true;
02332 
02333   QByteArray tmp = "cwd ";
02334   tmp += remoteEncoding()->encode(newPath);
02335   if( !ftpSendCmd(tmp) )
02336     return false;                  // connection failure
02337   if(m_iRespType != 2)
02338   {
02339     if(bReportError)
02340       error(ERR_CANNOT_ENTER_DIRECTORY, path);
02341     return false;                  // not a folder
02342   }
02343   m_currentPath = newPath;
02344   return true;
02345 }
02346 
02347 
02348 //===============================================================================
02349 // public: copy          don't use kio data pump if one side is a local file
02350 // helper: ftpCopyPut    called from copy() on upload
02351 // helper: ftpCopyGet    called from copy() on download
02352 //===============================================================================
02353 void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags )
02354 {
02355   int iError = 0;
02356   int iCopyFile = -1;
02357   StatusCode cs = statusSuccess;
02358   bool bSrcLocal = src.isLocalFile();
02359   bool bDestLocal = dest.isLocalFile();
02360   QString  sCopyFile;
02361 
02362   if(bSrcLocal && !bDestLocal)                    // File -> Ftp
02363   {
02364     sCopyFile = src.toLocalFile();
02365     kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path();
02366     cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags);
02367     if( cs == statusServerError) sCopyFile = dest.url();
02368   }
02369   else if(!bSrcLocal && bDestLocal)               // Ftp -> File
02370   {
02371     sCopyFile = dest.toLocalFile();
02372     kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile;
02373     cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags);
02374     if( cs == statusServerError ) sCopyFile = src.url();
02375   }
02376   else {
02377     error( ERR_UNSUPPORTED_ACTION, QString() );
02378     return;
02379   }
02380 
02381   // perform clean-ups and report error (if any)
02382   if(iCopyFile != -1)
02383     ::close(iCopyFile);
02384   ftpCloseCommand();                        // must close command!
02385   if(iError)
02386     error(iError, sCopyFile);
02387   else
02388     finished();
02389 }
02390 
02391 
02392 Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile,
02393                                 const KUrl& url, int permissions, KIO::JobFlags flags)
02394 {
02395   // check if source is ok ...
02396   KDE_struct_stat buff;
02397   bool bSrcExists = (KDE::stat( sCopyFile, &buff ) != -1);
02398   if(bSrcExists)
02399   { if(S_ISDIR(buff.st_mode))
02400     {
02401       iError = ERR_IS_DIRECTORY;
02402       return statusClientError;
02403     }
02404   }
02405   else
02406   {
02407     iError = ERR_DOES_NOT_EXIST;
02408     return statusClientError;
02409   }
02410 
02411   iCopyFile = KDE::open( sCopyFile, O_RDONLY );
02412   if(iCopyFile == -1)
02413   {
02414     iError = ERR_CANNOT_OPEN_FOR_READING;
02415     return statusClientError;
02416   }
02417 
02418   // delegate the real work (iError gets status) ...
02419   totalSize(buff.st_size);
02420 #ifdef  ENABLE_CAN_RESUME
02421   return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume);
02422 #else
02423   return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume);
02424 #endif
02425 }
02426 
02427 
02428 Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile,
02429                                 const KUrl& url, int permissions, KIO::JobFlags flags)
02430 {
02431   // check if destination is ok ...
02432   KDE_struct_stat buff;
02433   const bool bDestExists = (KDE::stat( sCopyFile, &buff ) != -1);
02434   if(bDestExists)
02435   { if(S_ISDIR(buff.st_mode))
02436     {
02437       iError = ERR_IS_DIRECTORY;
02438       return statusClientError;
02439     }
02440     if(!(flags & KIO::Overwrite))
02441     {
02442       iError = ERR_FILE_ALREADY_EXIST;
02443       return statusClientError;
02444     }
02445   }
02446 
02447   // do we have a ".part" file?
02448   const QString sPart = sCopyFile + QLatin1String(".part");
02449   bool bResume = false;
02450   const bool bPartExists = (KDE::stat( sPart, &buff ) != -1);
02451   const bool bMarkPartial = config()->readEntry("MarkPartial", true);
02452   const QString dest = bMarkPartial ? sPart : sCopyFile;
02453   if (bMarkPartial && bPartExists && buff.st_size > 0)
02454   { // must not be a folder! please fix a similar bug in kio_file!!
02455     if(S_ISDIR(buff.st_mode))
02456     {
02457       iError = ERR_DIR_ALREADY_EXIST;
02458       return statusClientError;                            // client side error
02459     }
02460     //doesn't work for copy? -> design flaw?
02461 #ifdef  ENABLE_CAN_RESUME
02462     bResume = canResume( buff.st_size );
02463 #else
02464     bResume = true;
02465 #endif
02466   }
02467 
02468   if (bPartExists && !bResume)                  // get rid of an unwanted ".part" file
02469     QFile::remove(sPart);
02470 
02471   // WABA: Make sure that we keep writing permissions ourselves,
02472   // otherwise we can be in for a surprise on NFS.
02473   mode_t initialMode;
02474   if (permissions != -1)
02475     initialMode = permissions | S_IWUSR;
02476   else
02477     initialMode = 0666;
02478 
02479   // open the output file ...
02480   KIO::fileoffset_t hCopyOffset = 0;
02481   if (bResume) {
02482     iCopyFile = KDE::open( sPart, O_RDWR );  // append if resuming
02483     hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END);
02484     if(hCopyOffset < 0)
02485     {
02486       iError = ERR_CANNOT_RESUME;
02487       return statusClientError;                            // client side error
02488     }
02489     kDebug(7102) << "resuming at " << hCopyOffset;
02490   }
02491   else {
02492     iCopyFile = KDE::open(dest, O_CREAT | O_TRUNC | O_WRONLY, initialMode);
02493   }
02494 
02495   if(iCopyFile == -1)
02496   {
02497     kDebug(7102) << "### COULD NOT WRITE " << sCopyFile;
02498     iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED
02499                                : ERR_CANNOT_OPEN_FOR_WRITING;
02500     return statusClientError;
02501   }
02502 
02503   // delegate the real work (iError gets status) ...
02504   StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset);
02505   if( ::close(iCopyFile) && iRes == statusSuccess )
02506   {
02507     iError = ERR_COULD_NOT_WRITE;
02508     iRes = statusClientError;
02509   }
02510   iCopyFile = -1;
02511 
02512   // handle renaming or deletion of a partial file ...
02513   if(bMarkPartial)
02514   {
02515     if(iRes == statusSuccess)
02516     { // rename ".part" on success
02517       if ( KDE::rename( sPart, sCopyFile ) )
02518       {
02519         // If rename fails, try removing the destination first if it exists.
02520         if (!bDestExists || !(QFile::remove(sCopyFile) && KDE::rename(sPart, sCopyFile) == 0)) {
02521             kDebug(7102) << "cannot rename " << sPart << " to " << sCopyFile;
02522             iError = ERR_CANNOT_RENAME_PARTIAL;
02523             iRes = statusClientError;
02524         }
02525       }
02526     }
02527     else if(KDE::stat( sPart, &buff ) == 0)
02528     { // should a very small ".part" be deleted?
02529       int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
02530       if (buff.st_size <  size)
02531         QFile::remove(sPart);
02532     }
02533   }
02534   return iRes;
02535 }
02536 
02537 Ftp::StatusCode Ftp::ftpSendMimeType(int& iError, const KUrl& url)
02538 {
02539   const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : m_size);
02540   QByteArray buffer(totalSize, '\0');
02541 
02542   while (true) {
02543       // Wait for content to be available...
02544       if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((readTimeout() * 1000))) {
02545           iError = ERR_COULD_NOT_READ;
02546           return statusServerError;
02547       }
02548 
02549       const int bytesRead = m_data->peek(buffer.data(), totalSize);
02550 
02551       // If we got a -1, it must be an error so return an error.
02552       if (bytesRead == -1) {
02553           iError = ERR_COULD_NOT_READ;
02554           return statusServerError;
02555       }
02556 
02557       // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size
02558       // equal to the size we want, then break.
02559       if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) {
02560           break;
02561       }
02562   }
02563 
02564   if (!buffer.isEmpty()) {
02565       KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), buffer);
02566       kDebug(7102) << "Emitting mimetype" << mime->name();
02567       mimeType( mime->name() ); // emit the mime type...
02568   }
02569 
02570   return statusSuccess;
02571 }
02572 
02573 void Ftp::proxyAuthentication(const QNetworkProxy& proxy, QAuthenticator* authenticator)
02574 {
02575     Q_UNUSED(proxy);
02576     kDebug(7102) << "Authenticator received -- realm:" << authenticator->realm() << "user:"
02577                  << authenticator->user();
02578 
02579     AuthInfo info;
02580     info.url = m_proxyURL;
02581     info.realmValue = authenticator->realm();
02582     info.verifyPath = true;    //### whatever
02583     info.username = authenticator->user();
02584 
02585     const bool haveCachedCredentials = checkCachedAuthentication(info);
02586 
02587     // if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
02588     // and it was not successful. see below and saveProxyAuthenticationForSocket().
02589     if (!haveCachedCredentials || m_socketProxyAuth) {
02590         // Save authentication info if the connection succeeds. We need to disconnect
02591         // this after saving the auth data (or an error) so we won't save garbage afterwards!
02592         connect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
02593         //### fillPromptInfo(&info);
02594         info.prompt = i18n("You need to supply a username and a password for "
02595                            "the proxy server listed below before you are allowed "
02596                            "to access any sites.");
02597         info.keepPassword = true;
02598         info.commentLabel = i18n("Proxy:");
02599         info.comment = i18n("<b>%1</b> at <b>%2</b>", info.realmValue, m_proxyURL.host());
02600         const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
02601         if (!dataEntered) {
02602             kDebug(7102) << "looks like the user canceled proxy authentication.";
02603             error(ERR_USER_CANCELED, m_proxyURL.host());
02604             return;
02605         }
02606     }
02607     authenticator->setUser(info.username);
02608     authenticator->setPassword(info.password);
02609     authenticator->setOption(QLatin1String("keepalive"), info.keepPassword);
02610 
02611     if (m_socketProxyAuth) {
02612         *m_socketProxyAuth = *authenticator;
02613     } else {
02614         m_socketProxyAuth = new QAuthenticator(*authenticator);
02615     }
02616 
02617     m_proxyURL.setUser(info.username);
02618     m_proxyURL.setPassword(info.password);
02619 }
02620 
02621 void Ftp::saveProxyAuthentication()
02622 {
02623     kDebug(7102);
02624     disconnect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
02625     Q_ASSERT(m_socketProxyAuth);
02626     if (m_socketProxyAuth) {
02627         kDebug(7102) << "-- realm:" << m_socketProxyAuth->realm() << "user:" << m_socketProxyAuth->user();
02628         KIO::AuthInfo a;
02629         a.verifyPath = true;
02630         a.url = m_proxyURL;
02631         a.realmValue = m_socketProxyAuth->realm();
02632         a.username = m_socketProxyAuth->user();
02633         a.password = m_socketProxyAuth->password();
02634         a.keepPassword = m_socketProxyAuth->option(QLatin1String("keepalive")).toBool();
02635         cacheAuthentication(a);
02636     }
02637     delete m_socketProxyAuth;
02638     m_socketProxyAuth = 0;
02639 }
02640 
02641 void Ftp::fixupEntryName(FtpEntry* e)
02642 {
02643     Q_ASSERT(e);
02644     if (e->type == S_IFDIR) {
02645         if (!ftpFolder(e->name, false)) {
02646             QString name (e->name.trimmed());
02647             if (ftpFolder(name, false)) {
02648                 e->name = name;
02649                 kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
02650             } else {
02651                 int index = 0;
02652                 while (e->name.at(index).isSpace()) {
02653                     index++;
02654                     name = e->name.mid(index);
02655                     if (ftpFolder(name, false)) {
02656                         kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
02657                         e->name = name;
02658                         break;
02659                     }
02660                 }
02661             }
02662         }
02663     } else {
02664         if (!ftpFileExists(e->name)) {
02665             QString name (e->name.trimmed());
02666             if (ftpFileExists(name)) {
02667                 e->name = name;
02668                 kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
02669             } else {
02670                 int index = 0;
02671                 while (e->name.at(index).isSpace()) {
02672                     index++;
02673                     name = e->name.mid(index);
02674                     if (ftpFileExists(name)) {
02675                         kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
02676                         e->name = name;
02677                         break;
02678                     }
02679                 }
02680             }
02681         }
02682     }
02683 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:37:22 by doxygen 1.7.5.1 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.9.5 API Reference

Skip menu "kdelibs-4.9.5 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