• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 #include <sys/stat.h>
00036 #ifdef HAVE_SYS_TIME_H
00037 #include <sys/time.h>
00038 #endif
00039 #ifdef HAVE_SYS_SELECT_H
00040 #include <sys/select.h>
00041 #endif
00042 
00043 #include <netinet/in.h>
00044 #include <arpa/inet.h>
00045 
00046 #include <assert.h>
00047 #include <ctype.h>
00048 #include <errno.h>
00049 #include <fcntl.h>
00050 #include <netdb.h>
00051 #include <stdlib.h>
00052 #include <string.h>
00053 #include <unistd.h>
00054 #include <signal.h>
00055 
00056 #if TIME_WITH_SYS_TIME
00057 #include <time.h>
00058 #endif
00059 
00060 #include <QtCore/QDir>
00061 #include <QtNetwork/QHostAddress>
00062 #include <QtNetwork/QTcpSocket>
00063 #include <QtNetwork/QTcpServer>
00064 
00065 #include <kdebug.h>
00066 #include <kglobal.h>
00067 #include <klocale.h>
00068 #include <kcomponentdata.h>
00069 #include <kmimetype.h>
00070 #include <kio/ioslave_defaults.h>
00071 #include <kio/slaveconfig.h>
00072 #include <kremoteencoding.h>
00073 #include <ksocketfactory.h>
00074 #include <kde_file.h>
00075 #include <kconfiggroup.h>
00076 
00077 #ifdef HAVE_STRTOLL
00078   #define charToLongLong(a) strtoll(a, 0, 10)
00079 #else
00080   #define charToLongLong(a) strtol(a, 0, 10)
00081 #endif
00082 
00083 #define FTP_LOGIN   "anonymous"
00084 #define FTP_PASSWD  "anonymous@"

//#undef  kDebug
#define ENABLE_CAN_RESUME

// JPF: somebody should find a better solution for this or move this to KIO
// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
namespace KIO {
    enum buffersizes
    {  /**
        * largest buffer size that should be used to transfer data between
        * KIO slaves using the data() function
        */
        maximumIpcSize = 32 * 1024,
00089         initialIpcSize =  2 * 1024,
00093         mimimumMimeSize =     1024
    };

    // JPF: this helper was derived from write_all in file.cc (FileProtocol).
    static // JPF: in ftp.cc we make it static
00101    int WriteToFile(int fd, const char *buf, size_t len)
   {
      while (len > 0)
      {  // JPF: shouldn't there be a KDE_write?
         ssize_t written = write(fd, buf, len);
         if (written >= 0)
         {   buf += written;
             len -= written;
             continue;
         }
         switch(errno)
         {   case EINTR:   continue;
             case EPIPE:   return ERR_CONNECTION_BROKEN;
             case ENOSPC:  return ERR_DISK_FULL;
             default:      return ERR_COULD_NOT_WRITE;
         }
      }
      return 0;
   }
}

KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;

using namespace KIO;

extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
00102 {
00103   KComponentData componentData( "kio_ftp", "kdelibs4" );
00104   ( void ) KGlobal::locale();
00105 
00106   kDebug(7102) << "Starting " << getpid();
00107 
00108   if (argc != 4)
00109   {
00110      fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n");
00111      exit(-1);
00112   }
00113 
00114   Ftp slave(argv[2], argv[3]);
00115   slave.dispatchLoop();
00116 
00117   kDebug(7102) << "Done";
00118   return 0;
00119 }
00120 
00121 //===============================================================================
00122 // Ftp
00123 //===============================================================================
00124 
00125 Ftp::Ftp( const QByteArray &pool, const QByteArray &app )
00126     : SlaveBase( "ftp", pool, app )
00127 {
00128   // init the socket data
00129   m_data = m_control = NULL;
00130   m_server = NULL;
00131   ftpCloseControlConnection();
00132 
00133   // init other members
00134   m_port = 0;
00135 }
00136 
00137 
00138 Ftp::~Ftp()
00139 {
00140   kDebug(7102);
00141   closeConnection();
00142 }
00143 
00147 void Ftp::ftpCloseDataConnection()
00148 {
00149     delete m_data;
00150     m_data = NULL;
00151     delete m_server;
00152     m_server = NULL;
00153 }
00154 
00159 void Ftp::ftpCloseControlConnection()
00160 {
00161   m_extControl = 0;
00162   delete m_control;
00163   m_control = NULL;
00164   m_cDataMode = 0;
00165   m_bLoggedOn = false;    // logon needs control connction
00166   m_bTextMode = false;
00167   m_bBusy = false;
00168 }
00169 
00174 const char* Ftp::ftpResponse(int iOffset)
00175 {
00176   assert(m_control != NULL);    // must have control connection socket
00177   const char *pTxt = m_lastControlLine.data();
00178 
00179   // read the next line ...
00180   if(iOffset < 0)
00181   {
00182     int  iMore = 0;
00183     m_iRespCode = 0;
00184 
00185     // If the server sends a multiline response starting with
00186     // "nnn-text" we loop here until a final "nnn text" line is
00187     // reached. Only data from the final line will be stored.
00188     do {
00189       while (!m_control->canReadLine() && m_control->waitForReadyRead()) {}
00190       m_lastControlLine = m_control->readLine();
00191       pTxt = m_lastControlLine.data();
00192       int iCode  = atoi(pTxt);
00193       if (iMore == 0) {
00194           // first line
00195           kDebug(7102) << "    > " << pTxt;
00196           if(iCode >= 100) {
00197               m_iRespCode = iCode;
00198               if (pTxt[3] == '-') {
00199                   // marker for a multiple line response
00200                   iMore = iCode;
00201               }
00202           } else {
00203               kWarning(7102) << "Cannot parse valid code from line" << pTxt;
00204           }
00205       } else {
00206           // multi-line
00207           kDebug(7102) << "    > " << pTxt;
00208           if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') {
00209               iMore = 0;
00210           }
00211       }
00212     } while(iMore != 0);
00213     kDebug(7102) << "resp> " << pTxt;
00214 
00215     m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0;
00216   }
00217 
00218   // return text with offset ...
00219   while(iOffset-- > 0 && pTxt[0])
00220     pTxt++;
00221   return pTxt;
00222 }
00223 
00224 
00225 void Ftp::closeConnection()
00226 {
00227   if(m_control != NULL || m_data != NULL)
00228     kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy;
00229 
00230   if(m_bBusy)              // ftpCloseCommand not called
00231   {
00232     kWarning(7102) << "Abandoned data stream";
00233     ftpCloseDataConnection();
00234   }
00235 
00236   if(m_bLoggedOn)           // send quit
00237   {
00238     if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) )
00239       kWarning(7102) << "QUIT returned error: " << m_iRespCode;
00240   }
00241 
00242   // close the data and control connections ...
00243   ftpCloseDataConnection();
00244   ftpCloseControlConnection();
00245 }
00246 
00247 void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user,
00248                    const QString& _pass )
00249 {
00250   kDebug(7102) << _host << "port=" << _port;
00251 
00252   m_proxyURL = metaData("UseProxy");
00253   m_bUseProxy = (m_proxyURL.isValid() && m_proxyURL.protocol() == "ftp");
00254 
00255   if ( m_host != _host || m_port != _port ||
00256        m_user != _user || m_pass != _pass )
00257     closeConnection();
00258 
00259   m_host = _host;
00260   m_port = _port;
00261   m_user = _user;
00262   m_pass = _pass;
00263 }
00264 
00265 void Ftp::openConnection()
00266 {
00267   ftpOpenConnection(loginExplicit);
00268 }
00269 
00270 bool Ftp::ftpOpenConnection (LoginMode loginMode)
00271 {
00272   // check for implicit login if we are already logged on ...
00273   if(loginMode == loginImplicit && m_bLoggedOn)
00274   {
00275     assert(m_control != NULL);    // must have control connection socket
00276     return true;
00277   }
00278 
00279   kDebug(7102) << "ftpOpenConnection " << m_host << ":" << m_port << " "
00280                 << m_user << " [password hidden]";
00281 
00282   infoMessage( i18n("Opening connection to host %1", m_host) );
00283 
00284   if ( m_host.isEmpty() )
00285   {
00286     error( ERR_UNKNOWN_HOST, QString() );
00287     return false;
00288   }
00289 
00290   assert( !m_bLoggedOn );
00291 
00292   m_initialPath.clear();
00293   m_currentPath.clear();
00294 
00295   QString host = m_bUseProxy ? m_proxyURL.host() : m_host;
00296   int port = m_bUseProxy ? m_proxyURL.port() : m_port;
00297 
00298   if (!ftpOpenControlConnection(host, port) )
00299     return false;          // error emitted by ftpOpenControlConnection
00300   infoMessage( i18n("Connected to host %1", m_host) );
00301 
00302   if(loginMode != loginDefered)
00303   {
00304     m_bLoggedOn = ftpLogin();
00305     if( !m_bLoggedOn )
00306       return false;       // error emitted by ftpLogin
00307   }
00308 
00309   m_bTextMode = config()->readEntry("textmode", false);
00310   connected();
00311   return true;
00312 }
00313 
00314 
00320 bool Ftp::ftpOpenControlConnection( const QString &host, int port )
00321 {
00322   // implicitly close, then try to open a new connection ...
00323   closeConnection();
00324   QString sErrorMsg;
00325 
00326   // now connect to the server and read the login message ...
00327   if (port == 0)
00328     port = 21;                  // default FTP port
00329   m_control = KSocketFactory::synchronousConnectToHost("ftp", host, port, connectTimeout() * 1000);
00330   int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT;
00331 
00332   // on connect success try to read the server message...
00333   if(iErrorCode == 0)
00334   {
00335     const char* psz = ftpResponse(-1);
00336     if(m_iRespType != 2)
00337     { // login not successful, do we have an message text?
00338       if(psz[0])
00339         sErrorMsg = i18n("%1.\n\nReason: %2", host, psz);
00340       iErrorCode = ERR_COULD_NOT_CONNECT;
00341     }
00342   }
00343   else
00344   {
00345     if (m_control->error() == QAbstractSocket::HostNotFoundError)
00346       iErrorCode = ERR_UNKNOWN_HOST;
00347 
00348     sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString());
00349   }
00350 
00351   // if there was a problem - report it ...
00352   if(iErrorCode == 0)             // OK, return success
00353     return true;
00354   closeConnection();              // clean-up on error
00355   error(iErrorCode, sErrorMsg);
00356   return false;
00357 }
00358 
00366 bool Ftp::ftpLogin()
00367 {
00368   infoMessage( i18n("Sending login information") );
00369 
00370   assert( !m_bLoggedOn );
00371 
00372   QString user = m_user;
00373   QString pass = m_pass;
00374 
00375   if ( config()->readEntry("EnableAutoLogin", false) )
00376   {
00377     QString au = config()->readEntry("autoLoginUser");
00378     if ( !au.isEmpty() )
00379     {
00380         user = au;
00381         pass = config()->readEntry("autoLoginPass");
00382     }
00383   }
00384 
00385   // Try anonymous login if both username/password
00386   // information is blank.
00387   if (user.isEmpty() && pass.isEmpty())
00388   {
00389     user = FTP_LOGIN;
00390     pass = FTP_PASSWD;
00391   }
00392 
00393   AuthInfo info;
00394   info.url.setProtocol( "ftp" );
00395   info.url.setHost( m_host );
00396   if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00397       info.url.setPort( m_port );
00398   info.url.setUser( user );
00399 
00400   QByteArray tempbuf;
00401   int failedAuth = 0;
00402 
00403   do
00404   {
00405     // Check the cache and/or prompt user for password if 1st
00406     // login attempt failed OR the user supplied a login name,
00407     // but no password.
00408     if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) )
00409     {
00410       QString errorMsg;
00411       kDebug(7102) << "Prompting user for login info...";
00412 
00413       // Ask user if we should retry after when login fails!
00414       if( failedAuth > 0 )
00415       {
00416         errorMsg = i18n("Message sent:\nLogin using username=%1 and "
00417                         "password=[hidden]\n\nServer replied:\n%2\n\n"
00418                         , user, ftpResponse(0));
00419       }
00420 
00421       if ( user != FTP_LOGIN )
00422         info.username = user;
00423 
00424       info.prompt = i18n("You need to supply a username and a password "
00425                           "to access this site.");
00426       info.commentLabel = i18n( "Site:" );
00427       info.comment = i18n("<b>%1</b>",  m_host );
00428       info.keepPassword = true; // Prompt the user for persistence as well.
00429       info.readOnly = (!m_user.isEmpty() && m_user != FTP_LOGIN);
00430 
00431       bool disablePassDlg = config()->readEntry( "DisablePassDlg", false );
00432       if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) )
00433       {
00434         error( ERR_USER_CANCELED, m_host );
00435         return false;
00436       }
00437       else
00438       {
00439         user = info.username;
00440         pass = info.password;
00441       }
00442     }
00443 
00444     tempbuf = "USER ";
00445     tempbuf += user.toLatin1();
00446     if ( m_bUseProxy )
00447     {
00448       tempbuf += '@';
00449       tempbuf += m_host.toLatin1();
00450       if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00451       {
00452         tempbuf += ':';
00453         tempbuf += QString::number(m_port).toLatin1();
00454       }
00455     }
00456 
00457     kDebug(7102) << "Sending Login name: " << tempbuf;
00458 
00459     bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00460     bool needPass = (m_iRespCode == 331);
00461     // Prompt user for login info if we do not
00462     // get back a "230" or "331".
00463     if ( !loggedIn && !needPass )
00464     {
00465       kDebug(7102) << "Login failed: " << ftpResponse(0);
00466       ++failedAuth;
00467       continue;  // Well we failed, prompt the user please!!
00468     }
00469 
00470     if( needPass )
00471     {
00472       tempbuf = "pass ";
00473       tempbuf += pass.toLatin1();
00474       kDebug(7102) << "Sending Login password: " << "[protected]";
00475       loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00476     }
00477 
00478     if ( loggedIn )
00479     {
00480       // Do not cache the default login!!
00481       if( user != FTP_LOGIN && pass != FTP_PASSWD )
00482         cacheAuthentication( info );
00483       failedAuth = -1;
00484     }
00485 
00486   } while( ++failedAuth );
00487 
00488 
00489   kDebug(7102) << "Login OK";
00490   infoMessage( i18n("Login OK") );
00491 
00492   // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix:
00493   // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint
00494   if( ftpSendCmd("SYST") && (m_iRespType == 2) )
00495   {
00496     if( !strncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version
00497     {
00498       ftpSendCmd( "site dirstyle" );
00499       // Check if it was already in Unix style
00500       // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk>
00501       if( !strncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 ))
00502          //It was in Unix style already!
00503          ftpSendCmd( "site dirstyle" );
00504       // windows won't support chmod before KDE konquers their desktop...
00505       m_extControl |= chmodUnknown;
00506     }
00507   }
00508   else
00509     kWarning(7102) << "SYST failed";
00510 
00511   if ( config()->readEntry ("EnableAutoLoginMacro", false) )
00512     ftpAutoLoginMacro ();
00513 
00514   // Get the current working directory
00515   kDebug(7102) << "Searching for pwd";
00516   if( !ftpSendCmd("PWD") || (m_iRespType != 2) )
00517   {
00518     kDebug(7102) << "Couldn't issue pwd command";
00519     error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) ); // or anything better ?
00520     return false;
00521   }
00522 
00523   QString sTmp = remoteEncoding()->decode( ftpResponse(3) );
00524   int iBeg = sTmp.indexOf('"');
00525   int iEnd = sTmp.lastIndexOf('"');
00526   if(iBeg > 0 && iBeg < iEnd)
00527   {
00528     m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1);
00529     if(m_initialPath[0] != '/') m_initialPath.prepend('/');
00530     kDebug(7102) << "Initial path set to: " << m_initialPath;
00531     m_currentPath = m_initialPath;
00532   }
00533   return true;
00534 }
00535 
00536 void Ftp::ftpAutoLoginMacro ()
00537 {
00538   QString macro = metaData( "autoLoginMacro" );
00539 
00540   if ( macro.isEmpty() )
00541     return;
00542 
00543   const QStringList list = macro.split('\n',QString::SkipEmptyParts);
00544 
00545   for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
00546   {
00547     if ( (*it).startsWith("init") )
00548     {
00549       const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
00550       it = list2.begin();
00551       ++it;  // ignore the macro name
00552 
00553       for( ; it != list2.end() ; ++it )
00554       {
00555         // TODO: Add support for arbitrary commands
00556         // besides simply changing directory!!
00557         if ( (*it).startsWith( "cwd" ) )
00558           ftpFolder( (*it).mid(4).trimmed(), false );
00559       }
00560 
00561       break;
00562     }
00563   }
00564 }
00565 
00566 
00576 bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries )
00577 {
00578   assert(m_control != NULL);    // must have control connection socket
00579 
00580   if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1)
00581   {
00582     kWarning(7102) << "Invalid command received (contains CR or LF):"
00583                     << cmd.data();
00584     error( ERR_UNSUPPORTED_ACTION, m_host );
00585     return false;
00586   }
00587 
00588   // Don't print out the password...
00589   bool isPassCmd = (cmd.left(4).toLower() == "pass");
00590   if ( !isPassCmd )
00591     kDebug(7102) << "send> " << cmd.data();
00592   else
00593     kDebug(7102) << "send> pass [protected]";
00594 
00595   // Send the message...
00596   QByteArray buf = cmd;
00597   buf += "\r\n";      // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html
00598   int num = m_control->write(buf);
00599   while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {}
00600 
00601   // If we were able to successfully send the command, then we will
00602   // attempt to read the response. Otherwise, take action to re-attempt
00603   // the login based on the maximum number of retries specified...
00604   if( num > 0 )
00605     ftpResponse(-1);
00606   else
00607   {
00608     m_iRespType = m_iRespCode = 0;
00609   }
00610 
00611   // If respCh is NULL or the response is 421 (Timed-out), we try to re-send
00612   // the command based on the value of maxretries.
00613   if( (m_iRespType <= 0) || (m_iRespCode == 421) )
00614   {
00615     // We have not yet logged on...
00616     if (!m_bLoggedOn)
00617     {
00618       // The command was sent from the ftpLogin function, i.e. we are actually
00619       // attempting to login in. NOTE: If we already sent the username, we
00620       // return false and let the user decide whether (s)he wants to start from
00621       // the beginning...
00622       if (maxretries > 0 && !isPassCmd)
00623       {
00624         closeConnection ();
00625         if( ftpOpenConnection(loginDefered) )
00626           ftpSendCmd ( cmd, maxretries - 1 );
00627       }
00628 
00629       return false;
00630     }
00631     else
00632     {
00633       if ( maxretries < 1 )
00634         return false;
00635       else
00636       {
00637         kDebug(7102) << "Was not able to communicate with " << m_host
00638                       << "Attempting to re-establish connection.";
00639 
00640         closeConnection(); // Close the old connection...
00641         openConnection();  // Attempt to re-establish a new connection...
00642 
00643         if (!m_bLoggedOn)
00644         {
00645           if (m_control != NULL)  // if openConnection succeeded ...
00646           {
00647             kDebug(7102) << "Login failure, aborting";
00648             error (ERR_COULD_NOT_LOGIN, m_host);
00649             closeConnection ();
00650           }
00651           return false;
00652         }
00653 
00654         kDebug(7102) << "Logged back in, re-issuing command";
00655 
00656         // If we were able to login, resend the command...
00657         if (maxretries)
00658           maxretries--;
00659 
00660         return ftpSendCmd( cmd, maxretries );
00661       }
00662     }
00663   }
00664 
00665   return true;
00666 }
00667 
00668 /*
00669  * ftpOpenPASVDataConnection - set up data connection, using PASV mode
00670  *
00671  * return 0 if successful, ERR_INTERNAL otherwise
00672  * doesn't set error message, since non-pasv mode will always be tried if
00673  * this one fails
00674  */
00675 int Ftp::ftpOpenPASVDataConnection()
00676 {
00677   assert(m_control != NULL);    // must have control connection socket
00678   assert(m_data == NULL);       // ... but no data connection
00679 
00680   // Check that we can do PASV
00681   QHostAddress addr = m_control->peerAddress();
00682   if (addr.protocol() != QAbstractSocket::IPv4Protocol)
00683     return ERR_INTERNAL;       // no PASV for non-PF_INET connections
00684 
00685  if (m_extControl & pasvUnknown)
00686     return ERR_INTERNAL;       // already tried and got "unknown command"
00687 
00688   m_bPasv = true;
00689 
00690   /* Let's PASsiVe*/
00691   if( !ftpSendCmd("PASV") || (m_iRespType != 2) )
00692   {
00693     kDebug(7102) << "PASV attempt failed";
00694     // unknown command?
00695     if( m_iRespType == 5 )
00696     {
00697         kDebug(7102) << "disabling use of PASV";
00698         m_extControl |= pasvUnknown;
00699     }
00700     return ERR_INTERNAL;
00701   }
00702 
00703   // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)'
00704   // but anonftpd gives '227 =160,39,200,55,6,245'
00705   int i[6];
00706   const char *start = strchr(ftpResponse(3), '(');
00707   if ( !start )
00708     start = strchr(ftpResponse(3), '=');
00709   if ( !start ||
00710        ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
00711          sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
00712   {
00713     kError(7102) << "parsing IP and port numbers failed. String parsed: " << start;
00714     return ERR_INTERNAL;
00715   }
00716 
00717   // we ignore the host part on purpose for two reasons
00718   // a) it might be wrong anyway
00719   // b) it would make us being suceptible to a port scanning attack
00720 
00721   // now connect the data socket ...
00722   quint16 port = i[4] << 8 | i[5];
00723   kDebug(7102) << "Connecting to " << addr.toString() << " port " << port;
00724   m_data = KSocketFactory::synchronousConnectToHost("ftp-data", addr.toString(), port,
00725                                                     connectTimeout() * 1000);
00726 
00727   return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL;
00728 }
00729 
00730 /*
00731  * ftpOpenEPSVDataConnection - opens a data connection via EPSV
00732  */
00733 int Ftp::ftpOpenEPSVDataConnection()
00734 {
00735   assert(m_control != NULL);    // must have control connection socket
00736   assert(m_data == NULL);       // ... but no data connection
00737 
00738   QHostAddress address = m_control->peerAddress();
00739   int portnum;
00740 
00741   if (m_extControl & epsvUnknown)
00742     return ERR_INTERNAL;
00743 
00744   m_bPasv = true;
00745   if( !ftpSendCmd("EPSV") || (m_iRespType != 2) )
00746   {
00747     // unknown command?
00748     if( m_iRespType == 5 )
00749     {
00750        kDebug(7102) << "disabling use of EPSV";
00751        m_extControl |= epsvUnknown;
00752     }
00753     return ERR_INTERNAL;
00754   }
00755 
00756   const char *start = strchr(ftpResponse(3), '|');
00757   if ( !start || sscanf(start, "|||%d|", &portnum) != 1)
00758     return ERR_INTERNAL;
00759 
00760   m_data = KSocketFactory::synchronousConnectToHost("ftp-data", address.toString(), portnum,
00761                                                     connectTimeout() * 1000);
00762   return m_data->isOpen() ? 0 : ERR_INTERNAL;
00763 }
00764 
00765 /*
00766  * ftpOpenDataConnection - set up data connection
00767  *
00768  * The routine calls several ftpOpenXxxxConnection() helpers to find
00769  * the best connection mode. If a helper cannot connect if returns
00770  * ERR_INTERNAL - so this is not really an error! All other error
00771  * codes are treated as fatal, e.g. they are passed back to the caller
00772  * who is responsible for calling error(). ftpOpenPortDataConnection
00773  * can be called as last try and it does never return ERR_INTERNAL.
00774  *
00775  * @return 0 if successful, err code otherwise
00776  */
00777 int Ftp::ftpOpenDataConnection()
00778 {
00779   // make sure that we are logged on and have no data connection...
00780   assert( m_bLoggedOn );
00781   ftpCloseDataConnection();
00782 
00783   int  iErrCode = 0;
00784   int  iErrCodePASV = 0;  // Remember error code from PASV
00785 
00786   // First try passive (EPSV & PASV) modes
00787   if( !config()->readEntry("DisablePassiveMode", false) )
00788   {
00789     iErrCode = ftpOpenPASVDataConnection();
00790     if(iErrCode == 0)
00791       return 0; // success
00792     iErrCodePASV = iErrCode;
00793     ftpCloseDataConnection();
00794 
00795     if( !config()->readEntry("DisableEPSV", false) )
00796     {
00797       iErrCode = ftpOpenEPSVDataConnection();
00798       if(iErrCode == 0)
00799         return 0; // success
00800       ftpCloseDataConnection();
00801     }
00802 
00803     // if we sent EPSV ALL already and it was accepted, then we can't
00804     // use active connections any more
00805     if (m_extControl & epsvAllSent)
00806       return iErrCodePASV ? iErrCodePASV : iErrCode;
00807   }
00808 
00809   // fall back to port mode
00810   iErrCode = ftpOpenPortDataConnection();
00811   if(iErrCode == 0)
00812     return 0; // success
00813 
00814   ftpCloseDataConnection();
00815   // prefer to return the error code from PASV if any, since that's what should have worked in the first place
00816   return iErrCodePASV ? iErrCodePASV : iErrCode;
00817 }
00818 
00819 /*
00820  * ftpOpenPortDataConnection - set up data connection
00821  *
00822  * @return 0 if successful, err code otherwise (but never ERR_INTERNAL
00823  *         because this is the last connection mode that is tried)
00824  */
00825 int Ftp::ftpOpenPortDataConnection()
00826 {
00827   assert(m_control != NULL);    // must have control connection socket
00828   assert(m_data == NULL);       // ... but no data connection
00829 
00830   m_bPasv = false;
00831   if (m_extControl & eprtUnknown)
00832     return ERR_INTERNAL;
00833 
00834   if (!m_server)
00835     m_server = KSocketFactory::listen("ftp-data");
00836 
00837   if (!m_server->isListening()) {
00838     delete m_server;
00839     return ERR_COULD_NOT_LISTEN;
00840   }
00841 
00842   m_server->setMaxPendingConnections(1);
00843 
00844   QString command;
00845   QHostAddress localAddress = m_control->localAddress();
00846   if (localAddress.protocol() == QAbstractSocket::IPv4Protocol)
00847   {
00848     struct
00849     {
00850       quint32 ip4;
00851       quint16 port;
00852     } data;
00853     data.ip4 = localAddress.toIPv4Address();
00854     data.port = m_server->serverPort();
00855 
00856     unsigned char *pData = reinterpret_cast<unsigned char*>(&data);
00857     command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]);
00858   }
00859   else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol)
00860   {
00861     command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort());
00862   }
00863 
00864   if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) )
00865   {
00866     return 0;
00867   }
00868 
00869   delete m_server;
00870   return ERR_INTERNAL;
00871 }
00872 
00873 bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
00874                           int errorcode, KIO::fileoffset_t _offset )
00875 {
00876   int errCode = 0;
00877   if( !ftpDataMode(_mode) )
00878     errCode = ERR_COULD_NOT_CONNECT;
00879   else
00880     errCode = ftpOpenDataConnection();
00881 
00882   if(errCode != 0)
00883   {
00884     error(errCode, m_host);
00885     return false;
00886   }
00887 
00888   if ( _offset > 0 ) {
00889     // send rest command if offset > 0, this applies to retr and stor commands
00890     char buf[100];
00891     sprintf(buf, "rest %lld", _offset);
00892     if ( !ftpSendCmd( buf ) )
00893        return false;
00894     if( m_iRespType != 3 )
00895     {
00896       error( ERR_CANNOT_RESUME, _path ); // should never happen
00897       return false;
00898     }
00899   }
00900 
00901   QByteArray tmp = _command;
00902   QString errormessage;
00903 
00904   if ( !_path.isEmpty() ) {
00905     tmp += ' ';
00906     tmp += remoteEncoding()->encode(_path);
00907   }
00908 
00909   if( !ftpSendCmd( tmp ) || (m_iRespType != 1) )
00910   {
00911     if( _offset > 0 && strcmp(_command, "retr") == 0 && (m_iRespType == 4) )
00912       errorcode = ERR_CANNOT_RESUME;
00913     // The error here depends on the command
00914     errormessage = _path;
00915   }
00916 
00917   else
00918   {
00919     // Only now we know for sure that we can resume
00920     if ( _offset > 0 && strcmp(_command, "retr") == 0 )
00921       canResume();
00922 
00923     if(m_server && !m_data) {
00924       kDebug(7102) << "waiting for connection from remote.";
00925       m_server->waitForNewConnection(connectTimeout() * 1000);
00926       m_data = m_server->nextPendingConnection();
00927     }
00928 
00929     if(m_data) {
00930       kDebug(7102) << "connected with remote.";
00931       m_bBusy = true;              // cleared in ftpCloseCommand
00932       return true;
00933     }
00934 
00935     kDebug(7102) << "no connection received from remote.";
00936     errorcode=ERR_COULD_NOT_ACCEPT;
00937     errormessage=m_host;
00938     return false;
00939   }
00940 
00941   error(errorcode, errormessage);
00942   return false;
00943 }
00944 
00945 
00946 bool Ftp::ftpCloseCommand()
00947 {
00948     // first close data sockets (if opened), then read response that
00949     // we got for whatever was used in ftpOpenCommand ( should be 226 )
00950     delete m_data;
00951     m_data = NULL;
00952     delete m_server;
00953     m_server = NULL;
00954 
00955   if(!m_bBusy)
00956     return true;
00957 
00958   kDebug(7102) << "ftpCloseCommand: reading command result";
00959   m_bBusy = false;
00960 
00961   if(!ftpResponse(-1) || (m_iRespType != 2) )
00962   {
00963     kDebug(7102) << "ftpCloseCommand: no transfer complete message";
00964     return false;
00965   }
00966   return true;
00967 }
00968 
00969 void Ftp::mkdir( const KUrl & url, int permissions )
00970 {
00971   if( !ftpOpenConnection(loginImplicit) )
00972         return;
00973 
00974   QString path = remoteEncoding()->encode(url);
00975   QByteArray buf = "mkd ";
00976   buf += remoteEncoding()->encode(path);
00977 
00978   if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
00979   {
00980     QString currentPath( m_currentPath );
00981 
00982     // Check whether or not mkdir failed because
00983     // the directory already exists...
00984     if( ftpFolder( path, false ) )
00985     {
00986       error( ERR_DIR_ALREADY_EXIST, path );
00987       // Change the directory back to what it was...
00988       (void) ftpFolder( currentPath, false );
00989       return;
00990     }
00991 
00992     error( ERR_COULD_NOT_MKDIR, path );
00993     return;
00994   }
00995 
00996   if ( permissions != -1 )
00997   {
00998     // chmod the dir we just created, ignoring errors.
00999     (void) ftpChmod( path, permissions );
01000   }
01001 
01002   finished();
01003 }
01004 
01005 void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags )
01006 {
01007   if( !ftpOpenConnection(loginImplicit) )
01008         return;
01009 
01010   // The actual functionality is in ftpRename because put needs it
01011   if ( ftpRename( src.path(), dst.path(), flags ) )
01012     finished();
01013   else
01014     error( ERR_CANNOT_RENAME, src.path() );
01015 }
01016 
01017 bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags)
01018 {
01019     assert(m_bLoggedOn);
01020 
01021     // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793).
01022     if (!(jobFlags & KIO::Overwrite)) {
01023         if (ftpFileExists(dst)) {
01024             error(ERR_FILE_ALREADY_EXIST, dst);
01025             return false;
01026         }
01027     }
01028     if (ftpFolder(dst, false)) {
01029         error(ERR_DIR_ALREADY_EXIST, dst);
01030         return false;
01031     }
01032 
01033     // CD into parent folder
01034     const int pos = src.lastIndexOf('/');
01035     if (pos > 0) {
01036         if(!ftpFolder(src.left(pos+1), false))
01037             return false;
01038     }
01039 
01040     QByteArray from_cmd = "RNFR ";
01041     from_cmd += remoteEncoding()->encode(src.mid(pos+1));
01042     if (!ftpSendCmd(from_cmd) || (m_iRespType != 3))
01043         return false;
01044 
01045     QByteArray to_cmd = "RNTO ";
01046     to_cmd += remoteEncoding()->encode(dst);
01047     if (!ftpSendCmd(to_cmd) || (m_iRespType != 2))
01048         return false;
01049 
01050     return true;
01051 }
01052 
01053 void Ftp::del( const KUrl& url, bool isfile )
01054 {
01055   if( !ftpOpenConnection(loginImplicit) )
01056         return;
01057 
01058   // When deleting a directory, we must exit from it first
01059   // The last command probably went into it (to stat it)
01060   if ( !isfile )
01061     ftpFolder(remoteEncoding()->directory(url), false); // ignore errors
01062 
01063   QByteArray cmd = isfile ? "DELE " : "RMD ";
01064   cmd += remoteEncoding()->encode(url);
01065 
01066   if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01067     error( ERR_CANNOT_DELETE, url.path() );
01068   else
01069     finished();
01070 }
01071 
01072 bool Ftp::ftpChmod( const QString & path, int permissions )
01073 {
01074   assert( m_bLoggedOn );
01075 
01076   if(m_extControl & chmodUnknown)      // previous errors?
01077     return false;
01078 
01079   // we need to do bit AND 777 to get permissions, in case
01080   // we were sent a full mode (unlikely)
01081   QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 /*octal*/ ) + ' ';
01082   cmd += path;
01083 
01084   ftpSendCmd(remoteEncoding()->encode(cmd));
01085   if(m_iRespType == 2)
01086      return true;
01087 
01088   if(m_iRespCode == 500)
01089   {
01090     m_extControl |= chmodUnknown;
01091     kDebug(7102) << "ftpChmod: CHMOD not supported - disabling";
01092   }
01093   return false;
01094 }
01095 
01096 void Ftp::chmod( const KUrl & url, int permissions )
01097 {
01098   if( !ftpOpenConnection(loginImplicit) )
01099         return;
01100 
01101   if ( !ftpChmod( url.path(), permissions ) )
01102     error( ERR_CANNOT_CHMOD, url.path() );
01103   else
01104     finished();
01105 }
01106 
01107 void Ftp::ftpCreateUDSEntry( const QString & filename, FtpEntry& ftpEnt, UDSEntry& entry, bool isDir )
01108 {
01109   assert(entry.count() == 0); // by contract :-)
01110 
01111   entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01112   entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size );
01113   entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date );
01114   entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access );
01115   entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner );
01116   if ( !ftpEnt.group.isEmpty() )
01117   {
01118     entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group );
01119   }
01120 
01121   if ( !ftpEnt.link.isEmpty() )
01122   {
01123     entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link );
01124 
01125     KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) );
01126     // Links on ftp sites are often links to dirs, and we have no way to check
01127     // that. Let's do like Netscape : assume dirs generally.
01128     // But we do this only when the mimetype can't be known from the filename.
01129     // --> we do better than Netscape :-)
01130     if ( mime->name() == KMimeType::defaultMimeType() )
01131     {
01132       kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename;
01133       entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) );
01134       isDir = true;
01135     }
01136   }
01137 
01138   entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type );
01139   // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime);
01140   // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime);
01141 }
01142 
01143 
01144 void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir )
01145 {
01146     UDSEntry entry;
01147 
01148 
01149     entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01150     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG );
01151     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01152     // No details about size, ownership, group, etc.
01153 
01154     statEntry(entry);
01155     finished();
01156 }
01157 
01158 void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename )
01159 {
01160     // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source")
01161     // When e.g. uploading a file, we still need stat() to return "not found"
01162     // when the file doesn't exist.
01163     QString statSide = metaData("statSide");
01164     kDebug(7102) << "statSide=" << statSide;
01165     if ( statSide == "source" )
01166     {
01167         kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing";
01168         // MS Server is incapable of handling "list <blah>" in a case insensitive way
01169         // But "retr <blah>" works. So lie in stat(), to get going...
01170         //
01171         // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run
01172         // where listing permissions are denied, but downloading is still possible.
01173         ftpShortStatAnswer( filename, false /*file, not dir*/ );
01174 
01175         return;
01176     }
01177 
01178     error( ERR_DOES_NOT_EXIST, path );
01179 }
01180 
01181 void Ftp::stat(const KUrl &url)
01182 {
01183   kDebug(7102) << "path=" << url.path();
01184   if( !ftpOpenConnection(loginImplicit) )
01185         return;
01186 
01187   QString path = QDir::cleanPath( url.path() );
01188   kDebug(7102) << "cleaned path=" << path;
01189 
01190   // We can't stat root, but we know it's a dir.
01191   if( path.isEmpty() || path == "/" )
01192   {
01193     UDSEntry entry;
01194     //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) );
01195     entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) );
01196     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01197     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01198     entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) );
01199     entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) );
01200     // no size
01201 
01202     statEntry( entry );
01203     finished();
01204     return;
01205   }
01206 
01207   KUrl tempurl( url );
01208   tempurl.setPath( path ); // take the clean one
01209   QString listarg; // = tempurl.directory(KUrl::ObeyTrailingSlash);
01210   QString parentDir;
01211   QString filename = tempurl.fileName();
01212   Q_ASSERT(!filename.isEmpty());
01213   QString search = filename;
01214 
01215   // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info)
01216   // if it doesn't work, it's a file (and then we'll use dir filename)
01217   bool isDir = ftpFolder(path, false);
01218 
01219   // if we're only interested in "file or directory", we should stop here
01220   QString sDetails = metaData("details");
01221   int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
01222   kDebug(7102) << "details=" << details;
01223   if ( details == 0 )
01224   {
01225      if ( !isDir && !ftpFileExists(path) ) // ok, not a dir -> is it a file ?
01226      {  // no -> it doesn't exist at all
01227         ftpStatAnswerNotFound( path, filename );
01228         return;
01229      }
01230      ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done
01231      return;
01232   }
01233 
01234   if (!isDir)
01235   {
01236     // It is a file or it doesn't exist, try going to parent directory
01237     parentDir = tempurl.directory(KUrl::AppendTrailingSlash);
01238     // With files we can do "LIST <filename>" to avoid listing the whole dir
01239     listarg = filename;
01240   }
01241   else
01242   {
01243     // --- New implementation:
01244     // Don't list the parent dir. Too slow, might not show it, etc.
01245     // Just return that it's a dir.
01246     UDSEntry entry;
01247     entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01248     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01249     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01250     // No clue about size, ownership, group, etc.
01251 
01252     statEntry(entry);
01253     finished();
01254     return;
01255   }
01256 
01257   // Now cwd the parent dir, to prepare for listing
01258   if( !ftpFolder(parentDir, true) )
01259     return;
01260 
01261   if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) )
01262   {
01263     kError(7102) << "COULD NOT LIST";
01264     return;
01265   }
01266   kDebug(7102) << "Starting of list was ok";
01267 
01268   Q_ASSERT( !search.isEmpty() && search != "/" );
01269 
01270   bool bFound = false;
01271   KUrl      linkURL;
01272   FtpEntry  ftpEnt;
01273   while( ftpReadDir(ftpEnt) )
01274   {
01275     // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at)
01276     // return only the filename when doing "dir /full/path/to/file"
01277     if (!bFound) {
01278         if ( ( search == ftpEnt.name || filename == ftpEnt.name ) ) {
01279             if ( !filename.isEmpty() ) {
01280               bFound = true;
01281               UDSEntry entry;
01282               ftpCreateUDSEntry( filename, ftpEnt, entry, isDir );
01283               statEntry( entry );
01284             }
01285         }
01286     }
01287 
01288     // kDebug(7102) << ftpEnt.name;
01289   }
01290 
01291   ftpCloseCommand();        // closes the data connection only
01292 
01293   if ( !bFound )
01294   {
01295     ftpStatAnswerNotFound( path, filename );
01296     return;
01297   }
01298 
01299   if ( !linkURL.isEmpty() )
01300   {
01301       if ( linkURL == url || linkURL == tempurl )
01302       {
01303           error( ERR_CYCLIC_LINK, linkURL.prettyUrl() );
01304           return;
01305       }
01306       Ftp::stat( linkURL );
01307       return;
01308   }
01309 
01310   kDebug(7102) << "stat : finished successfully";
01311   finished();
01312 }
01313 
01314 
01315 void Ftp::listDir( const KUrl &url )
01316 {
01317     kDebug(7102) << url;
01318   if( !ftpOpenConnection(loginImplicit) )
01319         return;
01320 
01321   // No path specified ?
01322   QString path = url.path();
01323   if ( path.isEmpty() )
01324   {
01325     KUrl realURL;
01326     realURL.setProtocol( "ftp" );
01327     if ( m_user != FTP_LOGIN )
01328       realURL.setUser( m_user );
01329     // We set the password, so that we don't ask for it if it was given
01330     if ( m_pass != FTP_PASSWD )
01331       realURL.setPass( m_pass );
01332     realURL.setHost( m_host );
01333     if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
01334         realURL.setPort( m_port );
01335     if ( m_initialPath.isEmpty() )
01336         m_initialPath = "/";
01337     realURL.setPath( m_initialPath );
01338     kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl();
01339     redirection( realURL );
01340     finished();
01341     return;
01342   }
01343 
01344     kDebug(7102) << "hunting for path" << path;
01345 
01346     if (!ftpOpenDir(path)) {
01347         if (ftpFileExists(path)) {
01348             error(ERR_IS_FILE, path);
01349         } else {
01350             // not sure which to emit
01351             //error( ERR_DOES_NOT_EXIST, path );
01352             error( ERR_CANNOT_ENTER_DIRECTORY, path );
01353         }
01354         return;
01355     }
01356 
01357   UDSEntry entry;
01358   FtpEntry  ftpEnt;
01359   while( ftpReadDir(ftpEnt) )
01360   {
01361     //kDebug(7102) << ftpEnt.name;
01362     //Q_ASSERT( !ftpEnt.name.isEmpty() );
01363     if ( !ftpEnt.name.isEmpty() )
01364     {
01365       //if ( S_ISDIR( (mode_t)ftpEnt.type ) )
01366       //   kDebug(7102) << "is a dir";
01367       //if ( !ftpEnt.link.isEmpty() )
01368       //   kDebug(7102) << "is a link to " << ftpEnt.link;
01369       entry.clear();
01370       ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
01371       listEntry( entry, false );
01372     }
01373   }
01374   listEntry( entry, true ); // ready
01375   ftpCloseCommand();        // closes the data connection only
01376   finished();
01377 }
01378 
01379 void Ftp::slave_status()
01380 {
01381   kDebug(7102) << "Got slave_status host = " << (!m_host.toAscii().isEmpty() ? m_host.toAscii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]";
01382   slaveStatus( m_host, m_bLoggedOn );
01383 }
01384 
01385 bool Ftp::ftpOpenDir( const QString & path )
01386 {
01387   //QString path( _url.path(KUrl::RemoveTrailingSlash) );
01388 
01389   // We try to change to this directory first to see whether it really is a directory.
01390   // (And also to follow symlinks)
01391   QString tmp = path.isEmpty() ? QString("/") : path;
01392 
01393   // We get '550', whether it's a file or doesn't exist...
01394   if( !ftpFolder(tmp, false) )
01395       return false;
01396 
01397   // Don't use the path in the list command:
01398   // We changed into this directory anyway - so it's enough just to send "list".
01399   // We use '-a' because the application MAY be interested in dot files.
01400   // The only way to really know would be to have a metadata flag for this...
01401   // Since some windows ftp server seems not to support the -a argument, we use a fallback here.
01402   // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com)
01403   if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01404   {
01405     if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01406     {
01407       kWarning(7102) << "Can't open for listing";
01408       return false;
01409     }
01410   }
01411   kDebug(7102) << "Starting of list was ok";
01412   return true;
01413 }
01414 
01415 bool Ftp::ftpReadDir(FtpEntry& de)
01416 {
01417   assert(m_data != NULL);
01418 
01419   // get a line from the data connecetion ...
01420   while( true )
01421   {
01422     while (!m_data->canReadLine() && m_data->waitForReadyRead()) {}
01423     QByteArray data = m_data->readLine();
01424     if (data.size() == 0)
01425       break;
01426 
01427     const char* buffer = data.data();
01428     kDebug(7102) << "dir > " << buffer;
01429 
01430     //Normally the listing looks like
01431     // -rw-r--r--   1 dfaure   dfaure        102 Nov  9 12:30 log
01432     // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442)
01433     // d [RWCEAFMS] Admin                     512 Oct 13  2004 PSI
01434 
01435     // we should always get the following 5 fields ...
01436     const char *p_access, *p_junk, *p_owner, *p_group, *p_size;
01437     if( (p_access = strtok((char*)buffer," ")) == 0) continue;
01438     if( (p_junk  = strtok(NULL," ")) == 0) continue;
01439     if( (p_owner = strtok(NULL," ")) == 0) continue;
01440     if( (p_group = strtok(NULL," ")) == 0) continue;
01441     if( (p_size  = strtok(NULL," ")) == 0) continue;
01442 
01443     //kDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size;
01444 
01445     de.access = 0;
01446     if ( strlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware
01447       de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions
01448     }
01449 
01450     const char *p_date_1, *p_date_2, *p_date_3, *p_name;
01451 
01452     // A special hack for "/dev". A listing may look like this:
01453     // crw-rw-rw-   1 root     root       1,   5 Jun 29  1997 zero
01454     // So we just ignore the number in front of the ",". Ok, its a hack :-)
01455     if ( strchr( p_size, ',' ) != 0L )
01456     {
01457       //kDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)";
01458       if ((p_size = strtok(NULL," ")) == 0)
01459         continue;
01460     }
01461 
01462     // Check whether the size we just read was really the size
01463     // or a month (this happens when the server lists no group)
01464     // Used to be the case on sunsite.uio.no, but not anymore
01465     // This is needed for the Netware case, too.
01466     if ( !isdigit( *p_size ) )
01467     {
01468       p_date_1 = p_size;
01469       p_size = p_group;
01470       p_group = 0;
01471       //kDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1;
01472     }
01473     else
01474     {
01475       p_date_1 = strtok(NULL," ");
01476       //kDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1;
01477     }
01478 
01479     if ( p_date_1 != 0 &&
01480          (p_date_2 = strtok(NULL," ")) != 0 &&
01481          (p_date_3 = strtok(NULL," ")) != 0 &&
01482          (p_name = strtok(NULL,"\r\n")) != 0 )
01483     {
01484       {
01485         QByteArray tmp( p_name );
01486         if ( p_access[0] == 'l' )
01487         {
01488           int i = tmp.lastIndexOf( " -> " );
01489           if ( i != -1 ) {
01490             de.link = remoteEncoding()->decode(p_name + i + 4);
01491             tmp.truncate( i );
01492           }
01493           else
01494             de.link.clear();
01495         }
01496         else
01497           de.link.clear();
01498 
01499         if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/'
01500           tmp.remove( 0, 1 );
01501 
01502         if (tmp.indexOf('/') != -1)
01503           continue; // Don't trick us!
01504         // Some sites put more than one space between the date and the name
01505         // e.g. ftp://ftp.uni-marburg.de/mirror/
01506         de.name     = remoteEncoding()->decode(tmp.trimmed());
01507       }
01508 
01509       de.type = S_IFREG;
01510       switch ( p_access[0] ) {
01511       case 'd':
01512         de.type = S_IFDIR;
01513         break;
01514       case 's':
01515         de.type = S_IFSOCK;
01516         break;
01517       case 'b':
01518         de.type = S_IFBLK;
01519         break;
01520       case 'c':
01521         de.type = S_IFCHR;
01522         break;
01523       case 'l':
01524         de.type = S_IFREG;
01525         // we don't set S_IFLNK here.  de.link says it.
01526         break;
01527       default:
01528         break;
01529       }
01530 
01531       if ( p_access[1] == 'r' )
01532         de.access |= S_IRUSR;
01533       if ( p_access[2] == 'w' )
01534         de.access |= S_IWUSR;
01535       if ( p_access[3] == 'x' || p_access[3] == 's' )
01536         de.access |= S_IXUSR;
01537       if ( p_access[4] == 'r' )
01538         de.access |= S_IRGRP;
01539       if ( p_access[5] == 'w' )
01540         de.access |= S_IWGRP;
01541       if ( p_access[6] == 'x' || p_access[6] == 's' )
01542         de.access |= S_IXGRP;
01543       if ( p_access[7] == 'r' )
01544         de.access |= S_IROTH;
01545       if ( p_access[8] == 'w' )
01546         de.access |= S_IWOTH;
01547       if ( p_access[9] == 'x' || p_access[9] == 't' )
01548         de.access |= S_IXOTH;
01549       if ( p_access[3] == 's' || p_access[3] == 'S' )
01550         de.access |= S_ISUID;
01551       if ( p_access[6] == 's' || p_access[6] == 'S' )
01552         de.access |= S_ISGID;
01553       if ( p_access[9] == 't' || p_access[9] == 'T' )
01554         de.access |= S_ISVTX;
01555 
01556       de.owner    = remoteEncoding()->decode(p_owner);
01557       de.group    = remoteEncoding()->decode(p_group);
01558       de.size     = charToLongLong(p_size);
01559 
01560       // Parsing the date is somewhat tricky
01561       // Examples : "Oct  6 22:49", "May 13  1999"
01562 
01563       // First get current time - we need the current month and year
01564       time_t currentTime = time( 0L );
01565       struct tm * tmptr = gmtime( &currentTime );
01566       int currentMonth = tmptr->tm_mon;
01567       //kDebug(7102) << "Current time :" << asctime( tmptr );
01568       // Reset time fields
01569       tmptr->tm_sec = 0;
01570       tmptr->tm_min = 0;
01571       tmptr->tm_hour = 0;
01572       // Get day number (always second field)
01573       tmptr->tm_mday = atoi( p_date_2 );
01574       // Get month from first field
01575       // NOTE : no, we don't want to use KLocale here
01576       // It seems all FTP servers use the English way
01577       //kDebug(7102) << "Looking for month " << p_date_1;
01578       static const char * s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
01579                                            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
01580       for ( int c = 0 ; c < 12 ; c ++ )
01581         if ( !strcmp( p_date_1, s_months[c]) )
01582         {
01583           //kDebug(7102) << "Found month " << c << " for " << p_date_1;
01584           tmptr->tm_mon = c;
01585           break;
01586         }
01587 
01588       // Parse third field
01589       if ( strlen( p_date_3 ) == 4 ) // 4 digits, looks like a year
01590         tmptr->tm_year = atoi( p_date_3 ) - 1900;
01591       else
01592       {
01593         // otherwise, the year is implicit
01594         // according to man ls, this happens when it is between than 6 months
01595         // old and 1 hour in the future.
01596         // So the year is : current year if tm_mon <= currentMonth+1
01597         // otherwise current year minus one
01598         // (The +1 is a security for the "+1 hour" at the end of the month issue)
01599         if ( tmptr->tm_mon > currentMonth + 1 )
01600           tmptr->tm_year--;
01601 
01602         // and p_date_3 contains probably a time
01603         char * semicolon;
01604         if ( ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
01605         {
01606           *semicolon = '\0';
01607           tmptr->tm_min = atoi( semicolon + 1 );
01608           tmptr->tm_hour = atoi( p_date_3 );
01609         }
01610         else
01611           kWarning(7102) << "Can't parse third field " << p_date_3;
01612       }
01613 
01614       //kDebug(7102) << asctime( tmptr );
01615       de.date = mktime( tmptr );
01616       return true;
01617     }
01618   } // line invalid, loop to get another line
01619   return false;
01620 }
01621 
01622 //===============================================================================
01623 // public: get           download file from server
01624 // helper: ftpGet        called from get() and copy()
01625 //===============================================================================
01626 void Ftp::get( const KUrl & url )
01627 {
01628     kDebug(7102) << url;
01629   int iError = 0;
01630   ftpGet(iError, -1, url, 0);               // iError gets status
01631   if(iError)                                // can have only server side errs
01632      error(iError, url.path());
01633   ftpCloseCommand();                        // must close command!
01634 }
01635 
01636 Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset)
01637 {
01638   // Calls error() by itself!
01639   if( !ftpOpenConnection(loginImplicit) )
01640     return statusServerError;
01641 
01642   // Try to find the size of the file (and check that it exists at
01643   // the same time). If we get back a 550, "File does not exist"
01644   // or "not a plain file", check if it is a directory. If it is a
01645   // directory, return an error; otherwise simply try to retrieve
01646   // the request...
01647   if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) &&
01648        ftpFolder(url.path(), false) )
01649   {
01650     // Ok it's a dir in fact
01651     kDebug(7102) << "ftpGet: it is a directory in fact";
01652     iError = ERR_IS_DIRECTORY;
01653     return statusServerError;
01654   }
01655 
01656   QString resumeOffset = metaData("resume");
01657   if ( !resumeOffset.isEmpty() )
01658   {
01659     llOffset = resumeOffset.toLongLong();
01660     kDebug(7102) << "ftpGet: got offset from metadata : " << llOffset;
01661   }
01662 
01663   if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) )
01664   {
01665     kWarning(7102) << "ftpGet: Can't open for reading";
01666     return statusServerError;
01667   }
01668 
01669   // Read the size from the response string
01670   if(m_size == UnknownSize)
01671   {
01672     const char* psz = strrchr( ftpResponse(4), '(' );
01673     if(psz) m_size = charToLongLong(psz+1);
01674     if (!m_size) m_size = UnknownSize;
01675   }
01676 
01677   KIO::filesize_t bytesLeft = 0;
01678   if ( m_size != UnknownSize )
01679     bytesLeft = m_size - llOffset;
01680 
01681   kDebug(7102) << "ftpGet: starting with offset=" << llOffset;
01682   KIO::fileoffset_t processed_size = llOffset;
01683 
01684   QByteArray array;
01685   bool mimetypeEmitted = false;
01686   char buffer[maximumIpcSize];
01687   // start with small data chunks in case of a slow data source (modem)
01688   // - unfortunately this has a negative impact on performance for large
01689   // - files - so we will increase the block size after a while ...
01690   int iBlockSize = initialIpcSize;
01691   int iBufferCur = 0;
01692 
01693   while(m_size == UnknownSize || bytesLeft > 0)
01694   {  // let the buffer size grow if the file is larger 64kByte ...
01695     if(processed_size-llOffset > 1024 * 64)
01696       iBlockSize = maximumIpcSize;
01697 
01698     // read the data and detect EOF or error ...
01699     if(iBlockSize+iBufferCur > (int)sizeof(buffer))
01700       iBlockSize = sizeof(buffer) - iBufferCur;
01701     if (m_data->bytesAvailable() == 0)
01702       m_data->waitForReadyRead();
01703     int n = m_data->read( buffer+iBufferCur, iBlockSize );
01704     if(n <= 0)
01705     {   // this is how we detect EOF in case of unknown size
01706       if( m_size == UnknownSize && n == 0 )
01707         break;
01708       // unexpected eof. Happens when the daemon gets killed.
01709       iError = ERR_COULD_NOT_READ;
01710       return statusServerError;
01711     }
01712     processed_size += n;
01713 
01714     // collect very small data chunks in buffer before processing ...
01715     if(m_size != UnknownSize)
01716     {
01717       bytesLeft -= n;
01718       iBufferCur += n;
01719       if(iBufferCur < mimimumMimeSize && bytesLeft > 0)
01720       {
01721         processedSize( processed_size );
01722         continue;
01723       }
01724       n = iBufferCur;
01725       iBufferCur = 0;
01726     }
01727 
01728     // get the mime type and set the total size ...
01729     if(!mimetypeEmitted)
01730     {
01731       mimetypeEmitted = true;
01732       array = QByteArray::fromRawData(buffer, n);
01733       KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), array);
01734       array.clear();
01735       kDebug(7102) << "ftpGet: Emitting mimetype " << mime->name();
01736       mimeType( mime->name() );
01737       if( m_size != UnknownSize )   // Emit total size AFTER mimetype
01738         totalSize( m_size );
01739     }
01740 
01741     // write output file or pass to data pump ...
01742     if(iCopyFile == -1)
01743     {
01744         array = QByteArray::fromRawData(buffer, n);
01745         data( array );
01746         array.clear();
01747     }
01748     else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0)
01749        return statusClientError;              // client side error
01750     processedSize( processed_size );
01751   }
01752 
01753   kDebug(7102) << "ftpGet: done";
01754   if(iCopyFile == -1)          // must signal EOF to data pump ...
01755     data(array);               // array is empty and must be empty!
01756 
01757   processedSize( m_size == UnknownSize ? processed_size : m_size );
01758   kDebug(7102) << "ftpGet: emitting finished()";
01759   finished();
01760   return statusSuccess;
01761 }
01762 
01763 #if 0
01764   void Ftp::mimetype( const KUrl& url )
01765   {
01766     if( !ftpOpenConnection(loginImplicit) )
01767           return;
01768 
01769     if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
01770       kWarning(7102) << "Can't open for reading";
01771       return;
01772     }
01773     char buffer[ 2048 ];
01774     QByteArray array;
01775     // Get one chunk of data only and send it, KIO::Job will determine the
01776     // mimetype from it using KMimeMagic
01777     int n = m_data->read( buffer, 2048 );
01778     array.setRawData(buffer, n);
01779     data( array );
01780     array.resetRawData(buffer, n);
01781 
01782     kDebug(7102) << "aborting";
01783     ftpAbortTransfer();
01784 
01785     kDebug(7102) << "finished";
01786     finished();
01787     kDebug(7102) << "after finished";
01788   }
01789 
01790   void Ftp::ftpAbortTransfer()
01791   {
01792     // RFC 959, page 34-35
01793     // IAC (interpret as command) = 255 ; IP (interrupt process) = 254
01794     // DM = 242 (data mark)
01795      char msg[4];
01796      // 1. User system inserts the Telnet "Interrupt Process" (IP) signal
01797      //   in the Telnet stream.
01798      msg[0] = (char) 255; //IAC
01799      msg[1] = (char) 254; //IP
01800      (void) send(sControl, msg, 2, 0);
01801      // 2. User system sends the Telnet "Sync" signal.
01802      msg[0] = (char) 255; //IAC
01803      msg[1] = (char) 242; //DM
01804      if (send(sControl, msg, 2, MSG_OOB) != 2)
01805        ; // error...
01806 
01807      // Send ABOR
01808      kDebug(7102) << "send ABOR";
01809      QCString buf = "ABOR\r\n";
01810      if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 )  {
01811        error( ERR_COULD_NOT_WRITE, QString() );
01812        return;
01813      }
01814 
01815      //
01816      kDebug(7102) << "read resp";
01817      if ( readresp() != '2' )
01818      {
01819        error( ERR_COULD_NOT_READ, QString() );
01820        return;
01821      }
01822 
01823     kDebug(7102) << "close sockets";
01824     closeSockets();
01825   }
01826 #endif
01827 
01828 //===============================================================================
01829 // public: put           upload file to server
01830 // helper: ftpPut        called from put() and copy()
01831 //===============================================================================
01832 void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags)
01833 {
01834     kDebug(7102) << url;
01835   int iError = 0;                           // iError gets status
01836   ftpPut(iError, -1, url, permissions, flags);
01837   if(iError)                                // can have only server side errs
01838      error(iError, url.path());
01839   ftpCloseCommand();                        // must close command!
01840 }
01841 
01842 Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url,
01843                             int permissions, KIO::JobFlags flags)
01844 {
01845   if( !ftpOpenConnection(loginImplicit) )
01846     return statusServerError;
01847 
01848   // Don't use mark partial over anonymous FTP.
01849   // My incoming dir allows put but not rename...
01850   bool bMarkPartial;
01851   if (m_user.isEmpty () || m_user == FTP_LOGIN)
01852     bMarkPartial = false;
01853   else
01854     bMarkPartial = config()->readEntry("MarkPartial", true);
01855 
01856   QString dest_orig = dest_url.path();
01857   QString dest_part( dest_orig );
01858   dest_part += ".part";
01859 
01860   if ( ftpSize( dest_orig, 'I' ) )
01861   {
01862     if ( m_size == 0 )
01863     { // delete files with zero size
01864       QByteArray cmd = "DELE ";
01865       cmd += remoteEncoding()->encode(dest_orig);
01866       if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01867       {
01868         iError = ERR_CANNOT_DELETE_PARTIAL;
01869         return statusServerError;
01870       }
01871     }
01872     else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
01873     {
01874        iError = ERR_FILE_ALREADY_EXIST;
01875        return statusServerError;
01876     }
01877     else if ( bMarkPartial )
01878     { // when using mark partial, append .part extension
01879       if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) )
01880       {
01881         iError = ERR_CANNOT_RENAME_PARTIAL;
01882         return statusServerError;
01883       }
01884     }
01885     // Don't chmod an existing file
01886     permissions = -1;
01887   }
01888   else if ( bMarkPartial && ftpSize( dest_part, 'I' ) )
01889   { // file with extension .part exists
01890     if ( m_size == 0 )
01891     {  // delete files with zero size
01892       QByteArray cmd = "DELE ";
01893       cmd += remoteEncoding()->encode(dest_part);
01894       if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01895       {
01896         iError = ERR_CANNOT_DELETE_PARTIAL;
01897         return statusServerError;
01898       }
01899     }
01900     else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
01901     {
01902       flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags;
01903       if (!(flags & KIO::Resume))
01904       {
01905         iError = ERR_FILE_ALREADY_EXIST;
01906         return statusServerError;
01907       }
01908     }
01909   }
01910   else
01911     m_size = 0;
01912 
01913   QString dest;
01914 
01915   // if we are using marking of partial downloads -> add .part extension
01916   if ( bMarkPartial ) {
01917     kDebug(7102) << "Adding .part extension to " << dest_orig;
01918     dest = dest_part;
01919   } else
01920     dest = dest_orig;
01921 
01922   KIO::fileoffset_t offset = 0;
01923 
01924   // set the mode according to offset
01925   if( (flags & KIO::Resume) && m_size > 0 )
01926   {
01927     offset = m_size;
01928     if(iCopyFile != -1)
01929     {
01930       if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 )
01931       {
01932         iError = ERR_CANNOT_RESUME;
01933         return statusClientError;
01934       }
01935     }
01936   }
01937 
01938   if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) )
01939      return statusServerError;
01940 
01941   kDebug(7102) << "ftpPut: starting with offset=" << offset;
01942   KIO::fileoffset_t processed_size = offset;
01943 
01944   QByteArray buffer;
01945   int result;
01946   int iBlockSize = initialIpcSize;
01947   // Loop until we got 'dataEnd'
01948   do
01949   {
01950     if(iCopyFile == -1)
01951     {
01952       dataReq(); // Request for data
01953       result = readData( buffer );
01954     }
01955     else
01956     { // let the buffer size grow if the file is larger 64kByte ...
01957       if(processed_size-offset > 1024 * 64)
01958         iBlockSize = maximumIpcSize;
01959       buffer.resize(iBlockSize);
01960       result = ::read(iCopyFile, buffer.data(), buffer.size());
01961       if(result < 0)
01962         iError = ERR_COULD_NOT_WRITE;
01963       else
01964         buffer.resize(result);
01965     }
01966 
01967     if (result > 0)
01968     {
01969       m_data->write( buffer );
01970       while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {}
01971       processed_size += result;
01972       processedSize (processed_size);
01973     }
01974   }
01975   while ( result > 0 );
01976 
01977   if (result != 0) // error
01978   {
01979     ftpCloseCommand();               // don't care about errors
01980     kDebug(7102) << "Error during 'put'. Aborting.";
01981     if (bMarkPartial)
01982     {
01983       // Remove if smaller than minimum size
01984       if ( ftpSize( dest, 'I' ) &&
01985            ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
01986       {
01987         QByteArray cmd = "DELE ";
01988         cmd += remoteEncoding()->encode(dest);
01989         (void) ftpSendCmd( cmd );
01990       }
01991     }
01992     return statusServerError;
01993   }
01994 
01995   if ( !ftpCloseCommand() )
01996   {
01997     iError = ERR_COULD_NOT_WRITE;
01998     return statusServerError;
01999   }
02000 
02001   // after full download rename the file back to original name
02002   if ( bMarkPartial )
02003   {
02004     kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")";
02005     if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) )
02006     {
02007       iError = ERR_CANNOT_RENAME_PARTIAL;
02008       return statusServerError;
02009     }
02010   }
02011 
02012   // set final permissions
02013   if ( permissions != -1 )
02014   {
02015     if ( m_user == FTP_LOGIN )
02016       kDebug(7102) << "Trying to chmod over anonymous FTP ???";
02017     // chmod the file we just put
02018     if ( ! ftpChmod( dest_orig, permissions ) )
02019     {
02020         // To be tested
02021         //if ( m_user != FTP_LOGIN )
02022         //    warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) );
02023     }
02024   }
02025 
02026   // We have done our job => finish
02027   finished();
02028   return statusSuccess;
02029 }
02030 
02031 
02034 bool Ftp::ftpSize( const QString & path, char mode )
02035 {
02036   m_size = UnknownSize;
02037   if( !ftpDataMode(mode) )
02038       return false;
02039 
02040   QByteArray buf;
02041   buf = "SIZE ";
02042   buf += remoteEncoding()->encode(path);
02043   if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02044     return false;
02045 
02046   // skip leading "213 " (response code)
02047   const char* psz = ftpResponse(4);
02048   if(!psz)
02049     return false;
02050   m_size = charToLongLong(psz);
02051   if (!m_size) m_size = UnknownSize;
02052   return true;
02053 }
02054 
02055 bool Ftp::ftpFileExists(const QString& path)
02056 {
02057   QByteArray buf;
02058   buf = "SIZE ";
02059   buf += remoteEncoding()->encode(path);
02060   if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02061     return false;
02062 
02063   // skip leading "213 " (response code)
02064   const char* psz = ftpResponse(4);
02065   return psz != 0;
02066 }
02067 
02068 // Today the differences between ASCII and BINARY are limited to
02069 // CR or CR/LF line terminators. Many servers ignore ASCII (like
02070 // win2003 -or- vsftp with default config). In the early days of
02071 // computing, when even text-files had structure, this stuff was
02072 // more important.
02073 // Theoretically "list" could return different results in ASCII
02074 // and BINARY mode. But again, most servers ignore ASCII here.
02075 bool Ftp::ftpDataMode(char cMode)
02076 {
02077   if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I';
02078   else if(cMode == 'a') cMode = 'A';
02079   else if(cMode != 'A') cMode = 'I';
02080 
02081   kDebug(7102) << "want" << cMode << "has" << m_cDataMode;
02082   if(m_cDataMode == cMode)
02083     return true;
02084 
02085   QByteArray buf = "TYPE ";
02086   buf += cMode;
02087   if( !ftpSendCmd(buf) || (m_iRespType != 2) )
02088       return false;
02089   m_cDataMode = cMode;
02090   return true;
02091 }
02092 
02093 
02094 bool Ftp::ftpFolder(const QString& path, bool bReportError)
02095 {
02096   QString newPath = path;
02097   int iLen = newPath.length();
02098   if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1);
02099 
02100   //kDebug(7102) << "want" << newPath << "has" << m_currentPath;
02101   if(m_currentPath == newPath)
02102     return true;
02103 
02104   QByteArray tmp = "cwd ";
02105   tmp += remoteEncoding()->encode(newPath);
02106   if( !ftpSendCmd(tmp) )
02107     return false;                  // connection failure
02108   if(m_iRespType != 2)
02109   {
02110     if(bReportError)
02111       error(ERR_CANNOT_ENTER_DIRECTORY, path);
02112     return false;                  // not a folder
02113   }
02114   m_currentPath = newPath;
02115   return true;
02116 }
02117 
02118 
02119 //===============================================================================
02120 // public: copy          don't use kio data pump if one side is a local file
02121 // helper: ftpCopyPut    called from copy() on upload
02122 // helper: ftpCopyGet    called from copy() on download
02123 //===============================================================================
02124 void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags )
02125 {
02126   int iError = 0;
02127   int iCopyFile = -1;
02128   StatusCode cs = statusSuccess;
02129   bool bSrcLocal = src.isLocalFile();
02130   bool bDestLocal = dest.isLocalFile();
02131   QString  sCopyFile;
02132 
02133   if(bSrcLocal && !bDestLocal)                    // File -> Ftp
02134   {
02135     sCopyFile = src.toLocalFile();
02136     kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path();
02137     cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags);
02138     if( cs == statusServerError) sCopyFile = dest.url();
02139   }
02140   else if(!bSrcLocal && bDestLocal)               // Ftp -> File
02141   {
02142     sCopyFile = dest.toLocalFile();
02143     kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile;
02144     cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags);
02145     if( cs == statusServerError ) sCopyFile = src.url();
02146   }
02147   else {
02148     error( ERR_UNSUPPORTED_ACTION, QString() );
02149     return;
02150   }
02151 
02152   // perform clean-ups and report error (if any)
02153   if(iCopyFile != -1)
02154     ::close(iCopyFile);
02155   if(iError)
02156     error(iError, sCopyFile);
02157   ftpCloseCommand();                        // must close command!
02158 }
02159 
02160 
02161 Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile,
02162                                 const KUrl& url, int permissions, KIO::JobFlags flags)
02163 {
02164   // check if source is ok ...
02165   KDE_struct_stat buff;
02166   const QByteArray sSrc( QFile::encodeName(sCopyFile) );
02167   bool bSrcExists = (KDE_stat( sSrc.data(), &buff ) != -1);
02168   if(bSrcExists)
02169   { if(S_ISDIR(buff.st_mode))
02170     {
02171       iError = ERR_IS_DIRECTORY;
02172       return statusClientError;
02173     }
02174   }
02175   else
02176   {
02177     iError = ERR_DOES_NOT_EXIST;
02178     return statusClientError;
02179   }
02180 
02181   iCopyFile = KDE_open( sSrc.data(), O_RDONLY );
02182   if(iCopyFile == -1)
02183   {
02184     iError = ERR_CANNOT_OPEN_FOR_READING;
02185     return statusClientError;
02186   }
02187 
02188   // delegate the real work (iError gets status) ...
02189   totalSize(buff.st_size);
02190 #ifdef  ENABLE_CAN_RESUME
02191   return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume);
02192 #else
02193   return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume);
02194 #endif
02195 }
02196 
02197 
02198 Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile,
02199                                 const KUrl& url, int permissions, KIO::JobFlags flags)
02200 {
02201   // check if destination is ok ...
02202   KDE_struct_stat buff;
02203   const QByteArray sDest( QFile::encodeName(sCopyFile) );
02204   bool bDestExists = (KDE_stat( sDest.data(), &buff ) != -1);
02205   if(bDestExists)
02206   { if(S_ISDIR(buff.st_mode))
02207     {
02208       iError = ERR_IS_DIRECTORY;
02209       return statusClientError;
02210     }
02211     if(!(flags & KIO::Overwrite))
02212     {
02213       iError = ERR_FILE_ALREADY_EXIST;
02214       return statusClientError;
02215     }
02216   }
02217 
02218   // do we have a ".part" file?
02219   const QByteArray sPart = QFile::encodeName(sCopyFile + ".part");
02220   bool bResume = false;
02221   bool bPartExists = (KDE_stat( sPart.data(), &buff ) != -1);
02222   bool bMarkPartial = config()->readEntry("MarkPartial", true);
02223   if(bMarkPartial && bPartExists && buff.st_size > 0)
02224   { // must not be a folder! please fix a similar bug in kio_file!!
02225     if(S_ISDIR(buff.st_mode))
02226     {
02227       iError = ERR_DIR_ALREADY_EXIST;
02228       return statusClientError;                            // client side error
02229     }
02230     //doesn't work for copy? -> design flaw?
02231 #ifdef  ENABLE_CAN_RESUME
02232     bResume = canResume( buff.st_size );
02233 #else
02234     bResume = true;
02235 #endif
02236   }
02237 
02238   if(bPartExists && !bResume)                  // get rid of an unwanted ".part" file
02239     remove(sPart.data());
02240 
02241   // JPF: in kio_file overwrite disables ".part" operations. I do not believe
02242   // JPF: that this is a good behaviour!
02243   if(bDestExists)                             // must delete for overwrite
02244     remove(sDest.data());
02245 
02246   // WABA: Make sure that we keep writing permissions ourselves,
02247   // otherwise we can be in for a surprise on NFS.
02248   mode_t initialMode;
02249   if (permissions != -1)
02250     initialMode = permissions | S_IWUSR;
02251   else
02252     initialMode = 0666;
02253 
02254   // open the output file ...
02255   KIO::fileoffset_t hCopyOffset = 0;
02256   if(bResume)
02257   {
02258     iCopyFile = KDE_open( sPart.data(), O_RDWR );  // append if resuming
02259     hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END);
02260     if(hCopyOffset < 0)
02261     {
02262       iError = ERR_CANNOT_RESUME;
02263       return statusClientError;                            // client side error
02264     }
02265     kDebug(7102) << "copy: resuming at " << hCopyOffset;
02266   }
02267   else
02268     iCopyFile = KDE_open(sPart.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
02269 
02270   if(iCopyFile == -1)
02271   {
02272     kDebug(7102) << "copy: ### COULD NOT WRITE " << sCopyFile;
02273     iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED
02274                                : ERR_CANNOT_OPEN_FOR_WRITING;
02275     return statusClientError;
02276   }
02277 
02278   // delegate the real work (iError gets status) ...
02279   StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset);
02280   if( ::close(iCopyFile) && iRes == statusSuccess )
02281   {
02282     iError = ERR_COULD_NOT_WRITE;
02283     iRes = statusClientError;
02284   }
02285   iCopyFile = -1;
02286 
02287   // handle renaming or deletion of a partial file ...
02288   if(bMarkPartial)
02289   {
02290     if(iRes == statusSuccess)
02291     { // rename ".part" on success
02292 #ifdef Q_OS_WIN
02293         if ( MoveFileExA( sPart.data(),
02294                           sDest.data(),
02295                           MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED ) == 0 )
02296 #else
02297       if ( KDE_rename( sPart.data(), sDest.data() ) )
02298 #endif
02299       {
02300         kDebug(7102) << "copy: cannot rename " << sPart << " to " << sDest;
02301         iError = ERR_CANNOT_RENAME_PARTIAL;
02302         iRes = statusClientError;
02303       }
02304     }
02305     else if(KDE_stat( sPart.data(), &buff ) == 0)
02306     { // should a very small ".part" be deleted?
02307       int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
02308       if (buff.st_size <  size)
02309         remove(sPart.data());
02310     }
02311   }
02312   return iRes;
02313 }
02314 

KIOSlave

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal