• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kioslave

ftp.cc

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

kioslave

Skip menu "kioslave"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
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