00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #define KIO_FTP_PRIVATE_INCLUDE
00033 #include "ftp.h"
00034
00035 #include <sys/stat.h>
00036 #ifdef HAVE_SYS_TIME_H
00037 #include <sys/time.h>
00038 #endif
00039 #ifdef HAVE_SYS_SELECT_H
00040 #include <sys/select.h>
00041 #endif
00042
00043 #include <netinet/in.h>
00044 #include <arpa/inet.h>
00045
00046 #include <assert.h>
00047 #include <ctype.h>
00048 #include <errno.h>
00049 #include <fcntl.h>
00050 #include <netdb.h>
00051 #include <stdlib.h>
00052 #include <string.h>
00053 #include <unistd.h>
00054 #include <signal.h>
00055
00056 #if TIME_WITH_SYS_TIME
00057 #include <time.h>
00058 #endif
00059
00060 #include <QtCore/QDir>
00061 #include <QtNetwork/QHostAddress>
00062 #include <QtNetwork/QTcpSocket>
00063 #include <QtNetwork/QTcpServer>
00064
00065 #include <kdebug.h>
00066 #include <kglobal.h>
00067 #include <klocale.h>
00068 #include <kcomponentdata.h>
00069 #include <kmimetype.h>
00070 #include <kio/ioslave_defaults.h>
00071 #include <kio/slaveconfig.h>
00072 #include <kremoteencoding.h>
00073 #include <ksocketfactory.h>
00074 #include <kde_file.h>
00075 #include <kconfiggroup.h>
00076
00077 #ifdef HAVE_STRTOLL
00078 #define charToLongLong(a) strtoll(a, 0, 10)
00079 #else
00080 #define charToLongLong(a) strtol(a, 0, 10)
00081 #endif
00082
00083 #define FTP_LOGIN "anonymous"
00084 #define FTP_PASSWD "anonymous@"
//#undef kDebug
#define ENABLE_CAN_RESUME
// JPF: somebody should find a better solution for this or move this to KIO
// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
namespace KIO {
enum buffersizes
{ /**
* largest buffer size that should be used to transfer data between
* KIO slaves using the data() function
*/
maximumIpcSize = 32 * 1024,
00089 initialIpcSize = 2 * 1024,
00093 mimimumMimeSize = 1024
};
// JPF: this helper was derived from write_all in file.cc (FileProtocol).
static // JPF: in ftp.cc we make it static
00101 int WriteToFile(int fd, const char *buf, size_t len)
{
while (len > 0)
{ // JPF: shouldn't there be a KDE_write?
ssize_t written = write(fd, buf, len);
if (written >= 0)
{ buf += written;
len -= written;
continue;
}
switch(errno)
{ case EINTR: continue;
case EPIPE: return ERR_CONNECTION_BROKEN;
case ENOSPC: return ERR_DISK_FULL;
default: return ERR_COULD_NOT_WRITE;
}
}
return 0;
}
}
KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;
using namespace KIO;
extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
00102 {
00103 KComponentData componentData( "kio_ftp", "kdelibs4" );
00104 ( void ) KGlobal::locale();
00105
00106 kDebug(7102) << "Starting " << getpid();
00107
00108 if (argc != 4)
00109 {
00110 fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n");
00111 exit(-1);
00112 }
00113
00114 Ftp slave(argv[2], argv[3]);
00115 slave.dispatchLoop();
00116
00117 kDebug(7102) << "Done";
00118 return 0;
00119 }
00120
00121
00122
00123
00124
00125 Ftp::Ftp( const QByteArray &pool, const QByteArray &app )
00126 : SlaveBase( "ftp", pool, app )
00127 {
00128
00129 m_data = m_control = NULL;
00130 m_server = NULL;
00131 ftpCloseControlConnection();
00132
00133
00134 m_port = 0;
00135 }
00136
00137
00138 Ftp::~Ftp()
00139 {
00140 kDebug(7102);
00141 closeConnection();
00142 }
00143
00147 void Ftp::ftpCloseDataConnection()
00148 {
00149 delete m_data;
00150 m_data = NULL;
00151 delete m_server;
00152 m_server = NULL;
00153 }
00154
00159 void Ftp::ftpCloseControlConnection()
00160 {
00161 m_extControl = 0;
00162 delete m_control;
00163 m_control = NULL;
00164 m_cDataMode = 0;
00165 m_bLoggedOn = false;
00166 m_bTextMode = false;
00167 m_bBusy = false;
00168 }
00169
00174 const char* Ftp::ftpResponse(int iOffset)
00175 {
00176 assert(m_control != NULL);
00177 const char *pTxt = m_lastControlLine.data();
00178
00179
00180 if(iOffset < 0)
00181 {
00182 int iMore = 0;
00183 m_iRespCode = 0;
00184
00185
00186
00187
00188 do {
00189 while (!m_control->canReadLine() && m_control->waitForReadyRead()) {}
00190 m_lastControlLine = m_control->readLine();
00191 pTxt = m_lastControlLine.data();
00192 int iCode = atoi(pTxt);
00193 if (iMore == 0) {
00194
00195 kDebug(7102) << " > " << pTxt;
00196 if(iCode >= 100) {
00197 m_iRespCode = iCode;
00198 if (pTxt[3] == '-') {
00199
00200 iMore = iCode;
00201 }
00202 } else {
00203 kWarning(7102) << "Cannot parse valid code from line" << pTxt;
00204 }
00205 } else {
00206
00207 kDebug(7102) << " > " << pTxt;
00208 if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') {
00209 iMore = 0;
00210 }
00211 }
00212 } while(iMore != 0);
00213 kDebug(7102) << "resp> " << pTxt;
00214
00215 m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0;
00216 }
00217
00218
00219 while(iOffset-- > 0 && pTxt[0])
00220 pTxt++;
00221 return pTxt;
00222 }
00223
00224
00225 void Ftp::closeConnection()
00226 {
00227 if(m_control != NULL || m_data != NULL)
00228 kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy;
00229
00230 if(m_bBusy)
00231 {
00232 kWarning(7102) << "Abandoned data stream";
00233 ftpCloseDataConnection();
00234 }
00235
00236 if(m_bLoggedOn)
00237 {
00238 if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) )
00239 kWarning(7102) << "QUIT returned error: " << m_iRespCode;
00240 }
00241
00242
00243 ftpCloseDataConnection();
00244 ftpCloseControlConnection();
00245 }
00246
00247 void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user,
00248 const QString& _pass )
00249 {
00250 kDebug(7102) << _host << "port=" << _port;
00251
00252 m_proxyURL = metaData("UseProxy");
00253 m_bUseProxy = (m_proxyURL.isValid() && m_proxyURL.protocol() == "ftp");
00254
00255 if ( m_host != _host || m_port != _port ||
00256 m_user != _user || m_pass != _pass )
00257 closeConnection();
00258
00259 m_host = _host;
00260 m_port = _port;
00261 m_user = _user;
00262 m_pass = _pass;
00263 }
00264
00265 void Ftp::openConnection()
00266 {
00267 ftpOpenConnection(loginExplicit);
00268 }
00269
00270 bool Ftp::ftpOpenConnection (LoginMode loginMode)
00271 {
00272
00273 if(loginMode == loginImplicit && m_bLoggedOn)
00274 {
00275 assert(m_control != NULL);
00276 return true;
00277 }
00278
00279 kDebug(7102) << "ftpOpenConnection " << m_host << ":" << m_port << " "
00280 << m_user << " [password hidden]";
00281
00282 infoMessage( i18n("Opening connection to host %1", m_host) );
00283
00284 if ( m_host.isEmpty() )
00285 {
00286 error( ERR_UNKNOWN_HOST, QString() );
00287 return false;
00288 }
00289
00290 assert( !m_bLoggedOn );
00291
00292 m_initialPath.clear();
00293 m_currentPath.clear();
00294
00295 QString host = m_bUseProxy ? m_proxyURL.host() : m_host;
00296 int port = m_bUseProxy ? m_proxyURL.port() : m_port;
00297
00298 if (!ftpOpenControlConnection(host, port) )
00299 return false;
00300 infoMessage( i18n("Connected to host %1", m_host) );
00301
00302 if(loginMode != loginDefered)
00303 {
00304 m_bLoggedOn = ftpLogin();
00305 if( !m_bLoggedOn )
00306 return false;
00307 }
00308
00309 m_bTextMode = config()->readEntry("textmode", false);
00310 connected();
00311 return true;
00312 }
00313
00314
00320 bool Ftp::ftpOpenControlConnection( const QString &host, int port )
00321 {
00322
00323 closeConnection();
00324 QString sErrorMsg;
00325
00326
00327 if (port == 0)
00328 port = 21;
00329 m_control = KSocketFactory::synchronousConnectToHost("ftp", host, port, connectTimeout() * 1000);
00330 int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT;
00331
00332
00333 if(iErrorCode == 0)
00334 {
00335 const char* psz = ftpResponse(-1);
00336 if(m_iRespType != 2)
00337 {
00338 if(psz[0])
00339 sErrorMsg = i18n("%1.\n\nReason: %2", host, psz);
00340 iErrorCode = ERR_COULD_NOT_CONNECT;
00341 }
00342 }
00343 else
00344 {
00345 if (m_control->error() == QAbstractSocket::HostNotFoundError)
00346 iErrorCode = ERR_UNKNOWN_HOST;
00347
00348 sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString());
00349 }
00350
00351
00352 if(iErrorCode == 0)
00353 return true;
00354 closeConnection();
00355 error(iErrorCode, sErrorMsg);
00356 return false;
00357 }
00358
00366 bool Ftp::ftpLogin()
00367 {
00368 infoMessage( i18n("Sending login information") );
00369
00370 assert( !m_bLoggedOn );
00371
00372 QString user = m_user;
00373 QString pass = m_pass;
00374
00375 if ( config()->readEntry("EnableAutoLogin", false) )
00376 {
00377 QString au = config()->readEntry("autoLoginUser");
00378 if ( !au.isEmpty() )
00379 {
00380 user = au;
00381 pass = config()->readEntry("autoLoginPass");
00382 }
00383 }
00384
00385
00386
00387 if (user.isEmpty() && pass.isEmpty())
00388 {
00389 user = FTP_LOGIN;
00390 pass = FTP_PASSWD;
00391 }
00392
00393 AuthInfo info;
00394 info.url.setProtocol( "ftp" );
00395 info.url.setHost( m_host );
00396 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00397 info.url.setPort( m_port );
00398 info.url.setUser( user );
00399
00400 QByteArray tempbuf;
00401 int failedAuth = 0;
00402
00403 do
00404 {
00405
00406
00407
00408 if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) )
00409 {
00410 QString errorMsg;
00411 kDebug(7102) << "Prompting user for login info...";
00412
00413
00414 if( failedAuth > 0 )
00415 {
00416 errorMsg = i18n("Message sent:\nLogin using username=%1 and "
00417 "password=[hidden]\n\nServer replied:\n%2\n\n"
00418 , user, ftpResponse(0));
00419 }
00420
00421 if ( user != FTP_LOGIN )
00422 info.username = user;
00423
00424 info.prompt = i18n("You need to supply a username and a password "
00425 "to access this site.");
00426 info.commentLabel = i18n( "Site:" );
00427 info.comment = i18n("<b>%1</b>", m_host );
00428 info.keepPassword = true;
00429 info.readOnly = (!m_user.isEmpty() && m_user != FTP_LOGIN);
00430
00431 bool disablePassDlg = config()->readEntry( "DisablePassDlg", false );
00432 if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) )
00433 {
00434 error( ERR_USER_CANCELED, m_host );
00435 return false;
00436 }
00437 else
00438 {
00439 user = info.username;
00440 pass = info.password;
00441 }
00442 }
00443
00444 tempbuf = "USER ";
00445 tempbuf += user.toLatin1();
00446 if ( m_bUseProxy )
00447 {
00448 tempbuf += '@';
00449 tempbuf += m_host.toLatin1();
00450 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
00451 {
00452 tempbuf += ':';
00453 tempbuf += QString::number(m_port).toLatin1();
00454 }
00455 }
00456
00457 kDebug(7102) << "Sending Login name: " << tempbuf;
00458
00459 bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00460 bool needPass = (m_iRespCode == 331);
00461
00462
00463 if ( !loggedIn && !needPass )
00464 {
00465 kDebug(7102) << "Login failed: " << ftpResponse(0);
00466 ++failedAuth;
00467 continue;
00468 }
00469
00470 if( needPass )
00471 {
00472 tempbuf = "pass ";
00473 tempbuf += pass.toLatin1();
00474 kDebug(7102) << "Sending Login password: " << "[protected]";
00475 loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
00476 }
00477
00478 if ( loggedIn )
00479 {
00480
00481 if( user != FTP_LOGIN && pass != FTP_PASSWD )
00482 cacheAuthentication( info );
00483 failedAuth = -1;
00484 }
00485
00486 } while( ++failedAuth );
00487
00488
00489 kDebug(7102) << "Login OK";
00490 infoMessage( i18n("Login OK") );
00491
00492
00493
00494 if( ftpSendCmd("SYST") && (m_iRespType == 2) )
00495 {
00496 if( !strncmp( ftpResponse(0), "215 Windows_NT", 14 ) )
00497 {
00498 ftpSendCmd( "site dirstyle" );
00499
00500
00501 if( !strncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 ))
00502
00503 ftpSendCmd( "site dirstyle" );
00504
00505 m_extControl |= chmodUnknown;
00506 }
00507 }
00508 else
00509 kWarning(7102) << "SYST failed";
00510
00511 if ( config()->readEntry ("EnableAutoLoginMacro", false) )
00512 ftpAutoLoginMacro ();
00513
00514
00515 kDebug(7102) << "Searching for pwd";
00516 if( !ftpSendCmd("PWD") || (m_iRespType != 2) )
00517 {
00518 kDebug(7102) << "Couldn't issue pwd command";
00519 error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) );
00520 return false;
00521 }
00522
00523 QString sTmp = remoteEncoding()->decode( ftpResponse(3) );
00524 int iBeg = sTmp.indexOf('"');
00525 int iEnd = sTmp.lastIndexOf('"');
00526 if(iBeg > 0 && iBeg < iEnd)
00527 {
00528 m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1);
00529 if(m_initialPath[0] != '/') m_initialPath.prepend('/');
00530 kDebug(7102) << "Initial path set to: " << m_initialPath;
00531 m_currentPath = m_initialPath;
00532 }
00533 return true;
00534 }
00535
00536 void Ftp::ftpAutoLoginMacro ()
00537 {
00538 QString macro = metaData( "autoLoginMacro" );
00539
00540 if ( macro.isEmpty() )
00541 return;
00542
00543 const QStringList list = macro.split('\n',QString::SkipEmptyParts);
00544
00545 for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
00546 {
00547 if ( (*it).startsWith("init") )
00548 {
00549 const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
00550 it = list2.begin();
00551 ++it;
00552
00553 for( ; it != list2.end() ; ++it )
00554 {
00555
00556
00557 if ( (*it).startsWith( "cwd" ) )
00558 ftpFolder( (*it).mid(4).trimmed(), false );
00559 }
00560
00561 break;
00562 }
00563 }
00564 }
00565
00566
00576 bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries )
00577 {
00578 assert(m_control != NULL);
00579
00580 if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1)
00581 {
00582 kWarning(7102) << "Invalid command received (contains CR or LF):"
00583 << cmd.data();
00584 error( ERR_UNSUPPORTED_ACTION, m_host );
00585 return false;
00586 }
00587
00588
00589 bool isPassCmd = (cmd.left(4).toLower() == "pass");
00590 if ( !isPassCmd )
00591 kDebug(7102) << "send> " << cmd.data();
00592 else
00593 kDebug(7102) << "send> pass [protected]";
00594
00595
00596 QByteArray buf = cmd;
00597 buf += "\r\n";
00598 int num = m_control->write(buf);
00599 while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {}
00600
00601
00602
00603
00604 if( num > 0 )
00605 ftpResponse(-1);
00606 else
00607 {
00608 m_iRespType = m_iRespCode = 0;
00609 }
00610
00611
00612
00613 if( (m_iRespType <= 0) || (m_iRespCode == 421) )
00614 {
00615
00616 if (!m_bLoggedOn)
00617 {
00618
00619
00620
00621
00622 if (maxretries > 0 && !isPassCmd)
00623 {
00624 closeConnection ();
00625 if( ftpOpenConnection(loginDefered) )
00626 ftpSendCmd ( cmd, maxretries - 1 );
00627 }
00628
00629 return false;
00630 }
00631 else
00632 {
00633 if ( maxretries < 1 )
00634 return false;
00635 else
00636 {
00637 kDebug(7102) << "Was not able to communicate with " << m_host
00638 << "Attempting to re-establish connection.";
00639
00640 closeConnection();
00641 openConnection();
00642
00643 if (!m_bLoggedOn)
00644 {
00645 if (m_control != NULL)
00646 {
00647 kDebug(7102) << "Login failure, aborting";
00648 error (ERR_COULD_NOT_LOGIN, m_host);
00649 closeConnection ();
00650 }
00651 return false;
00652 }
00653
00654 kDebug(7102) << "Logged back in, re-issuing command";
00655
00656
00657 if (maxretries)
00658 maxretries--;
00659
00660 return ftpSendCmd( cmd, maxretries );
00661 }
00662 }
00663 }
00664
00665 return true;
00666 }
00667
00668
00669
00670
00671
00672
00673
00674
00675 int Ftp::ftpOpenPASVDataConnection()
00676 {
00677 assert(m_control != NULL);
00678 assert(m_data == NULL);
00679
00680
00681 QHostAddress addr = m_control->peerAddress();
00682 if (addr.protocol() != QAbstractSocket::IPv4Protocol)
00683 return ERR_INTERNAL;
00684
00685 if (m_extControl & pasvUnknown)
00686 return ERR_INTERNAL;
00687
00688 m_bPasv = true;
00689
00690
00691 if( !ftpSendCmd("PASV") || (m_iRespType != 2) )
00692 {
00693 kDebug(7102) << "PASV attempt failed";
00694
00695 if( m_iRespType == 5 )
00696 {
00697 kDebug(7102) << "disabling use of PASV";
00698 m_extControl |= pasvUnknown;
00699 }
00700 return ERR_INTERNAL;
00701 }
00702
00703
00704
00705 int i[6];
00706 const char *start = strchr(ftpResponse(3), '(');
00707 if ( !start )
00708 start = strchr(ftpResponse(3), '=');
00709 if ( !start ||
00710 ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
00711 sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
00712 {
00713 kError(7102) << "parsing IP and port numbers failed. String parsed: " << start;
00714 return ERR_INTERNAL;
00715 }
00716
00717
00718
00719
00720
00721
00722 quint16 port = i[4] << 8 | i[5];
00723 kDebug(7102) << "Connecting to " << addr.toString() << " port " << port;
00724 m_data = KSocketFactory::synchronousConnectToHost("ftp-data", addr.toString(), port,
00725 connectTimeout() * 1000);
00726
00727 return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL;
00728 }
00729
00730
00731
00732
00733 int Ftp::ftpOpenEPSVDataConnection()
00734 {
00735 assert(m_control != NULL);
00736 assert(m_data == NULL);
00737
00738 QHostAddress address = m_control->peerAddress();
00739 int portnum;
00740
00741 if (m_extControl & epsvUnknown)
00742 return ERR_INTERNAL;
00743
00744 m_bPasv = true;
00745 if( !ftpSendCmd("EPSV") || (m_iRespType != 2) )
00746 {
00747
00748 if( m_iRespType == 5 )
00749 {
00750 kDebug(7102) << "disabling use of EPSV";
00751 m_extControl |= epsvUnknown;
00752 }
00753 return ERR_INTERNAL;
00754 }
00755
00756 const char *start = strchr(ftpResponse(3), '|');
00757 if ( !start || sscanf(start, "|||%d|", &portnum) != 1)
00758 return ERR_INTERNAL;
00759
00760 m_data = KSocketFactory::synchronousConnectToHost("ftp-data", address.toString(), portnum,
00761 connectTimeout() * 1000);
00762 return m_data->isOpen() ? 0 : ERR_INTERNAL;
00763 }
00764
00765
00766
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777 int Ftp::ftpOpenDataConnection()
00778 {
00779
00780 assert( m_bLoggedOn );
00781 ftpCloseDataConnection();
00782
00783 int iErrCode = 0;
00784 int iErrCodePASV = 0;
00785
00786
00787 if( !config()->readEntry("DisablePassiveMode", false) )
00788 {
00789 iErrCode = ftpOpenPASVDataConnection();
00790 if(iErrCode == 0)
00791 return 0;
00792 iErrCodePASV = iErrCode;
00793 ftpCloseDataConnection();
00794
00795 if( !config()->readEntry("DisableEPSV", false) )
00796 {
00797 iErrCode = ftpOpenEPSVDataConnection();
00798 if(iErrCode == 0)
00799 return 0;
00800 ftpCloseDataConnection();
00801 }
00802
00803
00804
00805 if (m_extControl & epsvAllSent)
00806 return iErrCodePASV ? iErrCodePASV : iErrCode;
00807 }
00808
00809
00810 iErrCode = ftpOpenPortDataConnection();
00811 if(iErrCode == 0)
00812 return 0;
00813
00814 ftpCloseDataConnection();
00815
00816 return iErrCodePASV ? iErrCodePASV : iErrCode;
00817 }
00818
00819
00820
00821
00822
00823
00824
00825 int Ftp::ftpOpenPortDataConnection()
00826 {
00827 assert(m_control != NULL);
00828 assert(m_data == NULL);
00829
00830 m_bPasv = false;
00831 if (m_extControl & eprtUnknown)
00832 return ERR_INTERNAL;
00833
00834 if (!m_server)
00835 m_server = KSocketFactory::listen("ftp-data");
00836
00837 if (!m_server->isListening()) {
00838 delete m_server;
00839 return ERR_COULD_NOT_LISTEN;
00840 }
00841
00842 m_server->setMaxPendingConnections(1);
00843
00844 QString command;
00845 QHostAddress localAddress = m_control->localAddress();
00846 if (localAddress.protocol() == QAbstractSocket::IPv4Protocol)
00847 {
00848 struct
00849 {
00850 quint32 ip4;
00851 quint16 port;
00852 } data;
00853 data.ip4 = localAddress.toIPv4Address();
00854 data.port = m_server->serverPort();
00855
00856 unsigned char *pData = reinterpret_cast<unsigned char*>(&data);
00857 command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]);
00858 }
00859 else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol)
00860 {
00861 command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort());
00862 }
00863
00864 if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) )
00865 {
00866 return 0;
00867 }
00868
00869 delete m_server;
00870 return ERR_INTERNAL;
00871 }
00872
00873 bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
00874 int errorcode, KIO::fileoffset_t _offset )
00875 {
00876 int errCode = 0;
00877 if( !ftpDataMode(_mode) )
00878 errCode = ERR_COULD_NOT_CONNECT;
00879 else
00880 errCode = ftpOpenDataConnection();
00881
00882 if(errCode != 0)
00883 {
00884 error(errCode, m_host);
00885 return false;
00886 }
00887
00888 if ( _offset > 0 ) {
00889
00890 char buf[100];
00891 sprintf(buf, "rest %lld", _offset);
00892 if ( !ftpSendCmd( buf ) )
00893 return false;
00894 if( m_iRespType != 3 )
00895 {
00896 error( ERR_CANNOT_RESUME, _path );
00897 return false;
00898 }
00899 }
00900
00901 QByteArray tmp = _command;
00902 QString errormessage;
00903
00904 if ( !_path.isEmpty() ) {
00905 tmp += ' ';
00906 tmp += remoteEncoding()->encode(_path);
00907 }
00908
00909 if( !ftpSendCmd( tmp ) || (m_iRespType != 1) )
00910 {
00911 if( _offset > 0 && strcmp(_command, "retr") == 0 && (m_iRespType == 4) )
00912 errorcode = ERR_CANNOT_RESUME;
00913
00914 errormessage = _path;
00915 }
00916
00917 else
00918 {
00919
00920 if ( _offset > 0 && strcmp(_command, "retr") == 0 )
00921 canResume();
00922
00923 if(m_server && !m_data) {
00924 kDebug(7102) << "waiting for connection from remote.";
00925 m_server->waitForNewConnection(connectTimeout() * 1000);
00926 m_data = m_server->nextPendingConnection();
00927 }
00928
00929 if(m_data) {
00930 kDebug(7102) << "connected with remote.";
00931 m_bBusy = true;
00932 return true;
00933 }
00934
00935 kDebug(7102) << "no connection received from remote.";
00936 errorcode=ERR_COULD_NOT_ACCEPT;
00937 errormessage=m_host;
00938 return false;
00939 }
00940
00941 error(errorcode, errormessage);
00942 return false;
00943 }
00944
00945
00946 bool Ftp::ftpCloseCommand()
00947 {
00948
00949
00950 delete m_data;
00951 m_data = NULL;
00952 delete m_server;
00953 m_server = NULL;
00954
00955 if(!m_bBusy)
00956 return true;
00957
00958 kDebug(7102) << "ftpCloseCommand: reading command result";
00959 m_bBusy = false;
00960
00961 if(!ftpResponse(-1) || (m_iRespType != 2) )
00962 {
00963 kDebug(7102) << "ftpCloseCommand: no transfer complete message";
00964 return false;
00965 }
00966 return true;
00967 }
00968
00969 void Ftp::mkdir( const KUrl & url, int permissions )
00970 {
00971 if( !ftpOpenConnection(loginImplicit) )
00972 return;
00973
00974 QString path = remoteEncoding()->encode(url);
00975 QByteArray buf = "mkd ";
00976 buf += remoteEncoding()->encode(path);
00977
00978 if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
00979 {
00980 QString currentPath( m_currentPath );
00981
00982
00983
00984 if( ftpFolder( path, false ) )
00985 {
00986 error( ERR_DIR_ALREADY_EXIST, path );
00987
00988 (void) ftpFolder( currentPath, false );
00989 return;
00990 }
00991
00992 error( ERR_COULD_NOT_MKDIR, path );
00993 return;
00994 }
00995
00996 if ( permissions != -1 )
00997 {
00998
00999 (void) ftpChmod( path, permissions );
01000 }
01001
01002 finished();
01003 }
01004
01005 void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags )
01006 {
01007 if( !ftpOpenConnection(loginImplicit) )
01008 return;
01009
01010
01011 if ( ftpRename( src.path(), dst.path(), flags ) )
01012 finished();
01013 else
01014 error( ERR_CANNOT_RENAME, src.path() );
01015 }
01016
01017 bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags)
01018 {
01019 assert(m_bLoggedOn);
01020
01021
01022 if (!(jobFlags & KIO::Overwrite)) {
01023 if (ftpFileExists(dst)) {
01024 error(ERR_FILE_ALREADY_EXIST, dst);
01025 return false;
01026 }
01027 }
01028 if (ftpFolder(dst, false)) {
01029 error(ERR_DIR_ALREADY_EXIST, dst);
01030 return false;
01031 }
01032
01033
01034 const int pos = src.lastIndexOf('/');
01035 if (pos > 0) {
01036 if(!ftpFolder(src.left(pos+1), false))
01037 return false;
01038 }
01039
01040 QByteArray from_cmd = "RNFR ";
01041 from_cmd += remoteEncoding()->encode(src.mid(pos+1));
01042 if (!ftpSendCmd(from_cmd) || (m_iRespType != 3))
01043 return false;
01044
01045 QByteArray to_cmd = "RNTO ";
01046 to_cmd += remoteEncoding()->encode(dst);
01047 if (!ftpSendCmd(to_cmd) || (m_iRespType != 2))
01048 return false;
01049
01050 return true;
01051 }
01052
01053 void Ftp::del( const KUrl& url, bool isfile )
01054 {
01055 if( !ftpOpenConnection(loginImplicit) )
01056 return;
01057
01058
01059
01060 if ( !isfile )
01061 ftpFolder(remoteEncoding()->directory(url), false);
01062
01063 QByteArray cmd = isfile ? "DELE " : "RMD ";
01064 cmd += remoteEncoding()->encode(url);
01065
01066 if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01067 error( ERR_CANNOT_DELETE, url.path() );
01068 else
01069 finished();
01070 }
01071
01072 bool Ftp::ftpChmod( const QString & path, int permissions )
01073 {
01074 assert( m_bLoggedOn );
01075
01076 if(m_extControl & chmodUnknown)
01077 return false;
01078
01079
01080
01081 QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 ) + ' ';
01082 cmd += path;
01083
01084 ftpSendCmd(remoteEncoding()->encode(cmd));
01085 if(m_iRespType == 2)
01086 return true;
01087
01088 if(m_iRespCode == 500)
01089 {
01090 m_extControl |= chmodUnknown;
01091 kDebug(7102) << "ftpChmod: CHMOD not supported - disabling";
01092 }
01093 return false;
01094 }
01095
01096 void Ftp::chmod( const KUrl & url, int permissions )
01097 {
01098 if( !ftpOpenConnection(loginImplicit) )
01099 return;
01100
01101 if ( !ftpChmod( url.path(), permissions ) )
01102 error( ERR_CANNOT_CHMOD, url.path() );
01103 else
01104 finished();
01105 }
01106
01107 void Ftp::ftpCreateUDSEntry( const QString & filename, FtpEntry& ftpEnt, UDSEntry& entry, bool isDir )
01108 {
01109 assert(entry.count() == 0);
01110
01111 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01112 entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size );
01113 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date );
01114 entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access );
01115 entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner );
01116 if ( !ftpEnt.group.isEmpty() )
01117 {
01118 entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group );
01119 }
01120
01121 if ( !ftpEnt.link.isEmpty() )
01122 {
01123 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link );
01124
01125 KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) );
01126
01127
01128
01129
01130 if ( mime->name() == KMimeType::defaultMimeType() )
01131 {
01132 kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename;
01133 entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) );
01134 isDir = true;
01135 }
01136 }
01137
01138 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type );
01139
01140
01141 }
01142
01143
01144 void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir )
01145 {
01146 UDSEntry entry;
01147
01148
01149 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01150 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG );
01151 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01152
01153
01154 statEntry(entry);
01155 finished();
01156 }
01157
01158 void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename )
01159 {
01160
01161
01162
01163 QString statSide = metaData("statSide");
01164 kDebug(7102) << "statSide=" << statSide;
01165 if ( statSide == "source" )
01166 {
01167 kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing";
01168
01169
01170
01171
01172
01173 ftpShortStatAnswer( filename, false );
01174
01175 return;
01176 }
01177
01178 error( ERR_DOES_NOT_EXIST, path );
01179 }
01180
01181 void Ftp::stat(const KUrl &url)
01182 {
01183 kDebug(7102) << "path=" << url.path();
01184 if( !ftpOpenConnection(loginImplicit) )
01185 return;
01186
01187 QString path = QDir::cleanPath( url.path() );
01188 kDebug(7102) << "cleaned path=" << path;
01189
01190
01191 if( path.isEmpty() || path == "/" )
01192 {
01193 UDSEntry entry;
01194
01195 entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) );
01196 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01197 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01198 entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) );
01199 entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) );
01200
01201
01202 statEntry( entry );
01203 finished();
01204 return;
01205 }
01206
01207 KUrl tempurl( url );
01208 tempurl.setPath( path );
01209 QString listarg;
01210 QString parentDir;
01211 QString filename = tempurl.fileName();
01212 Q_ASSERT(!filename.isEmpty());
01213 QString search = filename;
01214
01215
01216
01217 bool isDir = ftpFolder(path, false);
01218
01219
01220 QString sDetails = metaData("details");
01221 int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
01222 kDebug(7102) << "details=" << details;
01223 if ( details == 0 )
01224 {
01225 if ( !isDir && !ftpFileExists(path) )
01226 {
01227 ftpStatAnswerNotFound( path, filename );
01228 return;
01229 }
01230 ftpShortStatAnswer( filename, isDir );
01231 return;
01232 }
01233
01234 if (!isDir)
01235 {
01236
01237 parentDir = tempurl.directory(KUrl::AppendTrailingSlash);
01238
01239 listarg = filename;
01240 }
01241 else
01242 {
01243
01244
01245
01246 UDSEntry entry;
01247 entry.insert( KIO::UDSEntry::UDS_NAME, filename );
01248 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
01249 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
01250
01251
01252 statEntry(entry);
01253 finished();
01254 return;
01255 }
01256
01257
01258 if( !ftpFolder(parentDir, true) )
01259 return;
01260
01261 if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) )
01262 {
01263 kError(7102) << "COULD NOT LIST";
01264 return;
01265 }
01266 kDebug(7102) << "Starting of list was ok";
01267
01268 Q_ASSERT( !search.isEmpty() && search != "/" );
01269
01270 bool bFound = false;
01271 KUrl linkURL;
01272 FtpEntry ftpEnt;
01273 while( ftpReadDir(ftpEnt) )
01274 {
01275
01276
01277 if (!bFound) {
01278 if ( ( search == ftpEnt.name || filename == ftpEnt.name ) ) {
01279 if ( !filename.isEmpty() ) {
01280 bFound = true;
01281 UDSEntry entry;
01282 ftpCreateUDSEntry( filename, ftpEnt, entry, isDir );
01283 statEntry( entry );
01284 }
01285 }
01286 }
01287
01288
01289 }
01290
01291 ftpCloseCommand();
01292
01293 if ( !bFound )
01294 {
01295 ftpStatAnswerNotFound( path, filename );
01296 return;
01297 }
01298
01299 if ( !linkURL.isEmpty() )
01300 {
01301 if ( linkURL == url || linkURL == tempurl )
01302 {
01303 error( ERR_CYCLIC_LINK, linkURL.prettyUrl() );
01304 return;
01305 }
01306 Ftp::stat( linkURL );
01307 return;
01308 }
01309
01310 kDebug(7102) << "stat : finished successfully";
01311 finished();
01312 }
01313
01314
01315 void Ftp::listDir( const KUrl &url )
01316 {
01317 kDebug(7102) << url;
01318 if( !ftpOpenConnection(loginImplicit) )
01319 return;
01320
01321
01322 QString path = url.path();
01323 if ( path.isEmpty() )
01324 {
01325 KUrl realURL;
01326 realURL.setProtocol( "ftp" );
01327 if ( m_user != FTP_LOGIN )
01328 realURL.setUser( m_user );
01329
01330 if ( m_pass != FTP_PASSWD )
01331 realURL.setPass( m_pass );
01332 realURL.setHost( m_host );
01333 if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
01334 realURL.setPort( m_port );
01335 if ( m_initialPath.isEmpty() )
01336 m_initialPath = "/";
01337 realURL.setPath( m_initialPath );
01338 kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl();
01339 redirection( realURL );
01340 finished();
01341 return;
01342 }
01343
01344 kDebug(7102) << "hunting for path" << path;
01345
01346 if (!ftpOpenDir(path)) {
01347 if (ftpFileExists(path)) {
01348 error(ERR_IS_FILE, path);
01349 } else {
01350
01351
01352 error( ERR_CANNOT_ENTER_DIRECTORY, path );
01353 }
01354 return;
01355 }
01356
01357 UDSEntry entry;
01358 FtpEntry ftpEnt;
01359 while( ftpReadDir(ftpEnt) )
01360 {
01361
01362
01363 if ( !ftpEnt.name.isEmpty() )
01364 {
01365
01366
01367
01368
01369 entry.clear();
01370 ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
01371 listEntry( entry, false );
01372 }
01373 }
01374 listEntry( entry, true );
01375 ftpCloseCommand();
01376 finished();
01377 }
01378
01379 void Ftp::slave_status()
01380 {
01381 kDebug(7102) << "Got slave_status host = " << (!m_host.toAscii().isEmpty() ? m_host.toAscii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]";
01382 slaveStatus( m_host, m_bLoggedOn );
01383 }
01384
01385 bool Ftp::ftpOpenDir( const QString & path )
01386 {
01387
01388
01389
01390
01391 QString tmp = path.isEmpty() ? QString("/") : path;
01392
01393
01394 if( !ftpFolder(tmp, false) )
01395 return false;
01396
01397
01398
01399
01400
01401
01402
01403 if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01404 {
01405 if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
01406 {
01407 kWarning(7102) << "Can't open for listing";
01408 return false;
01409 }
01410 }
01411 kDebug(7102) << "Starting of list was ok";
01412 return true;
01413 }
01414
01415 bool Ftp::ftpReadDir(FtpEntry& de)
01416 {
01417 assert(m_data != NULL);
01418
01419
01420 while( true )
01421 {
01422 while (!m_data->canReadLine() && m_data->waitForReadyRead()) {}
01423 QByteArray data = m_data->readLine();
01424 if (data.size() == 0)
01425 break;
01426
01427 const char* buffer = data.data();
01428 kDebug(7102) << "dir > " << buffer;
01429
01430
01431
01432
01433
01434
01435
01436 const char *p_access, *p_junk, *p_owner, *p_group, *p_size;
01437 if( (p_access = strtok((char*)buffer," ")) == 0) continue;
01438 if( (p_junk = strtok(NULL," ")) == 0) continue;
01439 if( (p_owner = strtok(NULL," ")) == 0) continue;
01440 if( (p_group = strtok(NULL," ")) == 0) continue;
01441 if( (p_size = strtok(NULL," ")) == 0) continue;
01442
01443
01444
01445 de.access = 0;
01446 if ( strlen( p_access ) == 1 && p_junk[0] == '[' ) {
01447 de.access = S_IRWXU | S_IRWXG | S_IRWXO;
01448 }
01449
01450 const char *p_date_1, *p_date_2, *p_date_3, *p_name;
01451
01452
01453
01454
01455 if ( strchr( p_size, ',' ) != 0L )
01456 {
01457
01458 if ((p_size = strtok(NULL," ")) == 0)
01459 continue;
01460 }
01461
01462
01463
01464
01465
01466 if ( !isdigit( *p_size ) )
01467 {
01468 p_date_1 = p_size;
01469 p_size = p_group;
01470 p_group = 0;
01471
01472 }
01473 else
01474 {
01475 p_date_1 = strtok(NULL," ");
01476
01477 }
01478
01479 if ( p_date_1 != 0 &&
01480 (p_date_2 = strtok(NULL," ")) != 0 &&
01481 (p_date_3 = strtok(NULL," ")) != 0 &&
01482 (p_name = strtok(NULL,"\r\n")) != 0 )
01483 {
01484 {
01485 QByteArray tmp( p_name );
01486 if ( p_access[0] == 'l' )
01487 {
01488 int i = tmp.lastIndexOf( " -> " );
01489 if ( i != -1 ) {
01490 de.link = remoteEncoding()->decode(p_name + i + 4);
01491 tmp.truncate( i );
01492 }
01493 else
01494 de.link.clear();
01495 }
01496 else
01497 de.link.clear();
01498
01499 if ( tmp[0] == '/' )
01500 tmp.remove( 0, 1 );
01501
01502 if (tmp.indexOf('/') != -1)
01503 continue;
01504
01505
01506 de.name = remoteEncoding()->decode(tmp.trimmed());
01507 }
01508
01509 de.type = S_IFREG;
01510 switch ( p_access[0] ) {
01511 case 'd':
01512 de.type = S_IFDIR;
01513 break;
01514 case 's':
01515 de.type = S_IFSOCK;
01516 break;
01517 case 'b':
01518 de.type = S_IFBLK;
01519 break;
01520 case 'c':
01521 de.type = S_IFCHR;
01522 break;
01523 case 'l':
01524 de.type = S_IFREG;
01525
01526 break;
01527 default:
01528 break;
01529 }
01530
01531 if ( p_access[1] == 'r' )
01532 de.access |= S_IRUSR;
01533 if ( p_access[2] == 'w' )
01534 de.access |= S_IWUSR;
01535 if ( p_access[3] == 'x' || p_access[3] == 's' )
01536 de.access |= S_IXUSR;
01537 if ( p_access[4] == 'r' )
01538 de.access |= S_IRGRP;
01539 if ( p_access[5] == 'w' )
01540 de.access |= S_IWGRP;
01541 if ( p_access[6] == 'x' || p_access[6] == 's' )
01542 de.access |= S_IXGRP;
01543 if ( p_access[7] == 'r' )
01544 de.access |= S_IROTH;
01545 if ( p_access[8] == 'w' )
01546 de.access |= S_IWOTH;
01547 if ( p_access[9] == 'x' || p_access[9] == 't' )
01548 de.access |= S_IXOTH;
01549 if ( p_access[3] == 's' || p_access[3] == 'S' )
01550 de.access |= S_ISUID;
01551 if ( p_access[6] == 's' || p_access[6] == 'S' )
01552 de.access |= S_ISGID;
01553 if ( p_access[9] == 't' || p_access[9] == 'T' )
01554 de.access |= S_ISVTX;
01555
01556 de.owner = remoteEncoding()->decode(p_owner);
01557 de.group = remoteEncoding()->decode(p_group);
01558 de.size = charToLongLong(p_size);
01559
01560
01561
01562
01563
01564 time_t currentTime = time( 0L );
01565 struct tm * tmptr = gmtime( ¤tTime );
01566 int currentMonth = tmptr->tm_mon;
01567
01568
01569 tmptr->tm_sec = 0;
01570 tmptr->tm_min = 0;
01571 tmptr->tm_hour = 0;
01572
01573 tmptr->tm_mday = atoi( p_date_2 );
01574
01575
01576
01577
01578 static const char * s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
01579 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
01580 for ( int c = 0 ; c < 12 ; c ++ )
01581 if ( !strcmp( p_date_1, s_months[c]) )
01582 {
01583
01584 tmptr->tm_mon = c;
01585 break;
01586 }
01587
01588
01589 if ( strlen( p_date_3 ) == 4 )
01590 tmptr->tm_year = atoi( p_date_3 ) - 1900;
01591 else
01592 {
01593
01594
01595
01596
01597
01598
01599 if ( tmptr->tm_mon > currentMonth + 1 )
01600 tmptr->tm_year--;
01601
01602
01603 char * semicolon;
01604 if ( ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
01605 {
01606 *semicolon = '\0';
01607 tmptr->tm_min = atoi( semicolon + 1 );
01608 tmptr->tm_hour = atoi( p_date_3 );
01609 }
01610 else
01611 kWarning(7102) << "Can't parse third field " << p_date_3;
01612 }
01613
01614
01615 de.date = mktime( tmptr );
01616 return true;
01617 }
01618 }
01619 return false;
01620 }
01621
01622
01623
01624
01625
01626 void Ftp::get( const KUrl & url )
01627 {
01628 kDebug(7102) << url;
01629 int iError = 0;
01630 ftpGet(iError, -1, url, 0);
01631 if(iError)
01632 error(iError, url.path());
01633 ftpCloseCommand();
01634 }
01635
01636 Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset)
01637 {
01638
01639 if( !ftpOpenConnection(loginImplicit) )
01640 return statusServerError;
01641
01642
01643
01644
01645
01646
01647 if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) &&
01648 ftpFolder(url.path(), false) )
01649 {
01650
01651 kDebug(7102) << "ftpGet: it is a directory in fact";
01652 iError = ERR_IS_DIRECTORY;
01653 return statusServerError;
01654 }
01655
01656 QString resumeOffset = metaData("resume");
01657 if ( !resumeOffset.isEmpty() )
01658 {
01659 llOffset = resumeOffset.toLongLong();
01660 kDebug(7102) << "ftpGet: got offset from metadata : " << llOffset;
01661 }
01662
01663 if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) )
01664 {
01665 kWarning(7102) << "ftpGet: Can't open for reading";
01666 return statusServerError;
01667 }
01668
01669
01670 if(m_size == UnknownSize)
01671 {
01672 const char* psz = strrchr( ftpResponse(4), '(' );
01673 if(psz) m_size = charToLongLong(psz+1);
01674 if (!m_size) m_size = UnknownSize;
01675 }
01676
01677 KIO::filesize_t bytesLeft = 0;
01678 if ( m_size != UnknownSize )
01679 bytesLeft = m_size - llOffset;
01680
01681 kDebug(7102) << "ftpGet: starting with offset=" << llOffset;
01682 KIO::fileoffset_t processed_size = llOffset;
01683
01684 QByteArray array;
01685 bool mimetypeEmitted = false;
01686 char buffer[maximumIpcSize];
01687
01688
01689
01690 int iBlockSize = initialIpcSize;
01691 int iBufferCur = 0;
01692
01693 while(m_size == UnknownSize || bytesLeft > 0)
01694 {
01695 if(processed_size-llOffset > 1024 * 64)
01696 iBlockSize = maximumIpcSize;
01697
01698
01699 if(iBlockSize+iBufferCur > (int)sizeof(buffer))
01700 iBlockSize = sizeof(buffer) - iBufferCur;
01701 if (m_data->bytesAvailable() == 0)
01702 m_data->waitForReadyRead();
01703 int n = m_data->read( buffer+iBufferCur, iBlockSize );
01704 if(n <= 0)
01705 {
01706 if( m_size == UnknownSize && n == 0 )
01707 break;
01708
01709 iError = ERR_COULD_NOT_READ;
01710 return statusServerError;
01711 }
01712 processed_size += n;
01713
01714
01715 if(m_size != UnknownSize)
01716 {
01717 bytesLeft -= n;
01718 iBufferCur += n;
01719 if(iBufferCur < mimimumMimeSize && bytesLeft > 0)
01720 {
01721 processedSize( processed_size );
01722 continue;
01723 }
01724 n = iBufferCur;
01725 iBufferCur = 0;
01726 }
01727
01728
01729 if(!mimetypeEmitted)
01730 {
01731 mimetypeEmitted = true;
01732 array = QByteArray::fromRawData(buffer, n);
01733 KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), array);
01734 array.clear();
01735 kDebug(7102) << "ftpGet: Emitting mimetype " << mime->name();
01736 mimeType( mime->name() );
01737 if( m_size != UnknownSize )
01738 totalSize( m_size );
01739 }
01740
01741
01742 if(iCopyFile == -1)
01743 {
01744 array = QByteArray::fromRawData(buffer, n);
01745 data( array );
01746 array.clear();
01747 }
01748 else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0)
01749 return statusClientError;
01750 processedSize( processed_size );
01751 }
01752
01753 kDebug(7102) << "ftpGet: done";
01754 if(iCopyFile == -1)
01755 data(array);
01756
01757 processedSize( m_size == UnknownSize ? processed_size : m_size );
01758 kDebug(7102) << "ftpGet: emitting finished()";
01759 finished();
01760 return statusSuccess;
01761 }
01762
01763 #if 0
01764 void Ftp::mimetype( const KUrl& url )
01765 {
01766 if( !ftpOpenConnection(loginImplicit) )
01767 return;
01768
01769 if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
01770 kWarning(7102) << "Can't open for reading";
01771 return;
01772 }
01773 char buffer[ 2048 ];
01774 QByteArray array;
01775
01776
01777 int n = m_data->read( buffer, 2048 );
01778 array.setRawData(buffer, n);
01779 data( array );
01780 array.resetRawData(buffer, n);
01781
01782 kDebug(7102) << "aborting";
01783 ftpAbortTransfer();
01784
01785 kDebug(7102) << "finished";
01786 finished();
01787 kDebug(7102) << "after finished";
01788 }
01789
01790 void Ftp::ftpAbortTransfer()
01791 {
01792
01793
01794
01795 char msg[4];
01796
01797
01798 msg[0] = (char) 255;
01799 msg[1] = (char) 254;
01800 (void) send(sControl, msg, 2, 0);
01801
01802 msg[0] = (char) 255;
01803 msg[1] = (char) 242;
01804 if (send(sControl, msg, 2, MSG_OOB) != 2)
01805 ;
01806
01807
01808 kDebug(7102) << "send ABOR";
01809 QCString buf = "ABOR\r\n";
01810 if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) {
01811 error( ERR_COULD_NOT_WRITE, QString() );
01812 return;
01813 }
01814
01815
01816 kDebug(7102) << "read resp";
01817 if ( readresp() != '2' )
01818 {
01819 error( ERR_COULD_NOT_READ, QString() );
01820 return;
01821 }
01822
01823 kDebug(7102) << "close sockets";
01824 closeSockets();
01825 }
01826 #endif
01827
01828
01829
01830
01831
01832 void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags)
01833 {
01834 kDebug(7102) << url;
01835 int iError = 0;
01836 ftpPut(iError, -1, url, permissions, flags);
01837 if(iError)
01838 error(iError, url.path());
01839 ftpCloseCommand();
01840 }
01841
01842 Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url,
01843 int permissions, KIO::JobFlags flags)
01844 {
01845 if( !ftpOpenConnection(loginImplicit) )
01846 return statusServerError;
01847
01848
01849
01850 bool bMarkPartial;
01851 if (m_user.isEmpty () || m_user == FTP_LOGIN)
01852 bMarkPartial = false;
01853 else
01854 bMarkPartial = config()->readEntry("MarkPartial", true);
01855
01856 QString dest_orig = dest_url.path();
01857 QString dest_part( dest_orig );
01858 dest_part += ".part";
01859
01860 if ( ftpSize( dest_orig, 'I' ) )
01861 {
01862 if ( m_size == 0 )
01863 {
01864 QByteArray cmd = "DELE ";
01865 cmd += remoteEncoding()->encode(dest_orig);
01866 if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01867 {
01868 iError = ERR_CANNOT_DELETE_PARTIAL;
01869 return statusServerError;
01870 }
01871 }
01872 else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
01873 {
01874 iError = ERR_FILE_ALREADY_EXIST;
01875 return statusServerError;
01876 }
01877 else if ( bMarkPartial )
01878 {
01879 if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) )
01880 {
01881 iError = ERR_CANNOT_RENAME_PARTIAL;
01882 return statusServerError;
01883 }
01884 }
01885
01886 permissions = -1;
01887 }
01888 else if ( bMarkPartial && ftpSize( dest_part, 'I' ) )
01889 {
01890 if ( m_size == 0 )
01891 {
01892 QByteArray cmd = "DELE ";
01893 cmd += remoteEncoding()->encode(dest_part);
01894 if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
01895 {
01896 iError = ERR_CANNOT_DELETE_PARTIAL;
01897 return statusServerError;
01898 }
01899 }
01900 else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
01901 {
01902 flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags;
01903 if (!(flags & KIO::Resume))
01904 {
01905 iError = ERR_FILE_ALREADY_EXIST;
01906 return statusServerError;
01907 }
01908 }
01909 }
01910 else
01911 m_size = 0;
01912
01913 QString dest;
01914
01915
01916 if ( bMarkPartial ) {
01917 kDebug(7102) << "Adding .part extension to " << dest_orig;
01918 dest = dest_part;
01919 } else
01920 dest = dest_orig;
01921
01922 KIO::fileoffset_t offset = 0;
01923
01924
01925 if( (flags & KIO::Resume) && m_size > 0 )
01926 {
01927 offset = m_size;
01928 if(iCopyFile != -1)
01929 {
01930 if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 )
01931 {
01932 iError = ERR_CANNOT_RESUME;
01933 return statusClientError;
01934 }
01935 }
01936 }
01937
01938 if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) )
01939 return statusServerError;
01940
01941 kDebug(7102) << "ftpPut: starting with offset=" << offset;
01942 KIO::fileoffset_t processed_size = offset;
01943
01944 QByteArray buffer;
01945 int result;
01946 int iBlockSize = initialIpcSize;
01947
01948 do
01949 {
01950 if(iCopyFile == -1)
01951 {
01952 dataReq();
01953 result = readData( buffer );
01954 }
01955 else
01956 {
01957 if(processed_size-offset > 1024 * 64)
01958 iBlockSize = maximumIpcSize;
01959 buffer.resize(iBlockSize);
01960 result = ::read(iCopyFile, buffer.data(), buffer.size());
01961 if(result < 0)
01962 iError = ERR_COULD_NOT_WRITE;
01963 else
01964 buffer.resize(result);
01965 }
01966
01967 if (result > 0)
01968 {
01969 m_data->write( buffer );
01970 while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {}
01971 processed_size += result;
01972 processedSize (processed_size);
01973 }
01974 }
01975 while ( result > 0 );
01976
01977 if (result != 0)
01978 {
01979 ftpCloseCommand();
01980 kDebug(7102) << "Error during 'put'. Aborting.";
01981 if (bMarkPartial)
01982 {
01983
01984 if ( ftpSize( dest, 'I' ) &&
01985 ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
01986 {
01987 QByteArray cmd = "DELE ";
01988 cmd += remoteEncoding()->encode(dest);
01989 (void) ftpSendCmd( cmd );
01990 }
01991 }
01992 return statusServerError;
01993 }
01994
01995 if ( !ftpCloseCommand() )
01996 {
01997 iError = ERR_COULD_NOT_WRITE;
01998 return statusServerError;
01999 }
02000
02001
02002 if ( bMarkPartial )
02003 {
02004 kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")";
02005 if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) )
02006 {
02007 iError = ERR_CANNOT_RENAME_PARTIAL;
02008 return statusServerError;
02009 }
02010 }
02011
02012
02013 if ( permissions != -1 )
02014 {
02015 if ( m_user == FTP_LOGIN )
02016 kDebug(7102) << "Trying to chmod over anonymous FTP ???";
02017
02018 if ( ! ftpChmod( dest_orig, permissions ) )
02019 {
02020
02021
02022
02023 }
02024 }
02025
02026
02027 finished();
02028 return statusSuccess;
02029 }
02030
02031
02034 bool Ftp::ftpSize( const QString & path, char mode )
02035 {
02036 m_size = UnknownSize;
02037 if( !ftpDataMode(mode) )
02038 return false;
02039
02040 QByteArray buf;
02041 buf = "SIZE ";
02042 buf += remoteEncoding()->encode(path);
02043 if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02044 return false;
02045
02046
02047 const char* psz = ftpResponse(4);
02048 if(!psz)
02049 return false;
02050 m_size = charToLongLong(psz);
02051 if (!m_size) m_size = UnknownSize;
02052 return true;
02053 }
02054
02055 bool Ftp::ftpFileExists(const QString& path)
02056 {
02057 QByteArray buf;
02058 buf = "SIZE ";
02059 buf += remoteEncoding()->encode(path);
02060 if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
02061 return false;
02062
02063
02064 const char* psz = ftpResponse(4);
02065 return psz != 0;
02066 }
02067
02068
02069
02070
02071
02072
02073
02074
02075 bool Ftp::ftpDataMode(char cMode)
02076 {
02077 if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I';
02078 else if(cMode == 'a') cMode = 'A';
02079 else if(cMode != 'A') cMode = 'I';
02080
02081 kDebug(7102) << "want" << cMode << "has" << m_cDataMode;
02082 if(m_cDataMode == cMode)
02083 return true;
02084
02085 QByteArray buf = "TYPE ";
02086 buf += cMode;
02087 if( !ftpSendCmd(buf) || (m_iRespType != 2) )
02088 return false;
02089 m_cDataMode = cMode;
02090 return true;
02091 }
02092
02093
02094 bool Ftp::ftpFolder(const QString& path, bool bReportError)
02095 {
02096 QString newPath = path;
02097 int iLen = newPath.length();
02098 if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1);
02099
02100
02101 if(m_currentPath == newPath)
02102 return true;
02103
02104 QByteArray tmp = "cwd ";
02105 tmp += remoteEncoding()->encode(newPath);
02106 if( !ftpSendCmd(tmp) )
02107 return false;
02108 if(m_iRespType != 2)
02109 {
02110 if(bReportError)
02111 error(ERR_CANNOT_ENTER_DIRECTORY, path);
02112 return false;
02113 }
02114 m_currentPath = newPath;
02115 return true;
02116 }
02117
02118
02119
02120
02121
02122
02123
02124 void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags )
02125 {
02126 int iError = 0;
02127 int iCopyFile = -1;
02128 StatusCode cs = statusSuccess;
02129 bool bSrcLocal = src.isLocalFile();
02130 bool bDestLocal = dest.isLocalFile();
02131 QString sCopyFile;
02132
02133 if(bSrcLocal && !bDestLocal)
02134 {
02135 sCopyFile = src.toLocalFile();
02136 kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path();
02137 cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags);
02138 if( cs == statusServerError) sCopyFile = dest.url();
02139 }
02140 else if(!bSrcLocal && bDestLocal)
02141 {
02142 sCopyFile = dest.toLocalFile();
02143 kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile;
02144 cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags);
02145 if( cs == statusServerError ) sCopyFile = src.url();
02146 }
02147 else {
02148 error( ERR_UNSUPPORTED_ACTION, QString() );
02149 return;
02150 }
02151
02152
02153 if(iCopyFile != -1)
02154 ::close(iCopyFile);
02155 if(iError)
02156 error(iError, sCopyFile);
02157 ftpCloseCommand();
02158 }
02159
02160
02161 Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile,
02162 const KUrl& url, int permissions, KIO::JobFlags flags)
02163 {
02164
02165 KDE_struct_stat buff;
02166 const QByteArray sSrc( QFile::encodeName(sCopyFile) );
02167 bool bSrcExists = (KDE_stat( sSrc.data(), &buff ) != -1);
02168 if(bSrcExists)
02169 { if(S_ISDIR(buff.st_mode))
02170 {
02171 iError = ERR_IS_DIRECTORY;
02172 return statusClientError;
02173 }
02174 }
02175 else
02176 {
02177 iError = ERR_DOES_NOT_EXIST;
02178 return statusClientError;
02179 }
02180
02181 iCopyFile = KDE_open( sSrc.data(), O_RDONLY );
02182 if(iCopyFile == -1)
02183 {
02184 iError = ERR_CANNOT_OPEN_FOR_READING;
02185 return statusClientError;
02186 }
02187
02188
02189 totalSize(buff.st_size);
02190 #ifdef ENABLE_CAN_RESUME
02191 return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume);
02192 #else
02193 return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume);
02194 #endif
02195 }
02196
02197
02198 Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile,
02199 const KUrl& url, int permissions, KIO::JobFlags flags)
02200 {
02201
02202 KDE_struct_stat buff;
02203 const QByteArray sDest( QFile::encodeName(sCopyFile) );
02204 bool bDestExists = (KDE_stat( sDest.data(), &buff ) != -1);
02205 if(bDestExists)
02206 { if(S_ISDIR(buff.st_mode))
02207 {
02208 iError = ERR_IS_DIRECTORY;
02209 return statusClientError;
02210 }
02211 if(!(flags & KIO::Overwrite))
02212 {
02213 iError = ERR_FILE_ALREADY_EXIST;
02214 return statusClientError;
02215 }
02216 }
02217
02218
02219 const QByteArray sPart = QFile::encodeName(sCopyFile + ".part");
02220 bool bResume = false;
02221 bool bPartExists = (KDE_stat( sPart.data(), &buff ) != -1);
02222 bool bMarkPartial = config()->readEntry("MarkPartial", true);
02223 if(bMarkPartial && bPartExists && buff.st_size > 0)
02224 {
02225 if(S_ISDIR(buff.st_mode))
02226 {
02227 iError = ERR_DIR_ALREADY_EXIST;
02228 return statusClientError;
02229 }
02230
02231 #ifdef ENABLE_CAN_RESUME
02232 bResume = canResume( buff.st_size );
02233 #else
02234 bResume = true;
02235 #endif
02236 }
02237
02238 if(bPartExists && !bResume)
02239 remove(sPart.data());
02240
02241
02242
02243 if(bDestExists)
02244 remove(sDest.data());
02245
02246
02247
02248 mode_t initialMode;
02249 if (permissions != -1)
02250 initialMode = permissions | S_IWUSR;
02251 else
02252 initialMode = 0666;
02253
02254
02255 KIO::fileoffset_t hCopyOffset = 0;
02256 if(bResume)
02257 {
02258 iCopyFile = KDE_open( sPart.data(), O_RDWR );
02259 hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END);
02260 if(hCopyOffset < 0)
02261 {
02262 iError = ERR_CANNOT_RESUME;
02263 return statusClientError;
02264 }
02265 kDebug(7102) << "copy: resuming at " << hCopyOffset;
02266 }
02267 else
02268 iCopyFile = KDE_open(sPart.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
02269
02270 if(iCopyFile == -1)
02271 {
02272 kDebug(7102) << "copy: ### COULD NOT WRITE " << sCopyFile;
02273 iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED
02274 : ERR_CANNOT_OPEN_FOR_WRITING;
02275 return statusClientError;
02276 }
02277
02278
02279 StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset);
02280 if( ::close(iCopyFile) && iRes == statusSuccess )
02281 {
02282 iError = ERR_COULD_NOT_WRITE;
02283 iRes = statusClientError;
02284 }
02285 iCopyFile = -1;
02286
02287
02288 if(bMarkPartial)
02289 {
02290 if(iRes == statusSuccess)
02291 {
02292 #ifdef Q_OS_WIN
02293 if ( MoveFileExA( sPart.data(),
02294 sDest.data(),
02295 MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED ) == 0 )
02296 #else
02297 if ( KDE_rename( sPart.data(), sDest.data() ) )
02298 #endif
02299 {
02300 kDebug(7102) << "copy: cannot rename " << sPart << " to " << sDest;
02301 iError = ERR_CANNOT_RENAME_PARTIAL;
02302 iRes = statusClientError;
02303 }
02304 }
02305 else if(KDE_stat( sPart.data(), &buff ) == 0)
02306 {
02307 int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
02308 if (buff.st_size < size)
02309 remove(sPart.data());
02310 }
02311 }
02312 return iRes;
02313 }
02314