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

KIOSlave

ftp.cpp

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

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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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