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

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