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