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