diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt index 7a62d81e..c9316cd0 100644 --- a/cmake/modules/CMakeLists.txt +++ b/cmake/modules/CMakeLists.txt @@ -40,7 +40,6 @@ set(cmakeFiles FindLibLZMA.cmake FindLIBPARTED.cmake FindLibSpectre.cmake - FindLibSSH.cmake FindLibTorrent.cmake FindLibUSB.cmake FindLightDM.cmake diff --git a/cmake/modules/FindLibSSH.cmake b/cmake/modules/FindLibSSH.cmake deleted file mode 100644 index 5b2a34f9..00000000 --- a/cmake/modules/FindLibSSH.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# Try to find LibSSH, once done this will define: -# -# LIBSSH_FOUND - system has LibSSH -# LIBSSH_INCLUDE_DIRS - the LibSSH include directory -# LIBSSH_LIBRARIES - the libraries needed to use LibSSH -# LIBSSH_DEFINITIONS - compiler switches required for using LibSSH -# -# Copyright (c) 2020 Ivailo Monev -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -find_package(PkgConfig REQUIRED) -pkg_check_modules(PC_LIBSSH QUIET libssh) - -set(LIBSSH_INCLUDE_DIR ${PC_LIBSSH_INCLUDE_DIRS}) -set(LIBSSH_LIBRARIES ${PC_LIBSSH_LIBRARIES}) -set(LIBSSH_VERSION ${PC_LIBSSH_VERSION}) -set(LIBSSH_DEFINITIONS ${PC_LIBSSH_CFLAGS_OTHER}) - -if(NOT LIBSSH_INCLUDE_DIR OR NOT LIBSSH_LIBRARIES) - find_path(LIBSSH_INCLUDE_DIR - NAMES libssh/libssh.h - HINTS $ENV{LIBSSHDIR}/include - ) - - find_library(LIBSSH_LIBRARIES - NAMES ssh - HINTS $ENV{LIBSSHDIR}/lib - ) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibSSH - VERSION_VAR LIBSSH_VERSION - REQUIRED_VARS LIBSSH_LIBRARIES LIBSSH_INCLUDE_DIR -) - -mark_as_advanced(LIBSSH_INCLUDE_DIR LIBSSH_LIBRARIES) diff --git a/kdecore/kdebug.areas b/kdecore/kdebug.areas index 7ecc9183..be2efb49 100644 --- a/kdecore/kdebug.areas +++ b/kdecore/kdebug.areas @@ -65,7 +65,7 @@ 7050 kio (KHTTP) 7101 kio_file 7102 kio_ftp -7103 kio_http +7103 kio_curl 7104 kidletime 51003 kexiv2 51004 kpasswdstore diff --git a/kioslave/CMakeLists.txt b/kioslave/CMakeLists.txt index caf5dd02..8dacffbe 100644 --- a/kioslave/CMakeLists.txt +++ b/kioslave/CMakeLists.txt @@ -1,4 +1,3 @@ add_subdirectory( file ) -add_subdirectory( http ) -add_subdirectory( ftp ) +add_subdirectory( curl ) diff --git a/kioslave/http/CMakeLists.txt b/kioslave/curl/CMakeLists.txt similarity index 54% rename from kioslave/http/CMakeLists.txt rename to kioslave/curl/CMakeLists.txt index 6af6cf05..c99d2b4e 100644 --- a/kioslave/http/CMakeLists.txt +++ b/kioslave/curl/CMakeLists.txt @@ -1,4 +1,4 @@ -project(kioslave-http) +project(kioslave-curl) include_directories( ${KDE4_KIO_INCLUDES} @@ -7,19 +7,19 @@ include_directories( ########### next target ############### -add_executable(kio_http http.cpp) +add_executable(kio_curl kio_curl.cpp) -target_link_libraries(kio_http +target_link_libraries(kio_curl ${CURL_LIBRARIES} kdecore kio ) -install(TARGETS kio_http DESTINATION ${KDE4_LIBEXEC_INSTALL_DIR}) +install(TARGETS kio_curl DESTINATION ${KDE4_LIBEXEC_INSTALL_DIR}) ########### install files ############### install( - FILES http.protocol https.protocol + FILES http.protocol https.protocol ftp.protocol sftp.protocol DESTINATION ${KDE4_SERVICES_INSTALL_DIR} ) diff --git a/kioslave/curl/ftp.protocol b/kioslave/curl/ftp.protocol new file mode 100644 index 00000000..4d4ceb21 --- /dev/null +++ b/kioslave/curl/ftp.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_curl +protocol=ftp +copyToFile=false +copyFromFile=false +listing=true +reading=true +writing=false +makedir=false +deleting=false +moving=false +ProxiedBy=http +Icon=folder-remote +maxInstances=20 +maxInstancesPerHost=5 +X-DocPath=kioslave/curl/index.html +Class=:internet diff --git a/kioslave/http/http.protocol b/kioslave/curl/http.protocol similarity index 82% rename from kioslave/http/http.protocol rename to kioslave/curl/http.protocol index 69964207..e617236d 100644 --- a/kioslave/http/http.protocol +++ b/kioslave/curl/http.protocol @@ -1,11 +1,11 @@ [Protocol] -exec=kio_http +exec=kio_curl protocol=http copyToFile=false copyFromFile=false listing=false reading=true -writing=true +writing=false makedir=false deleting=false moving=false @@ -15,5 +15,5 @@ maxInstances=20 maxInstancesPerHost=5 defaultMimetype=application/octet-stream determineMimetypeFromExtension=false -X-DocPath=kioslave/http/index.html +X-DocPath=kioslave/curl/index.html Class=:internet diff --git a/kioslave/http/https.protocol b/kioslave/curl/https.protocol similarity index 82% rename from kioslave/http/https.protocol rename to kioslave/curl/https.protocol index 5aa458a2..13b15216 100644 --- a/kioslave/http/https.protocol +++ b/kioslave/curl/https.protocol @@ -1,11 +1,11 @@ [Protocol] -exec=kio_http +exec=kio_curl protocol=https copyToFile=false copyFromFile=false listing=false reading=true -writing=true +writing=false makedir=false deleting=false moving=false @@ -15,5 +15,5 @@ maxInstances=20 maxInstancesPerHost=5 defaultMimetype=application/octet-stream determineMimetypeFromExtension=false -X-DocPath=kioslave/http/index.html +X-DocPath=kioslave/curl/index.html Class=:internet diff --git a/kioslave/http/http.cpp b/kioslave/curl/kio_curl.cpp similarity index 57% rename from kioslave/http/http.cpp rename to kioslave/curl/kio_curl.cpp index def9bd43..44e972a9 100644 --- a/kioslave/http/http.cpp +++ b/kioslave/curl/kio_curl.cpp @@ -16,17 +16,112 @@ Boston, MA 02110-1301, USA. */ -#include "http.h" -#include "kdebug.h" +#include "kio_curl.h" #include "kcomponentdata.h" +#include "kmimetype.h" +#include "kdebug.h" #include #include #include #include +#include #include +#define KIO_CURL_ERROR(CODE) \ + const QString curlerror = QString::fromAscii(curl_easy_strerror(CODE)); \ + kWarning(7103) << "curl error" << curlerror; \ + error(KIO::ERR_SLAVE_DEFINED, curlerror); + +// for reference: +// https://linux.die.net/man/3/strmode +// https://datatracker.ietf.org/doc/html/rfc959 +// https://curl.se/libcurl/c/pop3-stat.html + +// TODO: what is the limit? +static const int filepathmax = 1024; + +static inline int ftpUserModeFromChar(const char modechar, const int rmode, const int wmode, const int xmode) +{ + mode_t result = 0; + switch (modechar) { + case '-': { + break; + } + case 'r': { + result |= rmode; + break; + } + case 'w': { + result |= wmode; + break; + } + case 'x': { + result |= xmode; + break; + } + default: { + kWarning(7103) << "Invalid FTP mode char" << modechar; + break; + } + } + return result; +} + +static inline mode_t ftpModeFromString(const char* modestring) +{ + mode_t result = 0; + switch (modestring[0]) { + case '-': { + result |= S_IFREG; + break; + } + case 'b': { + result |= S_IFBLK; + break; + } + case 'c': { + result |= S_IFCHR; + break; + } + case 'd': { + result |= S_IFDIR; + break; + } + case 'l': { + result |= S_IFLNK; + break; + } + case 'p': { + result |= S_IFIFO; + break; + } + case 's': { + result |= S_IFSOCK; + break; + } + default: { + kWarning(7103) << "Invalid FTP mode string" << modestring; + break; + } + } + + result |= ftpUserModeFromChar(modestring[1], S_IRUSR, S_IWUSR, S_IXUSR); + result |= ftpUserModeFromChar(modestring[2], S_IRUSR, S_IWUSR, S_IXUSR); + result |= ftpUserModeFromChar(modestring[3], S_IRUSR, S_IWUSR, S_IXUSR); + + result |= ftpUserModeFromChar(modestring[4], S_IRGRP, S_IWGRP, S_IXGRP); + result |= ftpUserModeFromChar(modestring[5], S_IRGRP, S_IWGRP, S_IXGRP); + result |= ftpUserModeFromChar(modestring[6], S_IRGRP, S_IWGRP, S_IXGRP); + + result |= ftpUserModeFromChar(modestring[7], S_IROTH, S_IWOTH, S_IXOTH); + result |= ftpUserModeFromChar(modestring[8], S_IROTH, S_IWOTH, S_IXOTH); + result |= ftpUserModeFromChar(modestring[9], S_IROTH, S_IWOTH, S_IXOTH); + + return result; +} + static inline QByteArray curlProxyBytes(const QString &proxy) { const KUrl proxyurl(proxy); @@ -171,68 +266,52 @@ static inline KIO::Error curlToKIOError(const CURLcode curlcode, CURL *curl) size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) { - HttpProtocol* httpprotocol = static_cast(userdata); - if (!httpprotocol) { + CurlProtocol* curlprotocol = static_cast(userdata); + if (!curlprotocol) { return 0; } // size should always be 1 Q_ASSERT(size == 1); - httpprotocol->slotData(ptr, nmemb); + curlprotocol->slotData(ptr, nmemb); return nmemb; } -size_t curlReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -{ - HttpProtocol* httpprotocol = static_cast(userdata); - if (!httpprotocol) { - return 0; - } - httpprotocol->dataReq(); - QByteArray kioreadbuffer; - const int kioreadresult = httpprotocol->readData(kioreadbuffer); - if (kioreadbuffer.size() > nmemb) { - kWarning(7103) << "Request data size larger than the buffer size"; - return 0; - } - ::memcpy(ptr, kioreadbuffer.constData(), kioreadbuffer.size() * sizeof(char)); - return kioreadresult; -} - int curlXFERCallback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { - HttpProtocol* httpprotocol = static_cast(userdata); - if (!httpprotocol) { + CurlProtocol* curlprotocol = static_cast(userdata); + if (!curlprotocol) { return CURLE_BAD_FUNCTION_ARGUMENT; } - if (httpprotocol->aborttransfer) { + if (curlprotocol->aborttransfer) { return CURLE_HTTP_RETURNED_ERROR; } - httpprotocol->slotProgress(KIO::filesize_t(dlnow), KIO::filesize_t(dltotal)); + curlprotocol->slotProgress(KIO::filesize_t(dlnow), KIO::filesize_t(dltotal)); return CURLE_OK; } int main(int argc, char **argv) { QApplication app(argc, argv); - KComponentData componentData("kio_http", "kdelibs4"); + KComponentData componentData("kio_curl", "kdelibs4"); kDebug(7103) << "Starting" << ::getpid(); if (argc != 2) { - ::fprintf(stderr, "Usage: kio_http app-socket\n"); + ::fprintf(stderr, "Usage: kio_curl app-socket\n"); ::exit(-1); } - HttpProtocol slave(argv[1]); + CurlProtocol slave(argv[1]); slave.dispatchLoop(); kDebug(7103) << "Done"; return 0; } -HttpProtocol::HttpProtocol(const QByteArray &app) - : SlaveBase("http", app), - aborttransfer(false), m_emitmime(true), +CurlProtocol::CurlProtocol(const QByteArray &app) + : SlaveBase("curl", app), + aborttransfer(false), + m_emitmime(true), m_ishttp(false), m_isftp(false), m_issftp(false), m_collectdata(false), m_curl(nullptr), m_curlheaders(nullptr) { m_curl = curl_easy_init(); @@ -242,7 +321,7 @@ HttpProtocol::HttpProtocol(const QByteArray &app) } } -HttpProtocol::~HttpProtocol() +CurlProtocol::~CurlProtocol() { if (m_curlheaders) { curl_slist_free_all(m_curlheaders); @@ -252,9 +331,9 @@ HttpProtocol::~HttpProtocol() } } -void HttpProtocol::stat(const KUrl &url) +void CurlProtocol::stat(const KUrl &url) { - kDebug(7103) << "URL" << url.prettyUrl(); + kDebug(7103) << "Stat URL" << url.prettyUrl(); if (redirectUrl(url)) { return; @@ -264,9 +343,18 @@ void HttpProtocol::stat(const KUrl &url) return; } - // NOTE: do not set CURLOPT_NOBODY, server may not send some headers - CURLcode curlresult = curl_easy_perform(m_curl); - kDebug(7103) << "Transfer result" << curlresult; + CURLcode curlresult = CURLE_OK; + // NOTE: do not set CURLOPT_NOBODY for HTTP, server may not send some headers + if (m_isftp || m_issftp) { + curlresult = curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); + if (curlresult != CURLE_OK) { + KIO_CURL_ERROR(curlresult); + return; + } + } + + curlresult = curl_easy_perform(m_curl); + kDebug(7103) << "Stat result" << curlresult; if (curlresult != CURLE_OK) { const KIO::Error kioerror = curlToKIOError(curlresult, m_curl); if (kioerror == KIO::ERR_COULD_NOT_LOGIN) { @@ -279,12 +367,14 @@ void HttpProtocol::stat(const KUrl &url) } QString httpmimetype; - char *curlcontenttype = nullptr; - curlresult = curl_easy_getinfo(m_curl, CURLINFO_CONTENT_TYPE, &curlcontenttype); - if (curlresult == CURLE_OK) { - httpmimetype = HTTPMIMEType(QString::fromAscii(curlcontenttype)); - } else { - kWarning(7103) << "Could not get content type info" << curl_easy_strerror(curlresult); + if (m_ishttp) { + char *curlcontenttype = nullptr; + curlresult = curl_easy_getinfo(m_curl, CURLINFO_CONTENT_TYPE, &curlcontenttype); + if (curlresult == CURLE_OK) { + httpmimetype = HTTPMIMEType(QString::fromAscii(curlcontenttype)); + } else { + kWarning(7103) << "Could not get content type info" << curl_easy_strerror(curlresult); + } } curl_off_t curlfiletime = 0; @@ -300,9 +390,9 @@ void HttpProtocol::stat(const KUrl &url) } KIO::UDSEntry kioudsentry; - kDebug(7103) << "HTTP last-modified" << curlfiletime; - kDebug(7103) << "HTTP content-length" << curlcontentlength; - kDebug(7103) << "HTTP content-type" << httpmimetype; + kDebug(7103) << "File time" << curlfiletime; + kDebug(7103) << "Content length" << curlcontentlength; + kDebug(7103) << "MIME type" << httpmimetype; kioudsentry.insert(KIO::UDSEntry::UDS_NAME, url.fileName()); kioudsentry.insert(KIO::UDSEntry::UDS_SIZE, qlonglong(curlcontentlength)); kioudsentry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, qlonglong(curlfiletime)); @@ -314,9 +404,111 @@ void HttpProtocol::stat(const KUrl &url) finished(); } -void HttpProtocol::get(const KUrl &url) +void CurlProtocol::listDir(const KUrl &url) { - kDebug(7103) << "URL" << url.prettyUrl(); + kDebug(7103) << "List URL" << url.prettyUrl(); + + KUrl urlhack(url); + urlhack = KUrl(url.url(KUrl::AddTrailingSlash)); + if (redirectUrl(urlhack)) { + return; + } + + if (!setupCurl(urlhack)) { + return; + } + + if (!m_isftp && !m_issftp) { + // only for FTP or SFTP + error(KIO::ERR_INTERNAL, url.prettyUrl()); + return; + } + + m_collectdata = true; + + CURLcode curlresult = curl_easy_perform(m_curl); + kDebug(7103) << "List result" << curlresult; + if (curlresult != CURLE_OK) { + const KIO::Error kioerror = curlToKIOError(curlresult, m_curl); + if (kioerror == KIO::ERR_COULD_NOT_LOGIN) { + if (authUrl(url)) { + return; + } + } + error(kioerror, url.prettyUrl()); + return; + } + + KIO::UDSEntry statentry; + char ftpmode[11]; + int ftpint1 = 0; + char ftpowner[128]; + char ftpgroup[128]; + int ftpsize = 0; + char ftpmonth[4]; + int ftpday = 0; + char ftpyearortime[6]; + char ftpfilepath[filepathmax]; + char ftplinkpath[filepathmax]; + foreach(const QByteArray &line, m_writedata.split('\n')) { + if (line.isEmpty()) { + continue; + } + + ::memset(ftpmode, 0, sizeof(ftpmode) * sizeof(char)); + ftpint1 = 0; + ::memset(ftpowner, 0, sizeof(ftpowner) * sizeof(char)); + ::memset(ftpgroup, 0, sizeof(ftpgroup) * sizeof(char)); + ftpsize = 0; + ::memset(ftpmonth, 0, sizeof(ftpmonth) * sizeof(char)); + ftpday = 0; + ::memset(ftpyearortime, 0, sizeof(ftpyearortime) * sizeof(char)); + ::memset(ftpfilepath, 0, filepathmax * sizeof(char)); + ::memset(ftplinkpath, 0, filepathmax * sizeof(char)); + const int sscanfresult = ::sscanf( + line.constData(), + "%s %d %s %s %d %s %d %s %s -> %s", + ftpmode, &ftpint1, ftpowner, ftpgroup, &ftpsize, ftpmonth, &ftpday, ftpyearortime, ftpfilepath, ftplinkpath + ); + // qDebug() << Q_FUNC_INFO << ftpmode << ftpint1 << ftpowner << ftpgroup << ftpsize << ftpmonth << ftpday << ftpyearortime << ftpfilepath << ftplinkpath; + if (sscanfresult == 10) { + const mode_t stdmode = ftpModeFromString(ftpmode); + statentry.insert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(ftpfilepath)); + statentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, stdmode & S_IFMT); + statentry.insert(KIO::UDSEntry::UDS_ACCESS, stdmode & 07777); + statentry.insert(KIO::UDSEntry::UDS_SIZE, ftpsize); + statentry.insert(KIO::UDSEntry::UDS_USER, QString::fromLatin1(ftpowner)); + statentry.insert(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1(ftpgroup)); + // link paths to current path causes KIO to do strange things + if (ftplinkpath[0] != '.' && ftplinkpath[1] != 0) { + statentry.insert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(ftplinkpath)); + } + if (ftpsize <= 0) { + statentry.insert(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1("application/x-zerosize")); + } + listEntry(statentry, false); + } else if (sscanfresult == 9) { + const mode_t stdmode = ftpModeFromString(ftpmode); + statentry.insert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(ftpfilepath)); + statentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, stdmode & S_IFMT); + statentry.insert(KIO::UDSEntry::UDS_ACCESS, stdmode & 07777); + statentry.insert(KIO::UDSEntry::UDS_SIZE, ftpsize); + statentry.insert(KIO::UDSEntry::UDS_USER, QString::fromLatin1(ftpowner)); + statentry.insert(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1(ftpgroup)); + listEntry(statentry, false); + } else { + kWarning(7103) << "Invalid FTP data line" << line << sscanfresult; + } + } + statentry.clear(); + listEntry(statentry, true); + + finished(); +} + +void CurlProtocol::get(const KUrl &url) +{ + kDebug(7103) << "Get URL" << url.prettyUrl(); if (redirectUrl(url)) { return; @@ -327,7 +519,7 @@ void HttpProtocol::get(const KUrl &url) } CURLcode curlresult = curl_easy_perform(m_curl); - kDebug(7103) << "Transfer result" << curlresult; + kDebug(7103) << "Get result" << curlresult; if (curlresult != CURLE_OK) { const KIO::Error kioerror = curlToKIOError(curlresult, m_curl); if (kioerror == KIO::ERR_COULD_NOT_LOGIN) { @@ -342,74 +534,46 @@ void HttpProtocol::get(const KUrl &url) finished(); } - -void HttpProtocol::put(const KUrl &url, int permissions, KIO::JobFlags flags) -{ - kDebug(7103) << "URL" << url.prettyUrl(); - - // no permissions to set, it is POST - Q_UNUSED(permissions); - Q_UNUSED(flags); - - if (redirectUrl(url)) { - return; - } - - if (!setupCurl(url)) { - return; - } - - CURLcode curlresult = curl_easy_setopt(m_curl, CURLOPT_POST, 1L); - if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); - return; - } - - curlresult = curl_easy_perform(m_curl); - kDebug(7103) << "Transfer result" << curlresult; - if (curlresult != CURLE_OK) { - const KIO::Error kioerror = curlToKIOError(curlresult, m_curl); - if (kioerror == KIO::ERR_COULD_NOT_LOGIN) { - if (authUrl(url)) { - return; - } - } - error(kioerror, url.prettyUrl()); - return; - } - - finished(); -} - -void HttpProtocol::slotData(const char* curldata, const size_t curldatasize) +void CurlProtocol::slotData(const char* curldata, const size_t curldatasize) { if (aborttransfer) { kDebug(7103) << "Transfer still in progress"; return; } + const QByteArray bytedata = QByteArray::fromRawData(curldata, curldatasize); + if (m_emitmime) { m_emitmime = false; - // if it's HTTP error do not send data and MIME, abort transfer - const long httpcode = HTTPCode(m_curl); - if (httpcode >= 400) { - aborttransfer = true; - return; - } + if (m_ishttp) { + // if it's HTTP error do not send data and MIME, abort transfer + const long httpcode = HTTPCode(m_curl); + if (httpcode >= 400) { + aborttransfer = true; + return; + } - QString httpmimetype = QString::fromLatin1("application/octet-stream"); - char *curlcontenttype = nullptr; - CURLcode curlresult = curl_easy_getinfo(m_curl, CURLINFO_CONTENT_TYPE, &curlcontenttype); - if (curlresult == CURLE_OK) { - httpmimetype = HTTPMIMEType(QString::fromAscii(curlcontenttype)); + QString httpmimetype = QString::fromLatin1("application/octet-stream"); + char *curlcontenttype = nullptr; + CURLcode curlresult = curl_easy_getinfo(m_curl, CURLINFO_CONTENT_TYPE, &curlcontenttype); + if (curlresult == CURLE_OK) { + httpmimetype = HTTPMIMEType(QString::fromAscii(curlcontenttype)); + } else { + kWarning(7103) << "Could not get content type info" << curl_easy_strerror(curlresult); + } + mimeType(httpmimetype); } else { - kWarning(7103) << "Could not get content type info" << curl_easy_strerror(curlresult); + KMimeType::Ptr kmimetype = KMimeType::findByNameAndContent(m_url.url(), bytedata); + mimeType(kmimetype->name()); } - mimeType(httpmimetype); } - data(QByteArray::fromRawData(curldata, curldatasize)); + if (m_collectdata) { + m_writedata.append(bytedata); + } else { + data(bytedata); + } curl_off_t curlspeeddownload = 0; CURLcode curlresult = curl_easy_getinfo(m_curl, CURLINFO_SPEED_DOWNLOAD_T, &curlspeeddownload); @@ -421,7 +585,7 @@ void HttpProtocol::slotData(const char* curldata, const size_t curldatasize) } } -void HttpProtocol::slotProgress(KIO::filesize_t received, KIO::filesize_t total) +void CurlProtocol::slotProgress(KIO::filesize_t received, KIO::filesize_t total) { kDebug(7103) << "Received" << received << "from" << total; processedSize(received); @@ -430,7 +594,7 @@ void HttpProtocol::slotProgress(KIO::filesize_t received, KIO::filesize_t total) } } -bool HttpProtocol::redirectUrl(const KUrl &url) +bool CurlProtocol::redirectUrl(const KUrl &url) { // curl cannot verify certs if the host is address, CURLOPT_USE_SSL set to CURLUSESSL_TRY // does not bypass such cases so resolving it manually @@ -452,7 +616,7 @@ bool HttpProtocol::redirectUrl(const KUrl &url) return false; } -bool HttpProtocol::setupCurl(const KUrl &url) +bool CurlProtocol::setupCurl(const KUrl &url) { if (Q_UNLIKELY(!m_curl)) { error(KIO::ERR_OUT_OF_MEMORY, QString::fromLatin1("Null context")); @@ -461,6 +625,13 @@ bool HttpProtocol::setupCurl(const KUrl &url) aborttransfer = false; m_emitmime = true; + const QString urlprotocol = url.protocol(); + m_ishttp = (urlprotocol == QLatin1String("http") || urlprotocol == QLatin1String("https")); + m_isftp = (urlprotocol == QLatin1String("ftp")); + m_issftp = (urlprotocol == QLatin1String("sftp")); + m_collectdata = false; + m_writedata.clear(); + m_url = url; curl_easy_reset(m_curl); curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(m_curl, CURLOPT_FILETIME, 1L); @@ -470,46 +641,47 @@ bool HttpProtocol::setupCurl(const KUrl &url) // curl_easy_setopt(m_curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1L); // breaks XFER info, fixes transfer of chunked content curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); - curl_easy_setopt(m_curl, CURLOPT_READDATA, this); - curl_easy_setopt(m_curl, CURLOPT_READFUNCTION, curlReadCallback); curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); // otherwise the XFER info callback is not called curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, this); curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, curlXFERCallback); curl_easy_setopt(m_curl, CURLOPT_FAILONERROR, 1L); + // TODO: option for this, warning? + curl_easy_setopt(m_curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY); // curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); // debugging const QByteArray urlbytes = url.prettyUrl().toLocal8Bit(); CURLcode curlresult = curl_easy_setopt(m_curl, CURLOPT_URL, urlbytes.constData()); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } #if CURL_AT_LEAST_VERSION(7, 85, 0) - static const char* const curlprotocols = "http,https"; + static const char* const curlprotocols = "http,https,ftp,sftp"; curlresult = curl_easy_setopt(m_curl, CURLOPT_PROTOCOLS_STR, curlprotocols); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } curlresult = curl_easy_setopt(m_curl, CURLOPT_REDIR_PROTOCOLS_STR, curlprotocols); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } #else // deprecated since v7.85.0 - curlresult = curl_easy_setopt(m_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + static const long const curlprotocols = (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_SFTP); + curlresult = curl_easy_setopt(m_curl, CURLOPT_PROTOCOLS, curlprotocols); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } - curlresult = curl_easy_setopt(m_curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curlresult = curl_easy_setopt(m_curl, CURLOPT_REDIR_PROTOCOLS, curlprotocols); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } #endif @@ -520,7 +692,7 @@ bool HttpProtocol::setupCurl(const KUrl &url) const QByteArray useragentbytes = metaData("UserAgent").toAscii(); curlresult = curl_easy_setopt(m_curl, CURLOPT_USERAGENT, useragentbytes.constData()); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } } @@ -533,12 +705,12 @@ bool HttpProtocol::setupCurl(const KUrl &url) kDebug(7103) << "Proxy" << proxybytes << curlproxytype; curlresult = curl_easy_setopt(m_curl, CURLOPT_PROXY, proxybytes.constData()); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } curlresult = curl_easy_setopt(m_curl, CURLOPT_PROXYTYPE, curlproxytype); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } @@ -546,31 +718,32 @@ bool HttpProtocol::setupCurl(const KUrl &url) kDebug(7103) << "No proxy auth" << noproxyauth; curlresult = curl_easy_setopt(m_curl, CURLOPT_PROXYAUTH, noproxyauth ? CURLAUTH_NONE : CURLAUTH_ANY); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } } const bool nowwwauth = (noauth || metaData("no-www-auth") == QLatin1String("true")); kDebug(7103) << "No WWW auth" << nowwwauth; - curlresult = curl_easy_setopt(m_curl, CURLOPT_HTTPAUTH, nowwwauth ? CURLAUTH_NONE : CURLAUTH_ANY); - if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); - return false; + if (m_ishttp) { + curlresult = curl_easy_setopt(m_curl, CURLOPT_HTTPAUTH, nowwwauth ? CURLAUTH_NONE : CURLAUTH_ANY); + if (curlresult != CURLE_OK) { + KIO_CURL_ERROR(curlresult); + return false; + } } - if (!nowwwauth) { const QByteArray urlusername = url.userName().toAscii(); const QByteArray urlpassword = url.password().toAscii(); if (!urlusername.isEmpty() && !urlpassword.isEmpty()) { curlresult = curl_easy_setopt(m_curl, CURLOPT_USERNAME, urlusername.constData()); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } curlresult = curl_easy_setopt(m_curl, CURLOPT_PASSWORD, urlpassword.constData()); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } } @@ -581,7 +754,7 @@ bool HttpProtocol::setupCurl(const KUrl &url) const qlonglong resumeoffset = metaData(QLatin1String("resume")).toLongLong(); curlresult = curl_easy_setopt(m_curl, CURLOPT_RESUME_FROM_LARGE, curl_off_t(resumeoffset)); if (curlresult != CURLE_OK) { - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); + KIO_CURL_ERROR(curlresult); return false; } else { canResume(); @@ -593,34 +766,36 @@ bool HttpProtocol::setupCurl(const KUrl &url) m_curlheaders = nullptr; } - if (hasMetaData(QLatin1String("Languages"))) { - m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept-Language: ") + metaData("Languages").toAscii()); - } + if (m_ishttp) { + if (hasMetaData(QLatin1String("Languages"))) { + m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept-Language: ") + metaData("Languages").toAscii()); + } - if (hasMetaData(QLatin1String("Charsets"))) { - m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept-Charset: ") + metaData("Charsets").toAscii()); - } + if (hasMetaData(QLatin1String("Charsets"))) { + m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept-Charset: ") + metaData("Charsets").toAscii()); + } - if (hasMetaData(QLatin1String("accept"))) { - m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept: ") + metaData("accept").toAscii()); - } + if (hasMetaData(QLatin1String("accept"))) { + m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Accept: ") + metaData("accept").toAscii()); + } - if (hasMetaData(QLatin1String("Authorization"))) { - m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Authorization: ") + metaData("Authorization").toAscii()); - } + if (hasMetaData(QLatin1String("Authorization"))) { + m_curlheaders = curl_slist_append(m_curlheaders, QByteArray("Authorization: ") + metaData("Authorization").toAscii()); + } - curlresult = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_curlheaders); - if (curlresult != CURLE_OK) { - curl_slist_free_all(m_curlheaders); - m_curlheaders = nullptr; - error(KIO::ERR_SLAVE_DEFINED, curl_easy_strerror(curlresult)); - return false; + curlresult = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_curlheaders); + if (curlresult != CURLE_OK) { + curl_slist_free_all(m_curlheaders); + m_curlheaders = nullptr; + KIO_CURL_ERROR(curlresult); + return false; + } } return true; } -bool HttpProtocol::authUrl(const KUrl &url) +bool CurlProtocol::authUrl(const KUrl &url) { KIO::AuthInfo kioauthinfo; kioauthinfo.url = url; diff --git a/kioslave/http/http.h b/kioslave/curl/kio_curl.h similarity index 84% rename from kioslave/http/http.h rename to kioslave/curl/kio_curl.h index 2c883148..66c34e75 100644 --- a/kioslave/http/http.h +++ b/kioslave/curl/kio_curl.h @@ -24,15 +24,15 @@ #include -class HttpProtocol : public KIO::SlaveBase +class CurlProtocol : public KIO::SlaveBase { public: - HttpProtocol(const QByteArray &app); - ~HttpProtocol(); + CurlProtocol(const QByteArray &app); + ~CurlProtocol(); void stat(const KUrl &url) final; + void listDir(const KUrl &url) final; void get(const KUrl &url) final; - void put(const KUrl &url, int permissions, KIO::JobFlags flags) final; void slotData(const char* curldata, const size_t curldatasize); void slotProgress(KIO::filesize_t received, KIO::filesize_t total); @@ -45,6 +45,12 @@ private: bool authUrl(const KUrl &url); bool m_emitmime; + bool m_ishttp; + bool m_isftp; + bool m_issftp; + bool m_collectdata; + QByteArray m_writedata; + KUrl m_url; CURL* m_curl; struct curl_slist *m_curlheaders; }; diff --git a/kioslave/curl/sftp.protocol b/kioslave/curl/sftp.protocol new file mode 100644 index 00000000..547d8026 --- /dev/null +++ b/kioslave/curl/sftp.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_curl +protocol=sftp +copyToFile=false +copyFromFile=false +listing=true +reading=true +writing=false +makedir=false +deleting=false +moving=false +ProxiedBy=http +Icon=folder-remote +maxInstances=20 +maxInstancesPerHost=5 +X-DocPath=kioslave/curl/index.html +Class=:internet diff --git a/kioslave/ftp/CMakeLists.txt b/kioslave/ftp/CMakeLists.txt deleted file mode 100644 index c6d41ac2..00000000 --- a/kioslave/ftp/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -project(kioslave-ftp) - -include_directories(${KDE4_KIO_INCLUDES}) - -########### next target ############### - -add_executable(kio_ftp ftp.cpp) - -target_link_libraries(kio_ftp kdecore kio) - -install(TARGETS kio_ftp DESTINATION ${KDE4_LIBEXEC_INSTALL_DIR}) - -########### install files ############### - -install(FILES ftp.protocol DESTINATION ${KDE4_SERVICES_INSTALL_DIR}) diff --git a/kioslave/ftp/ftp.cpp b/kioslave/ftp/ftp.cpp deleted file mode 100644 index 3d6c7dad..00000000 --- a/kioslave/ftp/ftp.cpp +++ /dev/null @@ -1,2539 +0,0 @@ -// -*- 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 - - 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) -*/ - - -#include "ftp.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 - -static QString ftpCleanPath(const QString& path) -{ - if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) || - path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) || - path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) { - return path.left((path.length() - qstrlen(";type=X"))); - } - - return path; -} - -static char ftpModeFromPath(const QString& path, char defaultMode = '\0') -{ - const int index = path.lastIndexOf(QLatin1String(";type=")); - - if (index > -1 && (index+6) < path.size()) { - const QChar mode = path.at(index+6); - // kio_ftp supports only A (ASCII) and I(BINARY) modes. - if (mode == QLatin1Char('A') || mode == QLatin1Char('a') || - mode == QLatin1Char('I') || mode == QLatin1Char('i')) { - return mode.toUpper().toLatin1(); - } - } - - return defaultMode; -} - - -// 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() - */ - minimumMimeSize = 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; - -int main( int argc, char **argv ) -{ - QApplication app(argc, argv); - KComponentData componentData( "kio_ftp", "kdelibs4" ); - ( void ) KGlobal::locale(); - - kDebug(7102) << "Starting " << getpid(); - - if (argc != 2) - { - fprintf(stderr, "Usage: kio_ftp app-socket\n"); - exit(-1); - } - - Ftp slave(argv[1]); - slave.dispatchLoop(); - - kDebug(7102) << "Done"; - return 0; -} - -//=============================================================================== -// Ftp -//=============================================================================== - -Ftp::Ftp( const QByteArray &app ) - : SlaveBase( "ftp", app ) -{ - // init the socket data - m_data = m_control = NULL; - m_server = NULL; - ftpCloseControlConnection(); - - // init other members - m_port = 0; -} - - -Ftp::~Ftp() -{ - kDebug(7102); - ftpCloseConnection(); -} - -/** - * This closes a data connection opened by ftpOpenDataConnection(). - */ -void Ftp::ftpCloseDataConnection() -{ - delete m_data; - m_data = NULL; - delete m_server; - m_server = 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) -{ - Q_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 (!pTxt) return 0; // avoid using a NULL when calling atoi. - - // If the server sends a multiline response starting with - // "nnn-text" we loop here until a final "nnn text" line is - // reached. Only data from the final line will be stored. - do { - while (!m_control->canReadLine() && m_control->waitForReadyRead((readTimeout() * 1000))) {} - m_lastControlLine = m_control->readLine(); - pTxt = m_lastControlLine.data(); - int iCode = atoi(pTxt); - if (iMore == 0) { - // first line - kDebug(7102) << " > " << pTxt; - if(iCode >= 100) { - m_iRespCode = iCode; - if (pTxt[3] == '-') { - // marker for a multiple line response - iMore = iCode; - } - } else { - kWarning(7102) << "Cannot parse valid code from line" << pTxt; - } - } else { - // multi-line - kDebug(7102) << " > " << pTxt; - if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') { - iMore = 0; - } - } - } 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::ftpCloseConnection() -{ - if(m_control != NULL || m_data != NULL) - kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy; - - if(m_bBusy) // ftpCloseCommand not called - { - kWarning(7102) << "Abandoned data stream"; - ftpCloseDataConnection(); - } - - if(m_bLoggedOn) // send quit - { - if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) ) - kWarning(7102) << "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) << _host << "port=" << _port << "user=" << _user; - - if ( m_host != _host || m_port != _port || - m_user != _user || m_pass != _pass ) - ftpCloseConnection(); - - m_host = _host; - m_port = _port; - m_user = _user; - m_pass = _pass; -} - -bool Ftp::ftpOpenConnection (LoginMode loginMode) -{ - // check for implicit login if we are already logged on ... - if(loginMode == loginImplicit && m_bLoggedOn) - { - Q_ASSERT(m_control != NULL); // must have control connection socket - return true; - } - - kDebug(7102) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]"; - - infoMessage( i18n("Opening connection to host %1", m_host) ); - - if ( m_host.isEmpty() ) - { - error( ERR_UNKNOWN_HOST, QString() ); - return false; - } - - Q_ASSERT( !m_bLoggedOn ); - - m_initialPath.clear(); - m_currentPath.clear(); - - if (!ftpOpenControlConnection() ) - return false; // error emitted by ftpOpenControlConnection - infoMessage( i18n("Connected to host %1", m_host) ); - - bool userNameChanged = false; - if(loginMode != loginDefered) - { - m_bLoggedOn = ftpLogin(&userNameChanged); - if( !m_bLoggedOn ) - return false; // error emitted by ftpLogin - } - - m_bTextMode = config()->readEntry("textmode", false); - - // Redirected due to credential change... - if (userNameChanged && m_bLoggedOn) - { - KUrl realURL; - realURL.setScheme( "ftp" ); - if (m_user != FTP_LOGIN) - realURL.setUserName( m_user ); - if (m_pass != FTP_PASSWD) - realURL.setPassword( m_pass ); - realURL.setHost( m_host ); - if ( m_port > 0 && m_port != DEFAULT_FTP_PORT ) - realURL.setPort( m_port ); - if ( m_initialPath.isEmpty() ) - m_initialPath = '/'; - realURL.setPath( m_initialPath ); - kDebug(7102) << "User name changed! Redirecting to" << realURL.prettyUrl(); - redirection( realURL ); - finished(); - return false; - } - - return true; -} - - -/** - * Called by @ref ftpOpenConnection. It opens the control connection to the ftp server. - * - * @return true on success. - */ -bool Ftp::ftpOpenControlConnection() -{ - return ftpOpenControlConnection(m_host, m_port); -} - -bool Ftp::ftpOpenControlConnection( const QString &host, int port ) -{ - // implicitly close, then try to open a new connection ... - ftpCloseConnection(); - QString sErrorMsg; - - // now connect to the server and read the login message ... - if (port == 0) - port = 21; // default FTP port - m_control = new QTcpSocket(); - m_control->connectToHost(host, port); - m_control->waitForConnected(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()); - } - - // if there was a problem - report it ... - if(iErrorCode == 0) // OK, return success - return true; - ftpCloseConnection(); // clean-up on error - error(iErrorCode, sErrorMsg); - return false; -} - -/** - * Called by @ref ftpOpenConnection. It logs us in. - * @ref m_initialPath is set to the current working directory - * if logging on was successful. - * - * @return true on success. - */ -bool Ftp::ftpLogin(bool* userChanged) -{ - infoMessage( i18n("Sending login information") ); - - Q_ASSERT( !m_bLoggedOn ); - - QString user (m_user); - QString pass (m_pass); - - AuthInfo info; - info.url.setScheme( "ftp" ); - info.url.setHost( m_host ); - if ( m_port > 0 && m_port != DEFAULT_FTP_PORT ) - info.url.setPort( m_port ); - if (!user.isEmpty()) - info.url.setUserName(user); - - // Check for cached authentication first and fallback to - // anonymous login when no stored credentials are found. - if (!config()->readEntry("TryAnonymousLoginFirst", false) && - pass.isEmpty() && checkCachedAuthentication(info)) - { - user = info.username; - pass = info.password; - } - - // Try anonymous login if both username/password - // information is blank. - if (user.isEmpty() && pass.isEmpty()) - { - user = FTP_LOGIN; - pass = FTP_PASSWD; - } - - QByteArray tempbuf; - QString lastServerResponse; - int failedAuth = 0; - bool promptForRetry = false; - - // Give the user the option to login anonymously... - info.setExtraField(QLatin1String("anonymous"), false); - - do - { - // Check the cache and/or prompt user for password if 1st - // login attempt failed OR the user supplied a login name, - // but no password. - if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) ) - { - QString errorMsg; - kDebug(7102) << "Prompting user for login info..."; - - // Ask user if we should retry after when login fails! - if( failedAuth > 0 && promptForRetry) - { - errorMsg = i18n("Message sent:\nLogin using username=%1 and " - "password=[hidden]\n\nServer replied:\n%2\n\n" - , user, lastServerResponse); - } - - if ( user != FTP_LOGIN ) - info.username = user; - - info.prompt = i18n("You need to supply a username and a password " - "to access this site."); - info.commentLabel = i18n( "Site:" ); - info.comment = i18n("%1", m_host ); - info.keepPassword = true; // Prompt the user for persistence as well. - - bool disablePassDlg = config()->readEntry( "DisablePassDlg", false ); - if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) ) - { - error( ERR_USER_CANCELED, m_host ); - return false; - } - else - { - // User can decide go anonymous using checkbox - if( info.getExtraField( "anonymous" ).toBool() ) - { - user = FTP_LOGIN; - pass = FTP_PASSWD; - } - else - { - user = info.username; - pass = info.password; - } - promptForRetry = true; - } - } - - tempbuf = "USER "; - tempbuf += user.toLatin1(); - - kDebug(7102) << "Sending Login name: " << tempbuf; - - bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); - bool needPass = (m_iRespCode == 331); - // Prompt user for login info if we do not - // get back a "230" or "331". - if ( !loggedIn && !needPass ) - { - lastServerResponse = ftpResponse(0); - kDebug(7102) << "Login failed: " << lastServerResponse; - ++failedAuth; - continue; // Well we failed, prompt the user please!! - } - - if( needPass ) - { - tempbuf = "PASS "; - tempbuf += pass.toLatin1(); - kDebug(7102) << "Sending Login password: " << "[protected]"; - loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); - } - - if ( loggedIn ) - { - // Make sure the user name changed flag is properly set. - if (userChanged) - *userChanged = (!m_user.isEmpty() && (m_user != user)); - - // Do not cache the default login!! - if( user != FTP_LOGIN && pass != FTP_PASSWD ) - { - // Update the username in case it was changed during login. - if (!m_user.isEmpty()) { - info.url.setUserName (user); - m_user = user; - } - - // Cache the password if the user requested it. - if (info.keepPassword) { - cacheAuthentication(info); - } - } - failedAuth = -1; - } - else - { - // some servers don't let you login anymore - // if you fail login once, so restart the connection here - lastServerResponse = ftpResponse(0); - if (!ftpOpenControlConnection()) - { - return false; - } - } - } while( ++failedAuth ); - - - kDebug(7102) << "Login OK"; - infoMessage( i18n("Login OK") ); - - // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix: - // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint - if( ftpSendCmd("SYST") && (m_iRespType == 2) ) - { - if( !qstrncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version - { - ftpSendCmd( "site dirstyle" ); - // Check if it was already in Unix style - // Patch from Keith Refson - if( !qstrncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 )) - //It was in Unix style already! - ftpSendCmd( "site dirstyle" ); - // windows won't support chmod before KDE konquers their desktop... - m_extControl |= chmodUnknown; - } - } - else - kWarning(7102) << "SYST failed"; - - // Get the current working directory - kDebug(7102) << "Searching for pwd"; - if( !ftpSendCmd("PWD") || (m_iRespType != 2) ) - { - kDebug(7102) << "Couldn't issue pwd command"; - error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) ); // or anything better ? - return false; - } - - QString sTmp = remoteEncoding()->decode( ftpResponse(3) ); - int iBeg = sTmp.indexOf('"'); - int iEnd = sTmp.lastIndexOf('"'); - if(iBeg > 0 && iBeg < iEnd) - { - m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1); - if(m_initialPath[0] != '/') m_initialPath.prepend('/'); - kDebug(7102) << "Initial path set to: " << m_initialPath; - m_currentPath = m_initialPath; - } - return true; -} - -/** - * ftpSendCmd - send a command (@p cmd) and read response - * - * @param maxretries number of time it should retry. Since it recursively - * calls itself if it can't read the answer (this happens especially after - * timeouts), we need to limit the recursiveness ;-) - * - * return true if any response received, false on error - */ -bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries ) -{ - Q_ASSERT(m_control != NULL); // must have control connection socket - - if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1) - { - kWarning(7102) << "Invalid command received (contains CR or LF):" - << cmd.data(); - error( ERR_UNSUPPORTED_ACTION, m_host ); - return false; - } - - // Don't print out the password... - bool isPassCmd = (cmd.left(4).toLower() == "pass"); - if ( !isPassCmd ) - kDebug(7102) << "send> " << cmd.data(); - else - kDebug(7102) << "send> pass [protected]"; - - // Send the message... - QByteArray buf = cmd; - buf += "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html - int num = m_control->write(buf); - while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {} - - // If we were able to successfully send the command, then we will - // attempt to read the response. Otherwise, take action to re-attempt - // the login based on the maximum number of retries specified... - if( num > 0 ) - ftpResponse(-1); - else - { - m_iRespType = m_iRespCode = 0; - } - - // If respCh is NULL or the response is 421 (Timed-out), we try to re-send - // the command based on the value of maxretries. - if( (m_iRespType <= 0) || (m_iRespCode == 421) ) - { - // We have not yet logged on... - if (!m_bLoggedOn) - { - // The command was sent from the ftpLogin function, i.e. we are actually - // attempting to login in. NOTE: If we already sent the username, we - // return false and let the user decide whether (s)he wants to start from - // the beginning... - if (maxretries > 0 && !isPassCmd) - { - ftpCloseConnection (); - if( ftpOpenConnection(loginDefered) ) - ftpSendCmd ( cmd, maxretries - 1 ); - } - - return false; - } - else - { - if ( maxretries < 1 ) - return false; - else - { - kDebug(7102) << "Was not able to communicate with " << m_host - << "Attempting to re-establish connection."; - - ftpCloseConnection(); // Close the old connection... - ftpOpenConnection(loginExplicit); // Attempt to re-establish a new connection... - - if (!m_bLoggedOn) - { - if (m_control != NULL) // if ftpOpenConnection succeeded ... - { - kDebug(7102) << "Login failure, aborting"; - error (ERR_COULD_NOT_LOGIN, m_host); - ftpCloseConnection (); - } - return false; - } - - kDebug(7102) << "Logged back in, re-issuing command"; - - // If we were able to login, resend the command... - if (maxretries) - maxretries--; - - return ftpSendCmd( cmd, maxretries ); - } - } - } - - return true; -} - -/* - * ftpOpenPASVDataConnection - set up data connection, using PASV mode - * - * return 0 if successful, ERR_INTERNAL otherwise - * doesn't set error message, since non-pasv mode will always be tried if - * this one fails - */ -int Ftp::ftpOpenPASVDataConnection() -{ - Q_ASSERT(m_control != NULL); // must have control connection socket - Q_ASSERT(m_data == NULL); // ... but no data connection - - // Check that we can do PASV - QHostAddress address = m_control->peerAddress(); - if (address.protocol() != QAbstractSocket::IPv4Protocol) - return ERR_INTERNAL; // no PASV for non-PF_INET connections - - if (m_extControl & pasvUnknown) - return ERR_INTERNAL; // already tried and got "unknown command" - - m_bPasv = true; - - /* Let's PASsiVe*/ - if( !ftpSendCmd("PASV") || (m_iRespType != 2) ) - { - kDebug(7102) << "PASV attempt failed"; - // unknown command? - if( m_iRespType == 5 ) - { - kDebug(7102) << "disabling use of PASV"; - m_extControl |= pasvUnknown; - } - return ERR_INTERNAL; - } - - // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)' - // but anonftpd gives '227 =160,39,200,55,6,245' - int i[6]; - const char *start = strchr(ftpResponse(3), '('); - if ( !start ) - start = strchr(ftpResponse(3), '='); - if ( !start || - ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 && - sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) ) - { - kError(7102) << "parsing IP and port numbers failed. String parsed: " << start; - return ERR_INTERNAL; - } - - // we ignore the host part on purpose for two reasons - // a) it might be wrong anyway - // b) it would make us being suceptible to a port scanning attack - - // now connect the data socket ... - quint16 port = i[4] << 8 | i[5]; - const QString host = (address.toString()); - m_data = new QTcpSocket(); - m_data->connectToHost(host, port); - m_data->waitForConnected(connectTimeout() * 1000); - - return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL; -} - -/* - * ftpOpenEPSVDataConnection - opens a data connection via EPSV - */ -int Ftp::ftpOpenEPSVDataConnection() -{ - Q_ASSERT(m_control != NULL); // must have control connection socket - Q_ASSERT(m_data == NULL); // ... but no data connection - - QHostAddress address = m_control->peerAddress(); - int portnum; - - if (m_extControl & epsvUnknown) - return ERR_INTERNAL; - - m_bPasv = true; - if( !ftpSendCmd("EPSV") || (m_iRespType != 2) ) - { - // unknown command? - if( m_iRespType == 5 ) - { - kDebug(7102) << "disabling use of EPSV"; - m_extControl |= epsvUnknown; - } - return ERR_INTERNAL; - } - - const char *start = strchr(ftpResponse(3), '|'); - if ( !start || sscanf(start, "|||%d|", &portnum) != 1) - return ERR_INTERNAL; - - const QString host = (address.toString()); - m_data = new QTcpSocket(); - m_data->connectToHost(host, portnum); - m_data->waitForConnected(connectTimeout() * 1000); - return m_data->isOpen() ? 0 : ERR_INTERNAL; -} - -/* - * ftpOpenDataConnection - set up data connection - * - * The routine calls several ftpOpenXxxxConnection() helpers to find - * the best connection mode. If a helper cannot connect if returns - * ERR_INTERNAL - so this is not really an error! All other error - * codes are treated as fatal, e.g. they are passed back to the caller - * who is responsible for calling error(). ftpOpenPortDataConnection - * can be called as last try and it does never return ERR_INTERNAL. - * - * @return 0 if successful, err code otherwise - */ -int Ftp::ftpOpenDataConnection() -{ - // make sure that we are logged on and have no data connection... - Q_ASSERT( m_bLoggedOn ); - ftpCloseDataConnection(); - - int iErrCode = 0; - int iErrCodePASV = 0; // Remember error code from PASV - - // First try passive (EPSV & PASV) modes - if( !config()->readEntry("DisablePassiveMode", false) ) - { - iErrCode = ftpOpenPASVDataConnection(); - if(iErrCode == 0) - return 0; // success - iErrCodePASV = iErrCode; - ftpCloseDataConnection(); - - if( !config()->readEntry("DisableEPSV", false) ) - { - iErrCode = ftpOpenEPSVDataConnection(); - if(iErrCode == 0) - return 0; // success - ftpCloseDataConnection(); - } - - // if we sent EPSV ALL already and it was accepted, then we can't - // use active connections any more - if (m_extControl & epsvAllSent) - return iErrCodePASV; - } - - // fall back to port mode - iErrCode = ftpOpenPortDataConnection(); - if(iErrCode == 0) - return 0; // success - - ftpCloseDataConnection(); - // prefer to return the error code from PASV if any, since that's what should have worked in the first place - return iErrCodePASV ? iErrCodePASV : iErrCode; -} - -/* - * ftpOpenPortDataConnection - set up data connection - * - * @return 0 if successful, err code otherwise (but never ERR_INTERNAL - * because this is the last connection mode that is tried) - */ -int Ftp::ftpOpenPortDataConnection() -{ - Q_ASSERT(m_control != NULL); // must have control connection socket - Q_ASSERT(m_data == NULL); // ... but no data connection - - m_bPasv = false; - if (m_extControl & eprtUnknown) - return ERR_INTERNAL; - - if (!m_server) { - m_server = new QTcpServer(); - m_server->listen(QHostAddress::Any, 0); - } - - if (!m_server->isListening()) { - delete m_server; - m_server = NULL; - return ERR_COULD_NOT_LISTEN; - } - - m_server->setMaxPendingConnections(1); - - QString command; - QHostAddress localAddress = m_control->localAddress(); - if (localAddress.protocol() == QAbstractSocket::IPv4Protocol) - { - struct - { - quint32 ip4; - quint16 port; - } data; - data.ip4 = localAddress.toIPv4Address(); - data.port = m_server->serverPort(); - - unsigned char *pData = reinterpret_cast(&data); - command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]); - } - else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol) - { - command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort()); - } - - if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) ) - { - return 0; - } - - delete m_server; - m_server = NULL; - return ERR_INTERNAL; -} - -bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode, - int errorcode, KIO::fileoffset_t _offset ) -{ - int errCode = 0; - if( !ftpDataMode(ftpModeFromPath(_path, _mode)) ) - errCode = ERR_COULD_NOT_CONNECT; - else - errCode = ftpOpenDataConnection(); - - if(errCode != 0) - { - error(errCode, m_host); - return false; - } - - if ( _offset > 0 ) { - // send rest command if offset > 0, this applies to retr and stor commands - char buf[100]; - sprintf(buf, "rest %lld", _offset); - if ( !ftpSendCmd( buf ) ) - return false; - if( m_iRespType != 3 ) - { - error( ERR_CANNOT_RESUME, _path ); // should never happen - return false; - } - } - - QByteArray tmp = _command; - QString errormessage; - - if ( !_path.isEmpty() ) { - tmp += ' '; - tmp += remoteEncoding()->encode(ftpCleanPath(_path)); - } - - if( !ftpSendCmd( tmp ) || (m_iRespType != 1) ) - { - if( _offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4) ) - errorcode = ERR_CANNOT_RESUME; - // The error here depends on the command - errormessage = _path; - } - - else - { - // Only now we know for sure that we can resume - if ( _offset > 0 && qstrcmp(_command, "retr") == 0 ) - canResume(); - - if(m_server && !m_data) { - kDebug(7102) << "waiting for connection from remote."; - m_server->waitForNewConnection(connectTimeout() * 1000); - m_data = m_server->nextPendingConnection(); - } - - if(m_data) { - kDebug(7102) << "connected with remote."; - m_bBusy = true; // cleared in ftpCloseCommand - return true; - } - - kDebug(7102) << "no connection received from remote."; - errorcode=ERR_COULD_NOT_ACCEPT; - errormessage=m_host; - return false; - } - - error(errorcode, errormessage); - return false; -} - - -bool Ftp::ftpCloseCommand() -{ - // first close data sockets (if opened), then read response that - // we got for whatever was used in ftpOpenCommand ( should be 226 ) - ftpCloseDataConnection(); - - if(!m_bBusy) - return true; - - kDebug(7102) << "ftpCloseCommand: reading command result"; - m_bBusy = false; - - if(!ftpResponse(-1) || (m_iRespType != 2) ) - { - kDebug(7102) << "ftpCloseCommand: no transfer complete message"; - return false; - } - return true; -} - -void Ftp::mkdir( const KUrl & url, int permissions ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - const QByteArray encodedPath (remoteEncoding()->encode(url)); - const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size()); - - if( !ftpSendCmd( (QByteArray ("mkd ") + encodedPath) ) || (m_iRespType != 2) ) - { - QString currentPath( m_currentPath ); - - // Check whether or not mkdir failed because - // the directory already exists... - if( ftpFolder( path, false ) ) - { - error( ERR_DIR_ALREADY_EXIST, path ); - // Change the directory back to what it was... - (void) ftpFolder( currentPath, false ); - return; - } - - error( ERR_COULD_NOT_MKDIR, path ); - return; - } - - if ( permissions != -1 ) - { - // chmod the dir we just created, ignoring errors. - (void) ftpChmod( path, permissions ); - } - - finished(); -} - -void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - // The actual functionality is in ftpRename because put needs it - if ( ftpRename( src.path(), dst.path(), flags ) ) - finished(); -} - -bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags) -{ - Q_ASSERT(m_bLoggedOn); - - // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). - if (!(jobFlags & KIO::Overwrite)) { - if (ftpFileExists(dst)) { - error(ERR_FILE_ALREADY_EXIST, dst); - return false; - } - } - - if (ftpFolder(dst, false)) { - error(ERR_DIR_ALREADY_EXIST, dst); - return false; - } - - // CD into parent folder - const int pos = src.lastIndexOf('/'); - if (pos >= 0) { - if(!ftpFolder(src.left(pos+1), false)) - return false; - } - - QByteArray from_cmd = "RNFR "; - from_cmd += remoteEncoding()->encode(src.mid(pos+1)); - if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) { - error( ERR_CANNOT_RENAME, src ); - return false; - } - - QByteArray to_cmd = "RNTO "; - to_cmd += remoteEncoding()->encode(dst); - if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) { - error( ERR_CANNOT_RENAME, src ); - return false; - } - - return true; -} - -void Ftp::del( const KUrl& url, bool isfile ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - // When deleting a directory, we must exit from it first - // The last command probably went into it (to stat it) - if ( !isfile ) - ftpFolder(remoteEncoding()->directory(url), false); // ignore errors - - QByteArray cmd = isfile ? "DELE " : "RMD "; - cmd += remoteEncoding()->encode(url); - - if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - error( ERR_CANNOT_DELETE, url.path() ); - else - finished(); -} - -bool Ftp::ftpChmod( const QString & path, int permissions ) -{ - Q_ASSERT( m_bLoggedOn ); - - if(m_extControl & chmodUnknown) // previous errors? - return false; - - // we need to do bit AND 777 to get permissions, in case - // we were sent a full mode (unlikely) - QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 /*octal*/ ) + ' '; - cmd += path; - - ftpSendCmd(remoteEncoding()->encode(cmd)); - if(m_iRespType == 2) - return true; - - if(m_iRespCode == 500) - { - m_extControl |= chmodUnknown; - kDebug(7102) << "ftpChmod: CHMOD not supported - disabling"; - } - return false; -} - -void Ftp::chmod( const KUrl & url, int permissions ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - if ( !ftpChmod( url.path(), permissions ) ) - error( ERR_CANNOT_CHMOD, url.path() ); - else - finished(); -} - -void Ftp::ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, UDSEntry& entry, bool isDir ) -{ - Q_ASSERT(entry.count() == 0); // by contract :-) - - entry.insert( KIO::UDSEntry::UDS_NAME, filename ); - entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size ); - entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date ); - entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access ); - entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner ); - if ( !ftpEnt.group.isEmpty() ) - { - entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group ); - } - - if ( !ftpEnt.link.isEmpty() ) - { - entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link ); - - KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) ); - // Links on ftp sites are often links to dirs, and we have no way to check - // that. Let's do like Netscape : assume dirs generally. - // But we do this only when the mimetype can't be known from the filename. - // --> we do better than Netscape :-) - if ( mime->name() == KMimeType::defaultMimeType() ) - { - kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename; - entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) ); - isDir = true; - } - } - - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type ); - // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime); - // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime); -} - - -void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir ) -{ - UDSEntry entry; - - - entry.insert( KIO::UDSEntry::UDS_NAME, filename ); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG ); - entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ); - if (isDir) { - entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); - } - // No details about size, ownership, group, etc. - - statEntry(entry); - finished(); -} - -void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename ) -{ - // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source") - // When e.g. uploading a file, we still need stat() to return "not found" - // when the file doesn't exist. - QString statSide = metaData("statSide"); - kDebug(7102) << "statSide=" << statSide; - if ( statSide == "source" ) - { - kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing"; - // MS Server is incapable of handling "list " in a case insensitive way - // But "retr " works. So lie in stat(), to get going... - // - // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run - // where listing permissions are denied, but downloading is still possible. - ftpShortStatAnswer( filename, false /*file, not dir*/ ); - - return; - } - - error( ERR_DOES_NOT_EXIST, path ); -} - -void Ftp::stat(const KUrl &url) -{ - kDebug(7102) << "path=" << url.path(); - if( !ftpOpenConnection(loginImplicit) ) - return; - - const QString path = ftpCleanPath( QDir::cleanPath( url.path() ) ); - kDebug(7102) << "cleaned path=" << path; - - // We can't stat root, but we know it's a dir. - if( path.isEmpty() || path == "/" ) - { - UDSEntry entry; - //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) ); - entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) ); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); - entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); - entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ); - entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) ); - entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) ); - // no size - - statEntry( entry ); - finished(); - return; - } - - KUrl tempurl( url ); - tempurl.setPath( path ); // take the clean one - QString listarg; // = tempurl.directory(KUrl::LeaveTrailingSlash); - QString parentDir; - QString filename = tempurl.fileName(); - Q_ASSERT(!filename.isEmpty()); - QString search = filename; - - // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info) - // if it doesn't work, it's a file (and then we'll use dir filename) - bool isDir = ftpFolder(path, false); - - // if we're only interested in "file or directory", we should stop here - QString sDetails = metaData("details"); - int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); - kDebug(7102) << "details=" << details; - if ( details == 0 ) - { - if ( !isDir && !ftpFileExists(path) ) // ok, not a dir -> is it a file ? - { // no -> it doesn't exist at all - ftpStatAnswerNotFound( path, filename ); - return; - } - ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done - return; - } - - if (!isDir) - { - // It is a file or it doesn't exist, try going to parent directory - parentDir = tempurl.directory(KUrl::AddTrailingSlash); - // With files we can do "LIST " to avoid listing the whole dir - listarg = filename; - } - else - { - // --- New implementation: - // Don't list the parent dir. Too slow, might not show it, etc. - // Just return that it's a dir. - UDSEntry entry; - entry.insert( KIO::UDSEntry::UDS_NAME, filename ); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); - entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ); - // No clue about size, ownership, group, etc. - - statEntry(entry); - finished(); - return; - } - - // Now cwd the parent dir, to prepare for listing - if( !ftpFolder(parentDir, true) ) - return; - - if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) ) - { - kError(7102) << "COULD NOT LIST"; - return; - } - kDebug(7102) << "Starting of list was ok"; - - Q_ASSERT( !search.isEmpty() && search != "/" ); - - bool bFound = false; - KUrl linkURL; - FtpEntry ftpEnt; - QList ftpValidateEntList; - while (ftpReadDir(ftpEnt)) { - if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) { - ftpValidateEntList.append(ftpEnt); - continue; - } - - // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at) - // return only the filename when doing "dir /full/path/to/file" - if (!bFound) { - bFound = maybeEmitStatEntry(ftpEnt, search, filename, isDir); - } - // kDebug(7102) << ftpEnt.name; - } - - for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { - FtpEntry& ftpEnt = ftpValidateEntList[i]; - fixupEntryName(&ftpEnt); - if (maybeEmitStatEntry(ftpEnt, search, filename, isDir)) { - break; - } - } - - ftpCloseCommand(); // closes the data connection only - - if ( !bFound ) - { - ftpStatAnswerNotFound( path, filename ); - return; - } - - if ( !linkURL.isEmpty() ) - { - if ( linkURL == url || linkURL == tempurl ) - { - error( ERR_CYCLIC_LINK, linkURL.prettyUrl() ); - return; - } - Ftp::stat( linkURL ); - return; - } - - kDebug(7102) << "stat : finished successfully"; - finished(); -} - -bool Ftp::maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir) -{ - if ((search == ftpEnt.name || filename == ftpEnt.name) && !filename.isEmpty()) { - UDSEntry entry; - ftpCreateUDSEntry( filename, ftpEnt, entry, isDir ); - statEntry( entry ); - return true; - } - - return false; -} - -void Ftp::listDir( const KUrl &url ) -{ - kDebug(7102) << url; - if( !ftpOpenConnection(loginImplicit) ) - return; - - // No path specified ? - QString path = url.path(); - if ( path.isEmpty() ) - { - KUrl realURL; - realURL.setScheme( "ftp" ); - realURL.setUserName( m_user ); - realURL.setPassword( m_pass ); - realURL.setHost( m_host ); - if ( m_port > 0 && m_port != DEFAULT_FTP_PORT ) - realURL.setPort( m_port ); - if ( m_initialPath.isEmpty() ) - m_initialPath = '/'; - realURL.setPath( m_initialPath ); - kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl(); - redirection( realURL ); - finished(); - return; - } - - kDebug(7102) << "hunting for path" << path; - - if (!ftpOpenDir(path)) { - if (ftpFileExists(path)) { - error(ERR_IS_FILE, path); - } else { - // not sure which to emit - //error( ERR_DOES_NOT_EXIST, path ); - error( ERR_CANNOT_ENTER_DIRECTORY, path ); - } - return; - } - - UDSEntry entry; - FtpEntry ftpEnt; - QList ftpValidateEntList; - while( ftpReadDir(ftpEnt) ) - { - //kDebug(7102) << ftpEnt.name; - //Q_ASSERT( !ftpEnt.name.isEmpty() ); - if (!ftpEnt.name.isEmpty()) { - if (ftpEnt.name.at(0).isSpace()) { - ftpValidateEntList.append(ftpEnt); - continue; - } - - //if ( S_ISDIR( (mode_t)ftpEnt.type ) ) - // kDebug(7102) << "is a dir"; - //if ( !ftpEnt.link.isEmpty() ) - // kDebug(7102) << "is a link to " << ftpEnt.link; - ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false ); - listEntry( entry, false ); - entry.clear(); - } - } - - for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) { - FtpEntry& ftpEnt = ftpValidateEntList[i]; - fixupEntryName(&ftpEnt); - ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false ); - listEntry( entry, false ); - entry.clear(); - } - - listEntry( entry, true ); // ready - ftpCloseCommand(); // closes the data connection only - finished(); -} - -bool Ftp::ftpOpenDir( const QString & path ) -{ - //QString path( _url.path(KUrl::RemoveTrailingSlash) ); - - // We try to change to this directory first to see whether it really is a directory. - // (And also to follow symlinks) - QString tmp = path.isEmpty() ? QString("/") : path; - - // We get '550', whether it's a file or doesn't exist... - if( !ftpFolder(tmp, false) ) - return false; - - // Don't use the path in the list command: - // We changed into this directory anyway - so it's enough just to send "list". - // We use '-a' because the application MAY be interested in dot files. - // The only way to really know would be to have a metadata flag for this... - // Since some windows ftp server seems not to support the -a argument, we use a fallback here. - // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com) - if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) - { - if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) - { - kWarning(7102) << "Can't open for listing"; - return false; - } - } - kDebug(7102) << "Starting of list was ok"; - return true; -} - -bool Ftp::ftpReadDir(FtpEntry& de) -{ - Q_ASSERT(m_data != NULL); - - // get a line from the data connecetion ... - while( true ) - { - while (!m_data->canReadLine() && m_data->waitForReadyRead((readTimeout() * 1000))) {} - QByteArray data = m_data->readLine(); - if (data.size() == 0) - break; - - const char* buffer = data.data(); - kDebug(7102) << "dir > " << buffer; - - //Normally the listing looks like - // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log - // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442) - // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI - - // we should always get the following 5 fields ... - const char *p_access, *p_junk, *p_owner, *p_group, *p_size; - if( (p_access = strtok((char*)buffer," ")) == 0) continue; - if( (p_junk = strtok(NULL," ")) == 0) continue; - if( (p_owner = strtok(NULL," ")) == 0) continue; - if( (p_group = strtok(NULL," ")) == 0) continue; - if( (p_size = strtok(NULL," ")) == 0) continue; - - //kDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size; - - de.access = 0; - if ( qstrlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware - de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions - } - - const char *p_date_1, *p_date_2, *p_date_3, *p_name; - - // A special hack for "/dev". A listing may look like this: - // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero - // So we just ignore the number in front of the ",". Ok, it is a hack :-) - if ( strchr( p_size, ',' ) != 0L ) - { - //kDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)"; - if ((p_size = strtok(NULL," ")) == 0) - continue; - } - - // Check whether the size we just read was really the size - // or a month (this happens when the server lists no group) - // Used to be the case on sunsite.uio.no, but not anymore - // This is needed for the Netware case, too. - if ( !isdigit( *p_size ) ) - { - p_date_1 = p_size; - p_size = p_group; - p_group = 0; - //kDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1; - } - else - { - p_date_1 = strtok(NULL," "); - //kDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1; - } - - if ( p_date_1 != 0 && - (p_date_2 = strtok(NULL," ")) != 0 && - (p_date_3 = strtok(NULL," ")) != 0 && - (p_name = strtok(NULL,"\r\n")) != 0 ) - { - { - QByteArray tmp( p_name ); - if ( p_access[0] == 'l' ) - { - int i = tmp.lastIndexOf( " -> " ); - if ( i != -1 ) { - de.link = remoteEncoding()->decode(p_name + i + 4); - tmp.truncate( i ); - } - else - de.link.clear(); - } - else - de.link.clear(); - - if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/' - tmp.remove( 0, 1 ); - - if (tmp.indexOf('/') != -1) - continue; // Don't trick us! - - de.name = remoteEncoding()->decode(tmp); - } - - de.type = S_IFREG; - switch ( p_access[0] ) { - case 'd': - de.type = S_IFDIR; - break; - case 's': - de.type = S_IFSOCK; - break; - case 'b': - de.type = S_IFBLK; - break; - case 'c': - de.type = S_IFCHR; - break; - case 'l': - de.type = S_IFREG; - // we don't set S_IFLNK here. de.link says it. - break; - default: - break; - } - - if ( p_access[1] == 'r' ) - de.access |= S_IRUSR; - if ( p_access[2] == 'w' ) - de.access |= S_IWUSR; - if ( p_access[3] == 'x' || p_access[3] == 's' ) - de.access |= S_IXUSR; - if ( p_access[4] == 'r' ) - de.access |= S_IRGRP; - if ( p_access[5] == 'w' ) - de.access |= S_IWGRP; - if ( p_access[6] == 'x' || p_access[6] == 's' ) - de.access |= S_IXGRP; - if ( p_access[7] == 'r' ) - de.access |= S_IROTH; - if ( p_access[8] == 'w' ) - de.access |= S_IWOTH; - if ( p_access[9] == 'x' || p_access[9] == 't' ) - de.access |= S_IXOTH; - if ( p_access[3] == 's' || p_access[3] == 'S' ) - de.access |= S_ISUID; - if ( p_access[6] == 's' || p_access[6] == 'S' ) - de.access |= S_ISGID; - if ( p_access[9] == 't' || p_access[9] == 'T' ) - de.access |= S_ISVTX; - - de.owner = remoteEncoding()->decode(p_owner); - de.group = remoteEncoding()->decode(p_group); - de.size = charToLongLong(p_size); - - // Parsing the date is somewhat tricky - // Examples : "Oct 6 22:49", "May 13 1999" - - // First get current time - we need the current month and year - time_t currentTime = time( 0L ); -#ifdef _POSIX_THREAD_SAFE_FUNCTIONS - struct tm tmpres; - struct tm * tmptr = gmtime_r(¤tTime, &tmpres); -#else - struct tm * tmptr = gmtime( ¤tTime ); -#endif - int currentMonth = tmptr->tm_mon; - //kDebug(7102) << "Current time :" << asctime( tmptr ); - // Reset time fields - tmptr->tm_isdst = -1; // We do not anything about day saving time - tmptr->tm_sec = 0; - tmptr->tm_min = 0; - tmptr->tm_hour = 0; - // Get day number (always second field) - if (p_date_2) - tmptr->tm_mday = atoi( p_date_2 ); - // Get month from first field - // NOTE : no, we don't want to use KLocale here - // It seems all FTP servers use the English way - //kDebug(7102) << "Looking for month " << p_date_1; - static const char * const s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - for ( int c = 0 ; c < 12 ; c ++ ) - if ( !qstrcmp( p_date_1, s_months[c]) ) - { - //kDebug(7102) << "Found month " << c << " for " << p_date_1; - tmptr->tm_mon = c; - break; - } - - // Parse third field - if ( qstrlen( p_date_3 ) == 4 ) // 4 digits, looks like a year - tmptr->tm_year = atoi( p_date_3 ) - 1900; - else - { - // otherwise, the year is implicit - // according to man ls, this happens when it is between than 6 months - // old and 1 hour in the future. - // So the year is : current year if tm_mon <= currentMonth+1 - // otherwise current year minus one - // (The +1 is a security for the "+1 hour" at the end of the month issue) - if ( tmptr->tm_mon > currentMonth + 1 ) - tmptr->tm_year--; - - // and p_date_3 contains probably a time - char * semicolon; - if ( p_date_3 && ( semicolon = (char*)strchr( p_date_3, ':' ) ) ) - { - *semicolon = '\0'; - tmptr->tm_min = atoi( semicolon + 1 ); - tmptr->tm_hour = atoi( p_date_3 ); - } - else - kWarning(7102) << "Can't parse third field " << p_date_3; - } - - //kDebug(7102) << asctime( tmptr ); - de.date = mktime( tmptr ); - return true; - } - } // line invalid, loop to get another line - return false; -} - -//=============================================================================== -// public: get download file from server -// helper: ftpGet called from get() and copy() -//=============================================================================== -void Ftp::get( const KUrl & url ) -{ - kDebug(7102) << url; - - int iError = 0; - const StatusCode cs = ftpGet(iError, -1, url, 0); - ftpCloseCommand(); // must close command! - - if (cs == statusSuccess) { - finished(); - return; - } - - if (iError) { // can have only server side errs - error(iError, url.path()); - } -} - -Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset) -{ - // Calls error() by itself! - if( !ftpOpenConnection(loginImplicit) ) - return statusServerError; - - // Try to find the size of the file (and check that it exists at - // the same time). If we get back a 550, "File does not exist" - // or "not a plain file", check if it is a directory. If it is a - // directory, return an error; otherwise simply try to retrieve - // the request... - if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) && - ftpFolder(url.path(), false) ) - { - // Ok it's a dir in fact - kDebug(7102) << "it is a directory in fact"; - iError = ERR_IS_DIRECTORY; - return statusServerError; - } - - QString resumeOffset = metaData("resume"); - if ( !resumeOffset.isEmpty() ) - { - llOffset = resumeOffset.toLongLong(); - kDebug(7102) << "got offset from metadata : " << llOffset; - } - - if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) ) - { - kWarning(7102) << "Can't open for reading"; - return statusServerError; - } - - // Read the size from the response string - if(m_size == UnknownSize) - { - const char* psz = strrchr( ftpResponse(4), '(' ); - if(psz) m_size = charToLongLong(psz+1); - if (!m_size) m_size = UnknownSize; - } - - // Send the mime-type... - if (iCopyFile == -1) { - StatusCode status = ftpSendMimeType(iError, url); - if (status != statusSuccess) { - return status; - } - } - - KIO::filesize_t bytesLeft = 0; - if ( m_size != UnknownSize ) { - bytesLeft = m_size - llOffset; - totalSize( m_size ); // emit the total size... - } - - kDebug(7102) << "starting with offset=" << llOffset; - KIO::fileoffset_t processed_size = llOffset; - - QByteArray array; - char buffer[maximumIpcSize]; - // start with small data chunks in case of a slow data source (modem) - // - unfortunately this has a negative impact on performance for large - // - files - so we will increase the block size after a while ... - int iBlockSize = initialIpcSize; - int iBufferCur = 0; - - while(m_size == UnknownSize || bytesLeft > 0) - { // let the buffer size grow if the file is larger 64kByte ... - if(processed_size-llOffset > 1024 * 64) - iBlockSize = maximumIpcSize; - - // read the data and detect EOF or error ... - if(iBlockSize+iBufferCur > (int)sizeof(buffer)) - iBlockSize = sizeof(buffer) - iBufferCur; - if (m_data->bytesAvailable() == 0) - m_data->waitForReadyRead((readTimeout() * 1000)); - int n = m_data->read( buffer+iBufferCur, iBlockSize ); - if(n <= 0) - { // this is how we detect EOF in case of unknown size - if( m_size == UnknownSize && n == 0 ) - break; - // unexpected eof. Happens when the daemon gets killed. - iError = ERR_COULD_NOT_READ; - return statusServerError; - } - processed_size += n; - - // collect very small data chunks in buffer before processing ... - if(m_size != UnknownSize) - { - bytesLeft -= n; - iBufferCur += n; - if(iBufferCur < minimumMimeSize && bytesLeft > 0) - { - processedSize( processed_size ); - continue; - } - n = iBufferCur; - iBufferCur = 0; - } - - // write output file or pass to data pump ... - if(iCopyFile == -1) - { - array = QByteArray::fromRawData(buffer, n); - data( array ); - array.clear(); - } - else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0) - return statusClientError; // client side error - processedSize( processed_size ); - } - - kDebug(7102) << "done"; - if(iCopyFile == -1) // must signal EOF to data pump ... - data(array); // array is empty and must be empty! - - processedSize( m_size == UnknownSize ? processed_size : m_size ); - return statusSuccess; -} - -#if 0 - void Ftp::mimetype( const KUrl& url ) - { - if( !ftpOpenConnection(loginImplicit) ) - return; - - if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) { - kWarning(7102) << "Can't open for reading"; - return; - } - char buffer[ 2048 ]; - QByteArray array; - // Get one chunk of data only and send it, KIO::Job will determine the - // mimetype from it using KMimeMagic - int n = m_data->read( buffer, 2048 ); - array.setRawData(buffer, n); - data( array ); - array.resetRawData(buffer, n); - - kDebug(7102) << "aborting"; - ftpAbortTransfer(); - - kDebug(7102) << "finished"; - finished(); - kDebug(7102) << "after finished"; - } - - void Ftp::ftpAbortTransfer() - { - // RFC 959, page 34-35 - // IAC (interpret as command) = 255 ; IP (interrupt process) = 254 - // DM = 242 (data mark) - char msg[4]; - // 1. User system inserts the Telnet "Interrupt Process" (IP) signal - // in the Telnet stream. - msg[0] = (char) 255; //IAC - msg[1] = (char) 254; //IP - (void) send(sControl, msg, 2, 0); - // 2. User system sends the Telnet "Sync" signal. - msg[0] = (char) 255; //IAC - msg[1] = (char) 242; //DM - if (send(sControl, msg, 2, MSG_OOB) != 2) - ; // error... - - // Send ABOR - kDebug(7102) << "send ABOR"; - QCString buf = "ABOR\r\n"; - if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) { - error( ERR_COULD_NOT_WRITE, QString() ); - return; - } - - // - kDebug(7102) << "read resp"; - if ( readresp() != '2' ) - { - error( ERR_COULD_NOT_READ, QString() ); - return; - } - - kDebug(7102) << "close sockets"; - closeSockets(); - } -#endif - -//=============================================================================== -// public: put upload file to server -// helper: ftpPut called from put() and copy() -//=============================================================================== -void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags) -{ - kDebug(7102) << url; - - int iError = 0; // iError gets status - const StatusCode cs = ftpPut(iError, -1, url, permissions, flags); - ftpCloseCommand(); // must close command! - - if (cs == statusSuccess) { - finished(); - return; - } - - if (iError) { // can have only server side errs - error(iError, url.path()); - } -} - -Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url, - int permissions, KIO::JobFlags flags) -{ - if( !ftpOpenConnection(loginImplicit) ) - return statusServerError; - - // Don't use mark partial over anonymous FTP. - // My incoming dir allows put but not rename... - bool bMarkPartial; - if (m_user.isEmpty () || m_user == FTP_LOGIN) - bMarkPartial = false; - else - bMarkPartial = config()->readEntry("MarkPartial", true); - - QString dest_orig = dest_url.path(); - QString dest_part( dest_orig ); - dest_part += ".part"; - - if ( ftpSize( dest_orig, 'I' ) ) - { - if ( m_size == 0 ) - { // delete files with zero size - QByteArray cmd = "DELE "; - cmd += remoteEncoding()->encode(dest_orig); - if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - { - iError = ERR_CANNOT_DELETE_PARTIAL; - return statusServerError; - } - } - else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) ) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusServerError; - } - else if ( bMarkPartial ) - { // when using mark partial, append .part extension - if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) ) - { - iError = ERR_CANNOT_RENAME_PARTIAL; - return statusServerError; - } - } - // Don't chmod an existing file - permissions = -1; - } - else if ( bMarkPartial && ftpSize( dest_part, 'I' ) ) - { // file with extension .part exists - if ( m_size == 0 ) - { // delete files with zero size - QByteArray cmd = "DELE "; - cmd += remoteEncoding()->encode(dest_part); - if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - { - iError = ERR_CANNOT_DELETE_PARTIAL; - return statusServerError; - } - } - else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) ) - { - flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags; - if (!(flags & KIO::Resume)) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusServerError; - } - } - } - else - m_size = 0; - - QString dest; - - // if we are using marking of partial downloads -> add .part extension - if ( bMarkPartial ) { - kDebug(7102) << "Adding .part extension to " << dest_orig; - dest = dest_part; - } else - dest = dest_orig; - - KIO::fileoffset_t offset = 0; - - // set the mode according to offset - if( (flags & KIO::Resume) && m_size > 0 ) - { - offset = m_size; - if(iCopyFile != -1) - { - if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 ) - { - iError = ERR_CANNOT_RESUME; - return statusClientError; - } - } - } - - if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) ) - return statusServerError; - - kDebug(7102) << "ftpPut: starting with offset=" << offset; - KIO::fileoffset_t processed_size = offset; - - QByteArray buffer; - int result; - int iBlockSize = initialIpcSize; - // Loop until we got 'dataEnd' - do - { - if(iCopyFile == -1) - { - dataReq(); // Request for data - result = readData( buffer ); - } - else - { // let the buffer size grow if the file is larger 64kByte ... - if(processed_size-offset > 1024 * 64) - iBlockSize = maximumIpcSize; - buffer.resize(iBlockSize); - result = ::read(iCopyFile, buffer.data(), buffer.size()); - if(result < 0) - iError = ERR_COULD_NOT_WRITE; - else - buffer.resize(result); - } - - if (result > 0) - { - m_data->write( buffer ); - while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {} - processed_size += result; - processedSize (processed_size); - } - } - while ( result > 0 ); - - if (result != 0) // error - { - ftpCloseCommand(); // don't care about errors - kDebug(7102) << "Error during 'put'. Aborting."; - if (bMarkPartial) - { - // Remove if smaller than minimum size - if ( ftpSize( dest, 'I' ) && - ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) ) - { - QByteArray cmd = "DELE "; - cmd += remoteEncoding()->encode(dest); - (void) ftpSendCmd( cmd ); - } - } - return statusServerError; - } - - if ( !ftpCloseCommand() ) - { - iError = ERR_COULD_NOT_WRITE; - return statusServerError; - } - - // after full download rename the file back to original name - if ( bMarkPartial ) - { - kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")"; - if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) ) - { - iError = ERR_CANNOT_RENAME_PARTIAL; - return statusServerError; - } - } - - // set final permissions - if ( permissions != -1 ) - { - if ( m_user == FTP_LOGIN ) - kDebug(7102) << "Trying to chmod over anonymous FTP ???"; - // chmod the file we just put - if ( ! ftpChmod( dest_orig, permissions ) ) - { - // To be tested - //if ( m_user != FTP_LOGIN ) - // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); - } - } - - return statusSuccess; -} - -const char* Ftp::ftpSendSizeCmd(const QString& path) -{ - // Some servers do not allow absolute path for SIZE; so we use - // relative paths whenever possible. #326292 - QString currentPath(m_currentPath); - if (!currentPath.endsWith(QLatin1Char('/'))) { - currentPath += QLatin1Char('/'); - } - - QByteArray buf; - buf = "SIZE "; - if (path.startsWith(currentPath)) { - buf += remoteEncoding()->encode(path.mid(currentPath.length())); - } else { - buf += remoteEncoding()->encode(path); - } - - if (!ftpSendCmd(buf) || m_iRespType != 2) { - return 0; - } - - // skip leading "213 " (response code) - return ftpResponse(4); -} - - -/** Use the SIZE command to get the file size. - Warning : the size depends on the transfer mode, hence the second arg. */ -bool Ftp::ftpSize(const QString & path, char mode) -{ - m_size = UnknownSize; - if (!ftpDataMode(mode)) { - return false; - } - - const QByteArray psz(ftpSendSizeCmd(path)); - if (psz.isEmpty()) { - return false; - } - - bool ok = false; - m_size = psz.trimmed().toLongLong(&ok); - if (!ok) { - m_size = UnknownSize; - } - - return true; -} - -bool Ftp::ftpFileExists(const QString& path) -{ - return ftpSendSizeCmd(path) != 0; -} - -// Today the differences between ASCII and BINARY are limited to -// CR or CR/LF line terminators. Many servers ignore ASCII (like -// win2003 -or- vsftp with default config). In the early days of -// computing, when even text-files had structure, this stuff was -// more important. -// Theoretically "list" could return different results in ASCII -// and BINARY mode. But again, most servers ignore ASCII here. -bool Ftp::ftpDataMode(char cMode) -{ - if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I'; - else if(cMode == 'a') cMode = 'A'; - else if(cMode != 'A') cMode = 'I'; - - kDebug(7102) << "want" << cMode << "has" << m_cDataMode; - if(m_cDataMode == cMode) - return true; - - QByteArray buf = "TYPE "; - buf += cMode; - if( !ftpSendCmd(buf) || (m_iRespType != 2) ) - return false; - m_cDataMode = cMode; - return true; -} - - -bool Ftp::ftpFolder(const QString& path, bool bReportError) -{ - QString newPath = path; - int iLen = newPath.length(); - if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1); - - //kDebug(7102) << "want" << newPath << "has" << m_currentPath; - if(m_currentPath == newPath) - return true; - - QByteArray tmp = "cwd "; - tmp += remoteEncoding()->encode(newPath); - if( !ftpSendCmd(tmp) ) - return false; // connection failure - if(m_iRespType != 2) - { - if(bReportError) - error(ERR_CANNOT_ENTER_DIRECTORY, path); - return false; // not a folder - } - m_currentPath = newPath; - return true; -} - - -//=============================================================================== -// public: copy don't use kio data pump if one side is a local file -// helper: ftpCopyPut called from copy() on upload -// helper: ftpCopyGet called from copy() on download -//=============================================================================== -void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags ) -{ - int iError = 0; - int iCopyFile = -1; - StatusCode cs = statusSuccess; - bool bSrcLocal = src.isLocalFile(); - bool bDestLocal = dest.isLocalFile(); - QString sCopyFile; - - if(bSrcLocal && !bDestLocal) { // File -> Ftp - sCopyFile = src.toLocalFile(); - kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path(); - cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags); - if(cs == statusServerError) { - sCopyFile = dest.url(); - } - } else if(!bSrcLocal && bDestLocal) { // Ftp -> File - sCopyFile = dest.toLocalFile(); - kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile; - cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags); - if(cs == statusServerError) { - sCopyFile = src.url(); - } - } else { - error(ERR_UNSUPPORTED_ACTION, QString()); - return; - } - - // perform clean-ups and report error (if any) - if(iCopyFile != -1) { - ::close(iCopyFile); - } - - ftpCloseCommand(); // must close command! - - if (cs == statusSuccess) { - finished(); - return; - } - - if(iError) { - error(iError, sCopyFile); - } -} - - -Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile, - const KUrl& url, int permissions, KIO::JobFlags flags) -{ - // check if source is ok ... - KDE_struct_stat buff; - bool bSrcExists = (KDE::stat( sCopyFile, &buff ) != -1); - if(bSrcExists) - { if(S_ISDIR(buff.st_mode)) - { - iError = ERR_IS_DIRECTORY; - return statusClientError; - } - } - else - { - iError = ERR_DOES_NOT_EXIST; - return statusClientError; - } - - iCopyFile = KDE::open( sCopyFile, O_RDONLY ); - if(iCopyFile == -1) - { - iError = ERR_CANNOT_OPEN_FOR_READING; - return statusClientError; - } - - // delegate the real work (iError gets status) ... - totalSize(buff.st_size); -#ifdef ENABLE_CAN_RESUME - return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume); -#else - return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume); -#endif -} - - -Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile, - const KUrl& url, int permissions, KIO::JobFlags flags) -{ - // check if destination is ok ... - KDE_struct_stat buff; - const bool bDestExists = (KDE::stat( sCopyFile, &buff ) != -1); - if(bDestExists) - { if(S_ISDIR(buff.st_mode)) - { - iError = ERR_IS_DIRECTORY; - return statusClientError; - } - if(!(flags & KIO::Overwrite)) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusClientError; - } - } - - // do we have a ".part" file? - const QString sPart = sCopyFile + QLatin1String(".part"); - bool bResume = false; - const bool bPartExists = (KDE::stat( sPart, &buff ) != -1); - const bool bMarkPartial = config()->readEntry("MarkPartial", true); - const QString dest = bMarkPartial ? sPart : sCopyFile; - if (bMarkPartial && bPartExists && buff.st_size > 0) - { // must not be a folder! please fix a similar bug in kio_file!! - if(S_ISDIR(buff.st_mode)) - { - iError = ERR_DIR_ALREADY_EXIST; - return statusClientError; // client side error - } - //doesn't work for copy? -> design flaw? -#ifdef ENABLE_CAN_RESUME - bResume = canResume( buff.st_size ); -#else - bResume = true; -#endif - } - - if (bPartExists && !bResume) // get rid of an unwanted ".part" file - QFile::remove(sPart); - - // WABA: Make sure that we keep writing permissions ourselves, - // otherwise we can be in for a surprise on NFS. - mode_t initialMode; - if (permissions != -1) - initialMode = permissions | S_IWUSR; - else - initialMode = 0666; - - // open the output file ... - KIO::fileoffset_t hCopyOffset = 0; - if (bResume) { - iCopyFile = KDE::open( sPart, O_RDWR ); // append if resuming - hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END); - if(hCopyOffset < 0) - { - iError = ERR_CANNOT_RESUME; - return statusClientError; // client side error - } - kDebug(7102) << "resuming at " << hCopyOffset; - } - else { - iCopyFile = KDE::open(dest, O_CREAT | O_TRUNC | O_WRONLY, initialMode); - } - - if(iCopyFile == -1) - { - kDebug(7102) << "### COULD NOT WRITE " << sCopyFile; - iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED - : ERR_CANNOT_OPEN_FOR_WRITING; - return statusClientError; - } - - // delegate the real work (iError gets status) ... - StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset); - if( ::close(iCopyFile) && iRes == statusSuccess ) - { - iError = ERR_COULD_NOT_WRITE; - iRes = statusClientError; - } - iCopyFile = -1; - - // handle renaming or deletion of a partial file ... - if(bMarkPartial) - { - if(iRes == statusSuccess) - { // rename ".part" on success - if ( KDE::rename( sPart, sCopyFile ) ) - { - // If rename fails, try removing the destination first if it exists. - if (!bDestExists || !(QFile::remove(sCopyFile) && KDE::rename(sPart, sCopyFile) == 0)) { - kDebug(7102) << "cannot rename " << sPart << " to " << sCopyFile; - iError = ERR_CANNOT_RENAME_PARTIAL; - iRes = statusClientError; - } - } - } - else if(KDE::stat( sPart, &buff ) == 0) - { // should a very small ".part" be deleted? - int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); - if (buff.st_size < size) - QFile::remove(sPart); - } - } - - if (iRes == statusSuccess) { - const QString mtimeStr = metaData("modified"); - if (!mtimeStr.isEmpty()) { - QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); - if (dt.isValid()) { - kDebug(7102) << "Updating modified timestamp to" << mtimeStr; - struct utimbuf utbuf; - utbuf.actime = buff.st_atime; // access time, unchanged - utbuf.modtime = dt.toTime_t(); // modification time - KDE::utime(sCopyFile, &utbuf); - } - } - } - - return iRes; -} - -Ftp::StatusCode Ftp::ftpSendMimeType(int& iError, const KUrl& url) -{ - // Emit proper mimetype for zero sized files. #323491 - if (m_size == 0) { - mimeType(QLatin1String("application/x-zerosize")); - return statusSuccess; - } - - const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : m_size); - QByteArray buffer(totalSize, '\0'); - - while (true) { - // Wait for content to be available... - if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((readTimeout() * 1000))) { - iError = ERR_COULD_NOT_READ; - return statusServerError; - } - - const int bytesRead = m_data->peek(buffer.data(), totalSize); - - // If we got a -1, it must be an error so return an error. - if (bytesRead == -1) { - iError = ERR_COULD_NOT_READ; - return statusServerError; - } - - // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size - // equal to the size we want, then break. - if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) { - break; - } - } - - if (!buffer.isEmpty()) { - KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), buffer); - kDebug(7102) << "Emitting mimetype" << mime->name(); - mimeType( mime->name() ); // emit the mime type... - } - - return statusSuccess; -} - -void Ftp::fixupEntryName(FtpEntry* e) -{ - Q_ASSERT(e); - if (e->type == S_IFDIR) { - if (!ftpFolder(e->name, false)) { - QString name (e->name.trimmed()); - if (ftpFolder(name, false)) { - e->name = name; - kDebug(7102) << "fixing up directory name from" << e->name << "to" << name; - } else { - int index = 0; - while (e->name.at(index).isSpace()) { - index++; - name = e->name.mid(index); - if (ftpFolder(name, false)) { - kDebug(7102) << "fixing up directory name from" << e->name << "to" << name; - e->name = name; - break; - } - } - } - } - } else { - if (!ftpFileExists(e->name)) { - QString name (e->name.trimmed()); - if (ftpFileExists(name)) { - e->name = name; - kDebug(7102) << "fixing up filename from" << e->name << "to" << name; - } else { - int index = 0; - while (e->name.at(index).isSpace()) { - index++; - name = e->name.mid(index); - if (ftpFileExists(name)) { - kDebug(7102) << "fixing up filename from" << e->name << "to" << name; - e->name = name; - break; - } - } - } - } - } -} diff --git a/kioslave/ftp/ftp.h b/kioslave/ftp/ftp.h deleted file mode 100644 index bb7dd3c5..00000000 --- a/kioslave/ftp/ftp.h +++ /dev/null @@ -1,426 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- -/* This file is part of the KDE libraries - Copyright (C) 2000 David Faure - - 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. -*/ - -#ifndef KDELIBS_FTP_H -#define KDELIBS_FTP_H - -#include - -#include -#include - -#include -#include - -#include -#include - -struct FtpEntry -{ - QString name; - QString owner; - QString group; - QString link; - - KIO::filesize_t size; - mode_t type; - mode_t access; - time_t date; -}; - -//=============================================================================== -// Ftp -//=============================================================================== -class Ftp : public QObject, public KIO::SlaveBase -{ - Q_OBJECT - -public: - Ftp( const QByteArray &app ); - virtual ~Ftp(); - - virtual void setHost( const QString& host, quint16 port, const QString& user, const QString& pass ); - - virtual void stat( const KUrl &url ); - - virtual void listDir( const KUrl & url ); - virtual void mkdir( const KUrl & url, int permissions ); - virtual void rename( const KUrl & src, const KUrl & dst, KIO::JobFlags flags ); - virtual void del( const KUrl & url, bool isfile ); - virtual void chmod( const KUrl & url, int permissions ); - - virtual void get( const KUrl& url ); - virtual void put( const KUrl& url, int permissions, KIO::JobFlags flags ); - //virtual void mimetype( const KUrl& url ); - - /** - * Handles the case that one side of the job is a local file - */ - virtual void copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags ); - -private: - // ------------------------------------------------------------------------ - // All the methods named ftpXyz are lowlevel methods that are not exported. - // The implement functionality used by the public high-level methods. Some - // low-level methods still use error() to emit errors. This behaviour is not - // recommended - please return a boolean status or an error code instead! - // ------------------------------------------------------------------------ - - /** - * Status Code returned from ftpPut() and ftpGet(), used to select - * source or destination url for error messages - */ - typedef enum { - statusSuccess, - statusClientError, - statusServerError - } StatusCode; - - /** - * Login Mode for ftpOpenConnection - */ - typedef enum { - loginDefered, - loginExplicit, - loginImplicit - } LoginMode; - - /** - * Connect and login to the FTP server. - * - * @param loginMode controls if login info should be sent
- * loginDefered - must not be logged on, no login info is sent
- * loginExplicit - must not be logged on, login info is sent
- * loginImplicit - login info is sent if not logged on - * - * @return true on success (a login failure would return false). - */ - bool ftpOpenConnection (LoginMode loginMode); - - /** - * Closes the connection - */ - void ftpCloseConnection(); - - /** - * Called by ftpOpenConnection. It logs us in. - * m_initialPath is set to the current working directory - * if logging on was successful. - * - * @param userChanged if not NULL, will be set to true if the user name - * was changed during login. - * @return true on success. - */ - bool ftpLogin(bool* userChanged = 0); - - /** - * ftpSendCmd - send a command (@p cmd) and read response - * - * @param maxretries number of time it should retry. Since it recursively - * calls itself if it can't read the answer (this happens especially after - * timeouts), we need to limit the recursiveness ;-) - * - * return true if any response received, false on error - */ - bool ftpSendCmd( const QByteArray& cmd, int maxretries = 1 ); - - /** - * Use the SIZE command to get the file size. - * @param mode the size depends on the transfer mode, hence this arg. - * @return true on success - * Gets the size into m_size. - */ - bool ftpSize( const QString & path, char mode ); - - /** - * Sends the SIZE command to the server and returns the response. - * @param path the path to file whose size is to be retrieved. - * @return response from the server - */ - const char* ftpSendSizeCmd(const QString& path); - - /** - * Returns true if the file exists. - * Implemented using the SIZE command. - */ - bool ftpFileExists(const QString& path); - - /** - * Set the current working directory, but only if not yet current - */ - bool ftpFolder(const QString& path, bool bReportError); - - /** - * Runs a command on the ftp server like "list" or "retr". In contrast to - * ftpSendCmd a data connection is opened. The corresponding socket - * sData is available for reading/writing on success. - * The connection must be closed afterwards with ftpCloseCommand. - * - * @param mode is 'A' or 'I'. 'A' means ASCII transfer, 'I' means binary transfer. - * @param errorcode the command-dependent error code to emit on error - * - * @return true if the command was accepted by the server. - */ - bool ftpOpenCommand( const char *command, const QString & path, char mode, - int errorcode, KIO::fileoffset_t offset = 0 ); - - /** - * The counterpart to openCommand. - * Closes data sockets and then reads line sent by server at - * end of command. - * @return false on error (line doesn't start with '2') - */ - bool ftpCloseCommand(); - - /** - * Send "TYPE I" or "TYPE A" only if required, see m_cDataMode. - * - * Use 'A' to select ASCII and 'I' to select BINARY mode. If - * cMode is '?' the m_bTextMode flag is used to choose a mode. - */ - bool ftpDataMode(char cMode); - - //void ftpAbortTransfer(); - - /** - * Used by ftpOpenCommand, return 0 on success or an error code - */ - int ftpOpenDataConnection(); - - /** - * closes a data connection, see ftpOpenDataConnection() - */ - void ftpCloseDataConnection(); - - /** - * Helper for ftpOpenDataConnection - */ - int ftpOpenPASVDataConnection(); - /** - * Helper for ftpOpenDataConnection - */ - int ftpOpenEPSVDataConnection(); - /** - * Helper for ftpOpenDataConnection - */ - int ftpOpenPortDataConnection(); - - bool ftpChmod( const QString & path, int permissions ); - - // used by listDir - bool ftpOpenDir( const QString & path ); - /** - * Called to parse directory listings, call this until it returns false - */ - bool ftpReadDir(FtpEntry& ftpEnt); - - /** - * Helper to fill an UDSEntry - */ - void ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, KIO::UDSEntry& entry, bool isDir ); - - void ftpShortStatAnswer( const QString& filename, bool isDir ); - - void ftpStatAnswerNotFound( const QString & path, const QString & filename ); - - /** - * This is the internal implementation of rename() - set put(). - * - * @return true on success. - */ - bool ftpRename( const QString & src, const QString & dst, KIO::JobFlags flags ); - - /** - * Called by ftpOpenConnection. It opens the control connection to the ftp server. - * - * @return true on success. - */ - bool ftpOpenControlConnection(); - bool ftpOpenControlConnection( const QString & host, int port ); - - /** - * closes the socket holding the control connection (see ftpOpenControlConnection) - */ - void ftpCloseControlConnection(); - - /** - * read a response from the server (a trailing CR gets stripped) - * @param iOffset -1 to read a new line from the server
- * 0 to return the whole response string - * >0 to return the response with iOffset chars skipped - * @return the reponse message with iOffset chars skipped (or "" if iOffset points - * behind the available data) - */ - const char* ftpResponse(int iOffset); - - /** - * This is the internal implementation of get() - see copy(). - * - * IMPORTANT: the caller should call ftpCloseCommand() on return. - * The function does not call error(), the caller should do this. - * - * @param iError set to an ERR_xxxx code on error - * @param iCopyFile -1 -or- handle of a local destination file - * @param hCopyOffset local file only: non-zero for resume - * @return 0 for success, -1 for server error, -2 for client error - */ - StatusCode ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t hCopyOffset); - - /** - * This is the internal implementation of put() - see copy(). - * - * IMPORTANT: the caller should call ftpCloseCommand() on return. - * The function does not call error(), the caller should do this. - * - * @param iError set to an ERR_xxxx code on error - * @param iCopyFile -1 -or- handle of a local source file - * @return 0 for success, -1 for server error, -2 for client error - */ - StatusCode ftpPut(int& iError, int iCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags); - - /** - * helper called from copy() to implement FILE -> FTP transfers - * - * @param iError set to an ERR_xxxx code on error - * @param iCopyFile [out] handle of a local source file - * @param sCopyFile path of the local source file - * @return 0 for success, -1 for server error, -2 for client error - */ - StatusCode ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags); - - /** - * helper called from copy() to implement FTP -> FILE transfers - * - * @param iError set to an ERR_xxxx code on error - * @param iCopyFile [out] handle of a local source file - * @param sCopyFile path of the local destination file - * @return 0 for success, -1 for server error, -2 for client error - */ - StatusCode ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags); - - /** - * Sends the mime type of the content to retrieved. - * - * @param iError set to an ERR_xxxx code on error - * @return 0 for success, -1 for server error, -2 for client error - */ - StatusCode ftpSendMimeType(int& iError, const KUrl& url); - - /** - * Fixes up an entry name so that extraneous whitespaces do not cause - * problems. See bug# 88575 and bug# 300988. - */ - void fixupEntryName(FtpEntry* ftpEnt); - - /** - * Calls @ref statEntry. - */ - bool maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir); - -private: // data members - - QString m_host; - int m_port; - QString m_user; - QString m_pass; - /** - * Where we end up after connecting - */ - QString m_initialPath; - - /** - * the current working directory - see ftpFolder - */ - QString m_currentPath; - - /** - * the status returned by the FTP protocol, set in ftpResponse() - */ - int m_iRespCode; - - /** - * the status/100 returned by the FTP protocol, set in ftpResponse() - */ - int m_iRespType; - - /** - * This flag is maintained by ftpDataMode() and contains I or A after - * ftpDataMode() has successfully set the mode. - */ - char m_cDataMode; - - /** - * true if logged on (m_control should also be non-NULL) - */ - bool m_bLoggedOn; - - /** - * true if a "textmode" metadata key was found by ftpLogin(). This - * switches the ftp data transfer mode from binary to ASCII. - */ - bool m_bTextMode; - - /** - * true if a data stream is open, used in ftpCloseConnection(). - * - * When the user cancels a get or put command the Ftp dtor will be called, - * which in turn calls ftpCloseConnection(). The later would try to send QUIT - * which won't work until timeout. ftpOpenCommand sets the m_bBusy flag so - * that the sockets will be closed immedeately - the server should be - * capable of handling this and return an error code on thru the control - * connection. The m_bBusy gets cleared by the ftpCloseCommand() routine. - */ - bool m_bBusy; - - bool m_bPasv; - - KIO::filesize_t m_size; - static KIO::filesize_t UnknownSize; - - enum - { - epsvUnknown = 0x01, - epsvAllUnknown = 0x02, - eprtUnknown = 0x04, - epsvAllSent = 0x10, - pasvUnknown = 0x20, - chmodUnknown = 0x100 - }; - int m_extControl; - - /** - * control connection socket, only set if openControl() succeeded - */ - QTcpSocket *m_control; - QByteArray m_lastControlLine; - - /** - * data connection socket - */ - QTcpSocket *m_data; - - /** - * active mode server socket - */ - QTcpServer *m_server; -}; - -#endif // KDELIBS_FTP_H - diff --git a/kioslave/ftp/ftp.protocol b/kioslave/ftp/ftp.protocol deleted file mode 100644 index 5e7665a8..00000000 --- a/kioslave/ftp/ftp.protocol +++ /dev/null @@ -1,17 +0,0 @@ -[Protocol] -exec=kio_ftp -protocol=ftp -copyToFile=true -copyFromFile=true -listing=true -reading=true -writing=true -makedir=true -deleting=true -moving=true -ProxiedBy=http -Icon=folder-remote -maxInstances=20 -maxInstancesPerHost=5 -X-DocPath=kioslave/ftp/index.html -Class=:internet