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