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