• Skip to content
  • Skip to link menu
KDE 4.1 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/QTcpServer>
00063 
00064 #include <kdebug.h>
00065 #include <kglobal.h>
00066 #include <klocale.h>
00067 #include <kcomponentdata.h>
00068 #include <kmimetype.h>
00069 #include <kio/ioslave_defaults.h>
00070 #include <kio/slaveconfig.h>
00071 #include <kremoteencoding.h>
00072 #include <ksocketfactory.h>
00073 #include <kde_file.h>
00074 #include <kconfiggroup.h>
00075 
00076 #ifdef HAVE_STRTOLL
00077   #define charToLongLong(a) strtoll(a, 0, 10)
00078 #else
00079   #define charToLongLong(a) strtol(a, 0, 10)
00080 #endif
00081 
00082 #define FTP_LOGIN   "anonymous"
00083 #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,
00088         initialIpcSize =  2 * 1024,
00092         mimimumMimeSize =     1024
    };

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

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • 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
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
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