kioslave: reimplement ftp and sftp slaves via curl

the sftp KIO slave is in the kde-workspace repo and the module for
finding libssh was for it. tested ftp and sftp - both work with user and
password authentication as intended (in read-only mode, put() not
implemented)

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2024-03-16 23:08:00 +02:00
parent 5064d34b04
commit a5132853b0
15 changed files with 380 additions and 3203 deletions

View file

@ -40,7 +40,6 @@ set(cmakeFiles
FindLibLZMA.cmake
FindLIBPARTED.cmake
FindLibSpectre.cmake
FindLibSSH.cmake
FindLibTorrent.cmake
FindLibUSB.cmake
FindLightDM.cmake

View file

@ -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 <xakepa10@gmail.com>
#
# 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)

View file

@ -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

View file

@ -1,4 +1,3 @@
add_subdirectory( file )
add_subdirectory( http )
add_subdirectory( ftp )
add_subdirectory( curl )

View file

@ -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}
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <QApplication>
#include <QHostAddress>
#include <QHostInfo>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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<HttpProtocol*>(userdata);
if (!httpprotocol) {
CurlProtocol* curlprotocol = static_cast<CurlProtocol*>(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<HttpProtocol*>(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<HttpProtocol*>(userdata);
if (!httpprotocol) {
CurlProtocol* curlprotocol = static_cast<CurlProtocol*>(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;

View file

@ -24,15 +24,15 @@
#include <curl/curl.h>
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;
};

View file

@ -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

View file

@ -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})

File diff suppressed because it is too large Load diff

View file

@ -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 <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KDELIBS_FTP_H
#define KDELIBS_FTP_H
#include <config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <kurl.h>
#include <kio/slavebase.h>
#include <QTcpServer>
#include <QTcpSocket>
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<br>
* loginDefered - must not be logged on, no login info is sent<br>
* loginExplicit - must not be logged on, login info is sent<br>
* 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<br>
* 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

View file

@ -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