mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 02:42:48 +00:00

so that it is clear that the options shall affect only the result, also for local files the correct thing can be done regardless of the trailing slash as is done in KUrl::upUrl() Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2583 lines
75 KiB
C++
2583 lines
75 KiB
C++
// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; c-file-style: "stroustrup" -*-
|
|
/* This file is part of the KDE libraries
|
|
Copyright (C) 2000-2006 David Faure <faure@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
Recommended reading explaining FTP details and quirks:
|
|
http://cr.yp.to/ftp.html (by D.J. Bernstein)
|
|
|
|
RFC:
|
|
RFC 959 "File Transfer Protocol (FTP)"
|
|
RFC 1635 "How to Use Anonymous FTP"
|
|
RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV)
|
|
*/
|
|
|
|
|
|
#include "ftp.h"
|
|
|
|
#include <sys/time.h>
|
|
#include <ctime>
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QDateTime>
|
|
#include <QtGui/QApplication>
|
|
|
|
#include <kdebug.h>
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <kcomponentdata.h>
|
|
#include <kmimetype.h>
|
|
#include <kio/ioslave_defaults.h>
|
|
#include <kio/slaveconfig.h>
|
|
#include <kremoteencoding.h>
|
|
#include <kde_file.h>
|
|
#include <kconfiggroup.h>
|
|
|
|
#ifdef HAVE_STRTOLL
|
|
#define charToLongLong(a) strtoll(a, 0, 10)
|
|
#else
|
|
#define charToLongLong(a) strtol(a, 0, 10)
|
|
#endif
|
|
|
|
#define FTP_LOGIN "anonymous"
|
|
#define FTP_PASSWD "anonymous@"
|
|
|
|
//#undef kDebug
|
|
#define ENABLE_CAN_RESUME
|
|
|
|
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);
|
|
|
|
if ( config()->readEntry("EnableAutoLogin", false) )
|
|
{
|
|
QString au = config()->readEntry("autoLoginUser");
|
|
if ( !au.isEmpty() )
|
|
{
|
|
user = au;
|
|
pass = config()->readEntry("autoLoginPass");
|
|
}
|
|
}
|
|
|
|
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("<b>%1</b>", 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 <Keith.Refson@earth.ox.ac.uk>
|
|
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";
|
|
|
|
if ( config()->readEntry ("EnableAutoLoginMacro", false) )
|
|
ftpAutoLoginMacro ();
|
|
|
|
// 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;
|
|
}
|
|
|
|
void Ftp::ftpAutoLoginMacro ()
|
|
{
|
|
QString macro = metaData( "autoLoginMacro" );
|
|
|
|
if ( macro.isEmpty() )
|
|
return;
|
|
|
|
const QStringList list = macro.split('\n',QString::SkipEmptyParts);
|
|
|
|
for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
|
|
{
|
|
if ( (*it).startsWith(QLatin1String("init")) )
|
|
{
|
|
const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
|
|
it = list2.begin();
|
|
++it; // ignore the macro name
|
|
|
|
for( ; it != list2.end() ; ++it )
|
|
{
|
|
// TODO: Add support for arbitrary commands
|
|
// besides simply changing directory!!
|
|
if ( (*it).startsWith( QLatin1String("cwd") ) )
|
|
(void)ftpFolder( (*it).mid(4), false );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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<unsigned char*>(&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 <blah>" in a case insensitive way
|
|
// But "retr <blah>" 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 <filename>" 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<FtpEntry> 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<FtpEntry> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|