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