📄 ftp.cpp
字号:
// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; c-file-style: "stroustrup" -*-/* This file is part of the KDE libraries Copyright (C) 2000-2006 David Faure <faure@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.*//* Recommended reading explaining FTP details and quirks: http://cr.yp.to/ftp.html (by D.J. Bernstein) RFC: RFC 959 "File Transfer Protocol (FTP)" RFC 1635 "How to Use Anonymous FTP" RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV)*/#define KIO_FTP_PRIVATE_INCLUDE#include "ftp.h"#include <sys/stat.h>#ifdef HAVE_SYS_TIME_H#include <sys/time.h>#endif#ifdef HAVE_SYS_SELECT_H#include <sys/select.h>#endif#include <netinet/in.h>#include <arpa/inet.h>#include <assert.h>#include <ctype.h>#include <errno.h>#include <fcntl.h>#include <netdb.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <signal.h>#if TIME_WITH_SYS_TIME#include <time.h>#endif#include <QtCore/QDir>#include <QtNetwork/QHostAddress>#include <QtNetwork/QTcpServer>#include <kdebug.h>#include <kglobal.h>#include <klocale.h>#include <kcomponentdata.h>#include <kmimetype.h>#include <kio/ioslave_defaults.h>#include <kio/slaveconfig.h>#include <kremoteencoding.h>#include <ksocketfactory.h>#include <kde_file.h>#include <kconfiggroup.h>#include <kmessagebox.h>#ifdef HAVE_STRTOLL #define charToLongLong(a) strtoll(a, 0, 10)#else #define charToLongLong(a) strtol(a, 0, 10)#endif#define FTP_LOGIN "anonymous"#define FTP_PASSWD "anonymous@"//#undef kDebug#define ENABLE_CAN_RESUME// JPF: somebody should find a better solution for this or move this to KIO// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!namespace KIO { enum buffersizes { /** * largest buffer size that should be used to transfer data between * KIO slaves using the data() function */ maximumIpcSize = 32 * 1024, /** * this is a reasonable value for an initial read() that a KIO slave * can do to obtain data via a slow network connection. */ initialIpcSize = 2 * 1024, /** * recommended size of a data block passed to findBufferFileType() */ mimimumMimeSize = 1024 }; // JPF: this helper was derived from write_all in file.cc (FileProtocol). static // JPF: in ftp.cc we make it static /** * This helper handles some special issues (blocking and interrupted * system call) when writing to a file handle. * * @return 0 on success or an error code on failure (ERR_COULD_NOT_WRITE, * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). */ int WriteToFile(int fd, const char *buf, size_t len) { while (len > 0) { // JPF: shouldn't there be a KDE_write? ssize_t written = write(fd, buf, len); if (written >= 0) { buf += written; len -= written; continue; } switch(errno) { case EINTR: continue; case EPIPE: return ERR_CONNECTION_BROKEN; case ENOSPC: return ERR_DISK_FULL; default: return ERR_COULD_NOT_WRITE; } } return 0; }}KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;using namespace KIO;extern "C" int KDE_EXPORT kdemain( int argc, char **argv ){ KComponentData componentData( "kio_ftpc", "kdelibs4" ); ( void ) KGlobal::locale(); kDebug(7102) << "Starting " << getpid(); if (argc != 4) { fprintf(stderr, "Usage: kio_ftps protocol domain-socket1 domain-socket2\n"); exit(-1); } Ftp slave(argv[2], argv[3]); slave.dispatchLoop(); kDebug(7102) << "Done"; return 0;}//===============================================================================// Ftp//===============================================================================Ftp::Ftp( const QByteArray &pool, const QByteArray &app ) : SlaveBase( "ftps", pool, app ){ // init the socket data m_data = m_control = NULL; ftpCloseControlConnection(); // init other members m_port = 0; kDebug(7102) << "Ftp::Ftp()";}Ftp::~Ftp(){ kDebug(7102) << "Ftp::~Ftp()"; closeConnection();}/** * This closes a data connection opened by ftpOpenDataConnection(). */void Ftp::ftpCloseDataConnection(){ delete m_data; m_data = NULL;}/** * This closes a control connection opened by ftpOpenControlConnection() and reinits the * related states. This method gets called from the constructor with m_control = NULL. */void Ftp::ftpCloseControlConnection(){ m_extControl = 0; delete m_control; m_control = NULL; m_cDataMode = 0; m_bLoggedOn = false; // logon needs control connction m_bTextMode = false; m_bBusy = false;}/** * Returns the last response from the server (iOffset >= 0) -or- reads a new response * (iOffset < 0). The result is returned (with iOffset chars skipped for iOffset > 0). */const char* Ftp::ftpResponse(int iOffset){ assert(m_control != NULL); // must have control connection socket const char *pTxt = m_lastControlLine.data(); // read the next line ... if(iOffset < 0) { int iMore = 0; m_iRespCode = 0; // If the server sends multiline responses "nnn-text" we loop here until // a final "nnn text" line is reached. Only data from the final line will // be stored. Some servers (OpenBSD) send a single "nnn-" followed by // optional lines that start with a space and a final "nnn text" line. do { while (!m_control->canReadLine() && m_control->waitForReadyRead()) {} m_lastControlLine = m_control->readLine(); pTxt = m_lastControlLine.data(); int nBytes = m_lastControlLine.size(); int iCode = atoi(pTxt); if(iCode > 0) m_iRespCode = iCode; // ignore lines starting with a space in multiline response if(iMore != 0 && pTxt[0] == 32) ; // otherwise the line should start with "nnn-" or "nnn " else if(nBytes < 4 || iCode < 100) iMore = 0; // we got a valid line, now check for multiline responses ... else if(iMore == 0 && pTxt[3] == '-') iMore = iCode; // "nnn " ends multiline mode ... else if(iMore != 0 && (iMore != iCode || pTxt[3] != '-')) iMore = 0; if(iMore != 0) kDebug(7102) << " > " << pTxt; } while(iMore != 0); kDebug(7102) << "resp> " << pTxt; m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0; } // return text with offset ... while(iOffset-- > 0 && pTxt[0]) pTxt++; return pTxt;}void Ftp::closeConnection(){ if(m_control != NULL || m_data != NULL) kDebug(7102) << "Ftp::closeConnection m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy; if(m_bBusy) // ftpCloseCommand not called { kWarning(7102) << "Ftp::closeConnection Abandoned data stream"; ftpCloseDataConnection(); } if(m_bLoggedOn) // send quit { if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) ) kWarning(7102) << "Ftp::closeConnection QUIT returned error: " << m_iRespCode; } // close the data and control connections ... ftpCloseDataConnection(); ftpCloseControlConnection();}void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user, const QString& _pass ){ kDebug(7102) << "Ftp::setHost (" << getpid() << "): " << _host << " port=" << _port; m_proxyURL = metaData("UseProxy"); m_bUseProxy = (m_proxyURL.isValid() && m_proxyURL.protocol() == "ftp"); if ( m_host != _host || m_port != _port || m_user != _user || m_pass != _pass ) closeConnection(); m_host = _host; m_port = _port; m_user = _user; m_pass = _pass;}void Ftp::openConnection(){ ftpOpenConnection(loginExplicit);}bool Ftp::ftpOpenConnection (LoginMode loginMode){ // check for implicit login if we are already logged on ... if(loginMode == loginImplicit && m_bLoggedOn) { assert(m_control != NULL); // must have control connection socket return true; } kDebug(7102) << "ftpOpenConnection " << m_host << ":" << m_port << " " << m_user << " [password hidden]"; infoMessage( i18n("Opening connection to host %1", m_host) ); if ( m_host.isEmpty() ) { error( ERR_UNKNOWN_HOST, QString() ); return false; } assert( !m_bLoggedOn ); m_initialPath.clear(); m_currentPath.clear(); QString host = m_bUseProxy ? m_proxyURL.host() : m_host; int port = m_bUseProxy ? m_proxyURL.port() : m_port; if (!ftpOpenControlConnection(host, port) ) return false; // error emitted by ftpOpenControlConnection infoMessage( i18n("Connected to host %1", m_host) ); if(loginMode != loginDefered) { m_bLoggedOn = ftpLogin(); if( !m_bLoggedOn ) return false; // error emitted by ftpLogin } m_bTextMode = config()->readEntry("textmode", false); connected(); return true;}/** * Called by @ref openConnection. It opens the control connection to the ftp server. * * @return true on success. */bool Ftp::ftpOpenControlConnection( const QString &host, int port, bool ignoreSslErrors ){ m_bIgnoreSslErrors = ignoreSslErrors; // implicitly close, then try to open a new connection ... closeConnection(); QString sErrorMsg; // now connect to the server and read the login message ... if (port == 0) port = 21; // default FTP port m_control = new QSslSocket(); KSocketFactory::synchronousConnectToHost(m_control, "ftps", host, port, connectTimeout() * 1000); int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT; // on connect success try to read the server message... if(iErrorCode == 0) { const char* psz = ftpResponse(-1); if(m_iRespType != 2) { // login not successful, do we have an message text? if(psz[0]) sErrorMsg = i18n("%1.\n\nReason: %2", host, psz); iErrorCode = ERR_COULD_NOT_CONNECT; } } else { if (m_control->error() == QAbstractSocket::HostNotFoundError) iErrorCode = ERR_UNKNOWN_HOST; sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString()); } // Send unencrypted "AUTH TLS" request. // TODO: redirect to FTP fallback on negative response. if(iErrorCode == 0) { bool authSucc = (ftpSendCmd("AUTH TLS") && (m_iRespCode == 234)); if (!authSucc) { iErrorCode = ERR_SLAVE_DEFINED; sErrorMsg = i18n("The FTP server does not seem to support ftps-encryption."); } } // Starts the encryption if(iErrorCode == 0) { // If the method has been called with ignoreSslErrors, make the ssl socket // ignore the errors during handshakes. if (ignoreSslErrors) m_control->ignoreSslErrors(); m_control->startClientEncryption(); if (!m_control->waitForEncrypted(connectTimeout() * 1000)) { // It is quite common, that the TLS handshake fails, as the majority // of certificates are self signed, and thus the host cannot be verified. // If the user wants to continue nevertheless, this method is called // again, with the "ignoreSslErrors" flag. bool doNotIgnore = false; QList<QSslError> errors = m_control->sslErrors(); for (int i = 0; i < errors.size(); ++i) { if (messageBox(WarningContinueCancel, errors.at(i).errorString(), "TLS Handshake Error", i18n("&Continue"), i18n("&Cancel")) == KMessageBox::Cancel) doNotIgnore = false; } if (doNotIgnore) { iErrorCode = ERR_SLAVE_DEFINED; sErrorMsg = i18n("TLS Handshake Error."); } else {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -