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

KIOSlave

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
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