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