You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2754 lines
90 KiB
2754 lines
90 KiB
/*
|
|
* This file is part of the KFTPGrabber project
|
|
*
|
|
* Copyright (C) 2003-2006 by the KFTPGrabber developers
|
|
* Copyright (C) 2003-2006 Jernej Kos <kostko@jweb-network.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
|
|
* NON-INFRINGEMENT. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*
|
|
* In addition, as a special exception, the copyright holders give
|
|
* permission to link the code of portions of this program with the
|
|
* OpenSSL library under certain conditions as described in each
|
|
* individual source file, and distribute linked combinations
|
|
* including the two.
|
|
*
|
|
* You must obey the GNU General Public License in all respects
|
|
* for all of the code used other than OpenSSL. If you modify
|
|
* file(s) with this exception, you may extend this exception to your
|
|
* version of the file(s), but you are not obligated to do so. If you
|
|
* do not wish to do so, delete this exception statement from your
|
|
* version. If you delete this exception statement from all source
|
|
* files in the program, then also delete it here.
|
|
*/
|
|
|
|
#include "ftpsocket.h"
|
|
#include "thread.h"
|
|
#include "ftpdirectoryparser.h"
|
|
#include "cache.h"
|
|
#include "speedlimiter.h"
|
|
#include "ssl.h"
|
|
|
|
#include "misc/kftpotpgenerator.h"
|
|
#include "misc/kftpconfig.h"
|
|
|
|
#include <tqdir.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <kstandarddirs.h>
|
|
#include <tdesocketdevice.h>
|
|
|
|
#include <utime.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
|
|
namespace KFTPEngine {
|
|
|
|
FtpSocket::FtpSocket(Thread *thread)
|
|
: KNetwork::KStreamSocket(),
|
|
Socket(thread, "ftp"),
|
|
SpeedLimiterItem(),
|
|
m_login(false),
|
|
m_transferSocket(0),
|
|
m_directoryParser(0),
|
|
m_controlConnecting(false),
|
|
m_controlSsl(0),
|
|
m_dataSsl(0),
|
|
m_clientCert(0)
|
|
{
|
|
enableRead(false);
|
|
setBlocking(false);
|
|
}
|
|
|
|
FtpSocket::~FtpSocket()
|
|
{
|
|
protoDisconnect();
|
|
}
|
|
|
|
void FtpSocket::poll()
|
|
{
|
|
if (m_controlConnecting) {
|
|
if (isFatalError(error())) {
|
|
slotError();
|
|
resetError();
|
|
m_controlConnecting = false;
|
|
return;
|
|
}
|
|
|
|
if (state() == Connected) {
|
|
m_controlConnecting = false;
|
|
slotConnected();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
slotControlTryRead();
|
|
|
|
if (!m_buffer.isEmpty())
|
|
processBuffer();
|
|
|
|
if (m_transferSocket) {
|
|
if (m_transferConnecting && m_transferSocket->state() == Connected) {
|
|
m_transferConnecting = false;
|
|
slotDataConnected();
|
|
} else if (!m_transferConnecting) {
|
|
if (getCurrentCommand() == Commands::CmdPut) {
|
|
if (m_transferStart >= 2)
|
|
slotDataTryWrite();
|
|
} else {
|
|
bool input;
|
|
m_transferSocket->socketDevice()->poll(&input, 0, 0, 0);
|
|
|
|
if (input)
|
|
slotDataTryRead();
|
|
}
|
|
}
|
|
} else if (m_serverSocket) {
|
|
bool input;
|
|
m_serverSocket->socketDevice()->poll(&input, 0, 0, 0);
|
|
|
|
if (input) {
|
|
KNetwork::KActiveSocketBase *socket = m_serverSocket->accept();
|
|
|
|
if (socket) {
|
|
slotDataAccept(static_cast<KNetwork::KStreamSocket*>(socket));
|
|
m_transferConnecting = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for timeouts
|
|
// NOTE This should be moved to a TQTimer's slot when ported to TQt 4
|
|
timeoutCheck();
|
|
keepaliveCheck();
|
|
}
|
|
|
|
void FtpSocket::slotControlTryRead()
|
|
{
|
|
TQString tmpStr;
|
|
TQ_LONG size = 0;
|
|
|
|
// Read what we can
|
|
if (getConfigInt("ssl") && m_controlSsl) {
|
|
size = m_controlSsl->read(m_controlBuffer, sizeof(m_controlBuffer) - 1);
|
|
|
|
if (size == -1) {
|
|
protoDisconnect();
|
|
return;
|
|
}
|
|
} else
|
|
size = readBlock(m_controlBuffer, sizeof(m_controlBuffer) - 1);
|
|
|
|
if (error() != NoError) {
|
|
// Have we been disconnected ?
|
|
if (error() != WouldBlock)
|
|
protoDisconnect();
|
|
|
|
return;
|
|
}
|
|
|
|
if (size == 0)
|
|
return;
|
|
|
|
for (int i = 0; i < size; i++)
|
|
if (m_controlBuffer[i] == 0)
|
|
m_controlBuffer[i] = '!';
|
|
|
|
memset(m_controlBuffer + size, 0, sizeof(m_controlBuffer) - size);
|
|
m_buffer.append(m_controlBuffer);
|
|
}
|
|
|
|
void FtpSocket::processBuffer()
|
|
{
|
|
// Parse any lines we might have
|
|
int pos;
|
|
while ((pos = m_buffer.find('\n')) > -1) {
|
|
TQString line = m_buffer.mid(0, pos);
|
|
line = m_remoteEncoding->decode(TQCString(line.ascii()));
|
|
parseLine(line);
|
|
|
|
// Remove what we just parsed
|
|
m_buffer.remove(0, pos + 1);
|
|
}
|
|
}
|
|
|
|
void FtpSocket::parseLine(const TQString &line)
|
|
{
|
|
// Is this the end of multiline response ?
|
|
if (!m_multiLineCode.isEmpty() && line.left(4) == m_multiLineCode) {
|
|
m_multiLineCode = "";
|
|
emitEvent(Event::EventResponse, line);
|
|
} else if (line[3] == '-' && m_multiLineCode.isEmpty()) {
|
|
m_multiLineCode = line.left(3) + " ";
|
|
emitEvent(Event::EventMultiline, line);
|
|
} else if (!m_multiLineCode.isEmpty()) {
|
|
emitEvent(Event::EventMultiline, line);
|
|
} else {
|
|
// Normal response
|
|
emitEvent(Event::EventResponse, line);
|
|
}
|
|
|
|
timeoutWait(false);
|
|
|
|
// Parse our response
|
|
m_response = line;
|
|
nextCommand();
|
|
}
|
|
|
|
bool FtpSocket::isResponse(const TQString &code)
|
|
{
|
|
TQString ref;
|
|
|
|
if (isMultiline())
|
|
ref = m_multiLineCode;
|
|
else
|
|
ref = m_response;
|
|
|
|
return ref.left(code.length()) == code;
|
|
}
|
|
|
|
void FtpSocket::sendCommand(const TQString &command)
|
|
{
|
|
emitEvent(Event::EventCommand, command);
|
|
TQCString buffer(m_remoteEncoding->encode(command) + "\r\n");
|
|
|
|
if (getConfigInt("ssl") && m_controlSsl)
|
|
m_controlSsl->write(buffer.data(), buffer.length());
|
|
else
|
|
writeBlock(buffer.data(), buffer.length());
|
|
|
|
timeoutWait(true);
|
|
}
|
|
|
|
void FtpSocket::resetCommandClass(ResetCode code)
|
|
{
|
|
timeoutWait(false);
|
|
|
|
if (m_transferSocket && code != Ok) {
|
|
// Invalidate the socket
|
|
closeDataTransferSocket();
|
|
|
|
// Close the file that failed transfer
|
|
if (getTransferFile()->isOpen()) {
|
|
getTransferFile()->close();
|
|
|
|
if (getCurrentCommand() == Commands::CmdGet && getTransferFile()->size() == 0)
|
|
getTransferFile()->remove();
|
|
}
|
|
}
|
|
|
|
if (m_serverSocket && code != Ok)
|
|
delete m_serverSocket;
|
|
|
|
Socket::resetCommandClass(code);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ***************************************** CONNECT *****************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandConnect : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentAuthTls,
|
|
SentUser,
|
|
SentPass,
|
|
SentPbsz,
|
|
SentProt,
|
|
DoingSyst,
|
|
DoingFeat,
|
|
SentPwd
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandConnect, FtpSocket, CmdNone)
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
if (!socket()->isMultiline()) {
|
|
if (socket()->isResponse("2")) {
|
|
// Negotiate a SSL connection if configured
|
|
if (socket()->getConfigInt("ssl.use_tls")) {
|
|
currentState = SentAuthTls;
|
|
socket()->sendCommand("AUTH TLS");
|
|
} else {
|
|
// Send username
|
|
currentState = SentUser;
|
|
socket()->sendCommand("USER " + socket()->getCurrentUrl().user());
|
|
}
|
|
} else {
|
|
socket()->emitEvent(Event::EventMessage, i18n("Connection has failed."));
|
|
|
|
socket()->protoAbort();
|
|
socket()->emitError(ConnectFailed);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SentAuthTls: {
|
|
if (socket()->isResponse("2")) {
|
|
socket()->m_controlSsl = new Ssl(socket());
|
|
|
|
// Setup client certificate if one was provided
|
|
if (socket()->m_clientCert)
|
|
socket()->m_controlSsl->setClientCertificate(socket()->m_clientCert);
|
|
|
|
if (socket()->m_controlSsl->connect()) {
|
|
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation successful. Connection is secured with %1 bit cipher %2.").arg(socket()->m_controlSsl->connectionInfo().getCipherUsedBits()).arg(socket()->m_controlSsl->connectionInfo().getCipher()));
|
|
socket()->setConfig("ssl", 1);
|
|
|
|
// Now send the username
|
|
currentState = SentUser;
|
|
socket()->sendCommand("USER " + socket()->getCurrentUrl().user());
|
|
} else {
|
|
delete socket()->m_controlSsl;
|
|
socket()->m_controlSsl = 0;
|
|
|
|
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation failed. Login aborted."));
|
|
socket()->resetCommandClass(Failed);
|
|
|
|
socket()->protoAbort();
|
|
}
|
|
} else {
|
|
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation request failed. Login aborted."));
|
|
socket()->resetCommandClass(Failed);
|
|
|
|
socket()->protoAbort();
|
|
}
|
|
break;
|
|
}
|
|
case SentUser: {
|
|
if (socket()->isResponse("331")) {
|
|
// Send password
|
|
if (socket()->isResponse("331 Response to otp-") ||
|
|
socket()->isResponse("331 Response to s/key")) {
|
|
// OTP: 331 Response to otp-md5 41 or4828 ext required for foo.
|
|
TQString tmp = socket()->getResponse();
|
|
tmp = tmp.section(' ', 3, 5);
|
|
|
|
KFTPOTPGenerator otp(tmp, socket()->getCurrentUrl().pass());
|
|
currentState = SentPass;
|
|
socket()->sendCommand("PASS " + otp.generateOTP());
|
|
} else {
|
|
socket()->sendCommand("PASS " + socket()->getCurrentUrl().pass());
|
|
currentState = SentPass;
|
|
}
|
|
} else if (socket()->isResponse("230")) {
|
|
// Some servers imediately send the 230 response for anonymous accounts
|
|
if (!socket()->isMultiline()) {
|
|
if (socket()->getConfigInt("ssl")) {
|
|
currentState = SentPbsz;
|
|
socket()->sendCommand("PBSZ 0");
|
|
} else {
|
|
// Do SYST
|
|
socket()->sendCommand("SYST");
|
|
currentState = DoingSyst;
|
|
}
|
|
}
|
|
} else {
|
|
socket()->emitEvent(Event::EventMessage, i18n("Login has failed."));
|
|
|
|
socket()->protoAbort();
|
|
socket()->emitError(LoginFailed);
|
|
}
|
|
break;
|
|
}
|
|
case SentPass: {
|
|
if (socket()->isResponse("230")) {
|
|
if (!socket()->isMultiline()) {
|
|
if (socket()->getConfigInt("ssl")) {
|
|
currentState = SentPbsz;
|
|
socket()->sendCommand("PBSZ 0");
|
|
} else {
|
|
// Do SYST
|
|
socket()->sendCommand("SYST");
|
|
currentState = DoingSyst;
|
|
}
|
|
}
|
|
} else {
|
|
socket()->emitEvent(Event::EventMessage, i18n("Login has failed."));
|
|
|
|
socket()->protoAbort();
|
|
socket()->emitError(LoginFailed);
|
|
}
|
|
break;
|
|
}
|
|
case SentPbsz: {
|
|
currentState = SentProt;
|
|
TQString prot = "PROT ";
|
|
|
|
if (socket()->getConfigInt("ssl.prot_mode") == 0)
|
|
prot.append('P');
|
|
else
|
|
prot.append('C');
|
|
|
|
socket()->sendCommand(prot);
|
|
break;
|
|
}
|
|
case SentProt: {
|
|
if (socket()->isResponse("5")) {
|
|
// Fallback to unencrypted data channel
|
|
socket()->setConfig("ssl.prot_mode", 2);
|
|
}
|
|
|
|
currentState = DoingSyst;
|
|
socket()->sendCommand("SYST");
|
|
break;
|
|
}
|
|
case DoingSyst: {
|
|
socket()->sendCommand("FEAT");
|
|
currentState = DoingFeat;
|
|
break;
|
|
}
|
|
case DoingFeat: {
|
|
if (socket()->isMultiline()) {
|
|
parseFeat();
|
|
} else {
|
|
socket()->sendCommand("PWD");
|
|
currentState = SentPwd;
|
|
}
|
|
break;
|
|
}
|
|
case SentPwd: {
|
|
// Parse the current working directory
|
|
if (socket()->isResponse("2")) {
|
|
// 257 "/home/default/path"
|
|
TQString tmp = socket()->getResponse();
|
|
int first = tmp.find('"') + 1;
|
|
tmp = tmp.mid(first, tmp.findRev('"') - first);
|
|
|
|
socket()->setDefaultDirectory(tmp);
|
|
socket()->setCurrentDirectory(tmp);
|
|
}
|
|
|
|
// Enable transmission of keepalive events
|
|
socket()->keepaliveStart();
|
|
|
|
currentState = None;
|
|
socket()->emitEvent(Event::EventMessage, i18n("Connected."));
|
|
socket()->emitEvent(Event::EventConnect);
|
|
socket()->m_login = true;
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void parseFeat()
|
|
{
|
|
TQString feat = socket()->getResponse().stripWhiteSpace().upper();
|
|
|
|
if (feat.left(3).toInt() > 0 && feat[3] == '-')
|
|
feat.remove(0, 4);
|
|
|
|
if (feat.left(4) == "MDTM") {
|
|
// Server has MDTM (MoDification TiMe) support
|
|
socket()->setConfig("feat.mdtm", 1);
|
|
} else if (feat.left(4) == "PRET") {
|
|
// Server is a distributed ftp server and requires PRET for transfers
|
|
socket()->setConfig("feat.pret", 1);
|
|
} else if (feat.left(4) == "MLSD") {
|
|
// Server supports machine-friendly directory listings
|
|
socket()->setConfig("feat.mlsd", 1);
|
|
} else if (feat.left(4) == "REST") {
|
|
// Server supports resume operations
|
|
socket()->setConfig("feat.rest", 1);
|
|
} else if (feat.left(4) == "SSCN") {
|
|
// Server supports SSCN for secure site-to-site transfers
|
|
socket()->setConfig("feat.sscn", 1);
|
|
socket()->setConfig("feat.cpsv", 0);
|
|
} else if (feat.left(4) == "CPSV" && !socket()->getConfigInt("feat.sscn")) {
|
|
// Server supports CPSV for secure site-to-site transfers
|
|
socket()->setConfig("feat.cpsv", 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoConnect(const KURL &url)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Connecting..."));
|
|
emitEvent(Event::EventMessage, i18n("Connecting to %1:%2...").arg(url.host()).arg(url.port()));
|
|
|
|
if (!getConfig("encoding").isEmpty())
|
|
changeEncoding(getConfig("encoding"));
|
|
|
|
// Start the connect procedure
|
|
m_controlConnecting = true;
|
|
setCurrentUrl(url);
|
|
KNetwork::KStreamSocket::connect(url.host(), TQString::number(url.port()));
|
|
}
|
|
|
|
void FtpSocket::slotConnected()
|
|
{
|
|
if (getConfigInt("ssl.use_implicit")) {
|
|
m_controlSsl = new Ssl(this);
|
|
|
|
// Setup client certificate if one was provided
|
|
if (m_clientCert)
|
|
m_controlSsl->setClientCertificate(m_clientCert);
|
|
|
|
if (m_controlSsl->connect()) {
|
|
emitEvent(Event::EventMessage, i18n("SSL negotiation successful. Connection is secured with %1 bit cipher %2.").arg(m_controlSsl->connectionInfo().getCipherUsedBits()).arg(m_controlSsl->connectionInfo().getCipher()));
|
|
setConfig("ssl", 1);
|
|
} else {
|
|
delete m_controlSsl;
|
|
m_controlSsl = 0;
|
|
|
|
emitEvent(Event::EventMessage, i18n("SSL negotiation failed. Connect aborted."));
|
|
resetCommandClass(Failed);
|
|
|
|
protoAbort();
|
|
}
|
|
}
|
|
|
|
timeoutWait(true);
|
|
|
|
emitEvent(Event::EventState, i18n("Logging in..."));
|
|
emitEvent(Event::EventMessage, i18n("Connected with server, waiting for welcome message..."));
|
|
setupCommandClass(FtpCommandConnect);
|
|
}
|
|
|
|
void FtpSocket::slotError()
|
|
{
|
|
if (isFatalError(error())) {
|
|
emitEvent(Event::EventMessage, i18n("Failed to connect (%1)").arg(errorString(error())));
|
|
emitError(ConnectFailed);
|
|
|
|
resetCommandClass(FailedSilently);
|
|
}
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// **************************************** DISCONNECT ***************************************
|
|
// *******************************************************************************************
|
|
|
|
void FtpSocket::protoDisconnect()
|
|
{
|
|
Socket::protoDisconnect();
|
|
|
|
// Close SSL
|
|
if (getConfigInt("ssl") && m_controlSsl) {
|
|
m_controlSsl->close();
|
|
delete m_controlSsl;
|
|
m_controlSsl = 0;
|
|
|
|
if (m_clientCert) {
|
|
delete m_clientCert;
|
|
m_clientCert = 0;
|
|
}
|
|
}
|
|
|
|
// Terminate the connection
|
|
m_login = false;
|
|
KNetwork::KStreamSocket::close();
|
|
}
|
|
|
|
void FtpSocket::protoAbort()
|
|
{
|
|
Socket::protoAbort();
|
|
|
|
if (getCurrentCommand() != Commands::CmdNone) {
|
|
// Abort current command
|
|
if (getCurrentCommand() == Commands::CmdConnect)
|
|
protoDisconnect();
|
|
|
|
if (m_cmdData)
|
|
resetCommandClass(UserAbort);
|
|
|
|
emitEvent(Event::EventMessage, i18n("Aborted."));
|
|
}
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ********************************* NEGOTIATE DATA CONNECTION *******************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandNegotiateData : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentSscnOff,
|
|
SentType,
|
|
SentProt,
|
|
SentPret,
|
|
NegotiateActive,
|
|
NegotiatePasv,
|
|
NegotiateEpsv,
|
|
HaveConnection,
|
|
SentRest,
|
|
SentDataCmd,
|
|
WaitTransfer
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandNegotiateData, FtpSocket, CmdNone)
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
if (socket()->getConfigInt("sscn.activated")) {
|
|
// First disable SSCN
|
|
currentState = SentSscnOff;
|
|
socket()->sendCommand("SSCN OFF");
|
|
return;
|
|
}
|
|
}
|
|
case SentSscnOff: {
|
|
if (currentState == SentSscnOff)
|
|
socket()->setConfig("sscn.activated", 0);
|
|
|
|
// Change type
|
|
currentState = SentType;
|
|
socket()->resetTransferStart();
|
|
|
|
TQString type = "TYPE ";
|
|
type.append(socket()->getConfigInt("params.data_type"));
|
|
socket()->sendCommand(type);
|
|
break;
|
|
}
|
|
case SentType: {
|
|
if (socket()->getConfigInt("ssl") && socket()->getConfigInt("ssl.prot_mode") == 1) {
|
|
currentState = SentProt;
|
|
|
|
if (socket()->getPreviousCommand() == Commands::CmdList)
|
|
socket()->sendCommand("PROT P");
|
|
else
|
|
socket()->sendCommand("PROT C");
|
|
} else if (socket()->getConfigInt("feat.pret")) {
|
|
currentState = SentPret;
|
|
socket()->sendCommand("PRET " + socket()->getConfig("params.data_command"));
|
|
} else {
|
|
negotiateDataConnection();
|
|
}
|
|
break;
|
|
}
|
|
case SentProt: {
|
|
if (socket()->getConfigInt("feat.pret")) {
|
|
currentState = SentPret;
|
|
socket()->sendCommand("PRET " + socket()->getConfig("params.data_command"));
|
|
} else {
|
|
negotiateDataConnection();
|
|
}
|
|
break;
|
|
}
|
|
case SentPret: {
|
|
// PRET failed because of filesystem problems, abort right away!
|
|
if (socket()->isResponse("530")) {
|
|
socket()->emitError(PermissionDenied);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
} else if (socket()->isResponse("550")) {
|
|
socket()->emitError(FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
} else if (socket()->isResponse("5")) {
|
|
// PRET is not supported, disable for future use
|
|
socket()->setConfig("feat.pret", 0);
|
|
}
|
|
|
|
negotiateDataConnection();
|
|
break;
|
|
}
|
|
case NegotiateActive: negotiateActive(); break;
|
|
case NegotiateEpsv: negotiateEpsv(); break;
|
|
case NegotiatePasv: negotiatePasv(); break;
|
|
case HaveConnection: {
|
|
// We have the connection
|
|
if (socket()->getConfigInt("params.data_rest_do")) {
|
|
currentState = SentRest;
|
|
socket()->sendCommand("REST " + TQString::number(socket()->getConfigFs("params.data_rest")));
|
|
} else {
|
|
currentState = SentDataCmd;
|
|
socket()->sendCommand(socket()->getConfig("params.data_command"));
|
|
}
|
|
break;
|
|
}
|
|
case SentRest: {
|
|
if (!socket()->isResponse("2") && !socket()->isResponse("3")) {
|
|
socket()->setConfig("feat.rest", 0);
|
|
socket()->getTransferFile()->close();
|
|
|
|
bool ok;
|
|
|
|
if (socket()->getPreviousCommand() == Commands::CmdGet)
|
|
ok = socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
|
|
else
|
|
ok = socket()->getTransferFile()->open(IO_ReadOnly);
|
|
|
|
// Check if there was a problem opening the file
|
|
if (!ok) {
|
|
socket()->emitError(FileOpenFailed);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We have sent REST, now send the data command
|
|
currentState = SentDataCmd;
|
|
socket()->sendCommand(socket()->getConfig("params.data_command"));
|
|
break;
|
|
}
|
|
case SentDataCmd: {
|
|
if (!socket()->isResponse("1")) {
|
|
// Some problems while executing the data command
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
if (!socket()->isMultiline()) {
|
|
socket()->checkTransferStart();
|
|
currentState = WaitTransfer;
|
|
}
|
|
break;
|
|
}
|
|
case WaitTransfer: {
|
|
if (!socket()->isResponse("2")) {
|
|
// Transfer has failed
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
if (!socket()->isMultiline()) {
|
|
// Transfer has been completed
|
|
socket()->checkTransferEnd();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void negotiateDataConnection()
|
|
{
|
|
if (socket()->getConfigInt("feat.epsv")) {
|
|
negotiateEpsv();
|
|
} else if (socket()->getConfigInt("feat.pasv")) {
|
|
negotiatePasv();
|
|
} else {
|
|
negotiateActive();
|
|
}
|
|
}
|
|
|
|
void negotiateEpsv()
|
|
{
|
|
if (currentState == NegotiateEpsv) {
|
|
if (!socket()->isResponse("2")) {
|
|
// Negotiation failed
|
|
socket()->setConfig("feat.epsv", "0");
|
|
|
|
// Try the next thing
|
|
negotiateDataConnection();
|
|
return;
|
|
}
|
|
|
|
// 229 Entering Extended Passive Mode (|||55016|)
|
|
const char *begin = strchr(socket()->getResponse().ascii(), '(');
|
|
int port;
|
|
|
|
if (!begin || sscanf(begin, "(|||%d|)", &port) != 1) {
|
|
// Unable to parse, try the next thing
|
|
socket()->setConfig("feat.epsv", "0");
|
|
negotiateDataConnection();
|
|
return;
|
|
}
|
|
|
|
// We have the address, let's setup the transfer socket and then
|
|
// we are done.
|
|
currentState = HaveConnection;
|
|
socket()->setupPassiveTransferSocket(TQString::null, port);
|
|
} else {
|
|
// Just send the EPSV command
|
|
currentState = NegotiateEpsv;
|
|
socket()->sendCommand("EPSV");
|
|
}
|
|
}
|
|
|
|
void negotiatePasv()
|
|
{
|
|
if (currentState == NegotiatePasv) {
|
|
if (!socket()->isResponse("2")) {
|
|
// Negotiation failed
|
|
socket()->setConfig("feat.pasv", "0");
|
|
|
|
// Try the next thing
|
|
negotiateDataConnection();
|
|
return;
|
|
}
|
|
|
|
// Ok PASV command successfull - let's parse the result
|
|
int ip[6];
|
|
const char *begin = strchr(socket()->getResponse().ascii(), '(');
|
|
|
|
// Some stinky servers don't respect RFC and do it on their own
|
|
if (!begin) {
|
|
delete &begin;
|
|
const char *begin = strchr(socket()->getResponse().ascii(), '=');
|
|
}
|
|
|
|
if (!begin || (sscanf(begin, "(%d,%d,%d,%d,%d,%d)",&ip[0], &ip[1], &ip[2], &ip[3], &ip[4], &ip[5]) != 6 &&
|
|
sscanf(begin, "=%d,%d,%d,%d,%d,%d",&ip[0], &ip[1], &ip[2], &ip[3], &ip[4], &ip[5]) != 6)) {
|
|
// Unable to parse, try the next thing
|
|
socket()->setConfig("feat.pasv", "0");
|
|
negotiateDataConnection();
|
|
return;
|
|
}
|
|
|
|
// Convert to string
|
|
TQString host;
|
|
int port;
|
|
|
|
host.sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
|
port = ip[4] << 8 | ip[5];
|
|
|
|
// If the reported IP address is from a private IP range, this might be because the
|
|
// remote server is not properly configured. So we just use the server's real IP instead
|
|
// of the one we got (if the host is really local, then this should work as well).
|
|
if (!socket()->getConfigInt("feat.pret")) {
|
|
if (host.startsWith("192.168.") || host.startsWith("10.") || host.startsWith("172.16."))
|
|
host = socket()->peerAddress().nodeName();
|
|
}
|
|
|
|
// We have the address, let's setup the transfer socket and then
|
|
// we are done.
|
|
currentState = HaveConnection;
|
|
socket()->setupPassiveTransferSocket(host, port);
|
|
} else {
|
|
// Just send the PASV command
|
|
currentState = NegotiatePasv;
|
|
socket()->sendCommand("PASV");
|
|
}
|
|
}
|
|
|
|
void negotiateActive()
|
|
{
|
|
if (currentState == NegotiateActive) {
|
|
if (!socket()->isResponse("2")) {
|
|
if (socket()->getConfigInt("feat.eprt")) {
|
|
socket()->setConfig("feat.eprt", 0);
|
|
} else {
|
|
// Negotiation failed, reset since active is the last fallback
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
} else {
|
|
currentState = HaveConnection;
|
|
socket()->nextCommandAsync();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Setup the socket and set the apropriate port command
|
|
currentState = NegotiateActive;
|
|
|
|
KNetwork::TDESocketAddress address = socket()->setupActiveTransferSocket();
|
|
if (address.address()) {
|
|
if (socket()->getConfigInt("feat.eprt")) {
|
|
TQString ianaFamily = TQString::number(address.ianaFamily());
|
|
|
|
socket()->sendCommand("EPRT |" + ianaFamily + "|" + address.nodeName() + "|" + address.serviceName() + "|");
|
|
} else if (address.ianaFamily() == 1) {
|
|
TQString format = address.nodeName().replace(".", ",");
|
|
|
|
format.append(",");
|
|
format.append(TQString::number((unsigned char) address.address()->sa_data[0]));
|
|
format.append(",");
|
|
format.append(TQString::number((unsigned char) address.address()->sa_data[1]));
|
|
|
|
socket()->sendCommand("PORT " + format);
|
|
} else {
|
|
socket()->emitEvent(Event::EventMessage, i18n("Incompatible address family for PORT, but EPRT not supported, aborting!"));
|
|
socket()->resetCommandClass(Failed);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::initializeTransferSocket()
|
|
{
|
|
m_transferConnecting = true;
|
|
m_transferEnd = 0;
|
|
m_transferBytes = 0;
|
|
m_transferBufferSize = 4096;
|
|
m_transferBuffer = (char*) malloc(m_transferBufferSize);
|
|
|
|
m_speedLastTime = time(0);
|
|
m_speedLastBytes = 0;
|
|
|
|
// Setup the speed limiter
|
|
switch (getPreviousCommand()) {
|
|
case Commands::CmdGet: SpeedLimiter::self()->append(this, SpeedLimiter::Download); break;
|
|
case Commands::CmdPut: SpeedLimiter::self()->append(this, SpeedLimiter::Upload); break;
|
|
default: break;
|
|
}
|
|
|
|
m_transferSocket->enableRead(false);
|
|
m_transferSocket->setBlocking(false);
|
|
m_transferSocket->setAddressReuseable(true);
|
|
}
|
|
|
|
void FtpSocket::setupPassiveTransferSocket(const TQString &host, int port)
|
|
{
|
|
// Use the host from control connection if empty
|
|
TQString realHost = host;
|
|
if (host.isEmpty() || getConfigInt("pasv.use_site_ip"))
|
|
realHost = peerAddress().nodeName();
|
|
|
|
// Let's connect
|
|
emitEvent(Event::EventMessage, i18n("Establishing data connection with %1:%2...").arg(realHost).arg(port));
|
|
|
|
if (!m_transferSocket)
|
|
m_transferSocket = new KNetwork::KStreamSocket();
|
|
|
|
initializeTransferSocket();
|
|
m_transferSocket->connect(realHost, TQString::number(port));
|
|
}
|
|
|
|
KNetwork::TDESocketAddress FtpSocket::setupActiveTransferSocket()
|
|
{
|
|
if (!m_serverSocket)
|
|
m_serverSocket = new KNetwork::TDEServerSocket();
|
|
|
|
m_serverSocket->setAcceptBuffered(false);
|
|
m_serverSocket->setFamily(KNetwork::KResolver::InetFamily);
|
|
|
|
if (KFTPCore::Config::activeForcePort()) {
|
|
// Bind only to ports in a specified portrange
|
|
bool found = false;
|
|
unsigned int max = KFTPCore::Config::activeMaxPort();
|
|
unsigned int min = KFTPCore::Config::activeMinPort();
|
|
|
|
for (unsigned int port = min + rand() % (max - min + 1); port <= max; port++) {
|
|
m_serverSocket->setAddress(TQString::number(port));
|
|
bool success = m_serverSocket->listen();
|
|
|
|
if (found = (success && m_serverSocket->error() == TDESocketBase::NoError))
|
|
break;
|
|
|
|
m_serverSocket->close();
|
|
}
|
|
|
|
if (!found) {
|
|
emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
|
|
resetCommandClass(Failed);
|
|
return KNetwork::TDESocketAddress();
|
|
}
|
|
} else {
|
|
m_serverSocket->setAddress("0");
|
|
|
|
if (!m_serverSocket->listen()) {
|
|
emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
|
|
resetCommandClass(Failed);
|
|
return KNetwork::TDESocketAddress();
|
|
}
|
|
}
|
|
|
|
KNetwork::TDESocketAddress serverAddr = m_serverSocket->localAddress();
|
|
KNetwork::TDESocketAddress controlAddr = localAddress();
|
|
KNetwork::TDESocketAddress request;
|
|
|
|
if (KFTPCore::Config::portForceIp() && !getConfigInt("active.no_force_ip")) {
|
|
TQString remoteIp = peerAddress().nodeName();
|
|
|
|
if (KFTPCore::Config::ignoreExternalIpForLan() &&
|
|
(remoteIp.startsWith("192.168.") || remoteIp.startsWith("10.") || remoteIp.startsWith("172.16."))) {
|
|
request = controlAddr;
|
|
} else {
|
|
// Force a specified IP/hostname to be used in PORT
|
|
KNetwork::KResolverResults resolverResults;
|
|
|
|
resolverResults = KNetwork::KResolver::resolve(KFTPCore::Config::portIp(), "21");
|
|
if (resolverResults.error() < 0) {
|
|
// Well, we are unable to resolve the name, so we should use what we got
|
|
// from control socket
|
|
request = controlAddr;
|
|
} else {
|
|
// The name has been resolved and we have the address, so we should
|
|
// use it
|
|
request = resolverResults[0].address();
|
|
}
|
|
}
|
|
} else {
|
|
// Just use our IP we bound to when connecting to the remote server
|
|
request = controlAddr;
|
|
}
|
|
|
|
// Set the proper port
|
|
request.address()->sa_data[0] = serverAddr.address()->sa_data[0];
|
|
request.address()->sa_data[1] = serverAddr.address()->sa_data[1];
|
|
|
|
emitEvent(Event::EventMessage, i18n("Waiting for data connection on port %1...").arg(serverAddr.serviceName()));
|
|
|
|
return request;
|
|
}
|
|
|
|
void FtpSocket::slotDataAccept(KNetwork::KStreamSocket *socket)
|
|
{
|
|
m_transferSocket = socket;
|
|
initializeTransferSocket();
|
|
|
|
// Socket has been accepted so the server is not needed anymore
|
|
delete m_serverSocket;
|
|
|
|
emitEvent(Event::EventMessage, i18n("Data connection established."));
|
|
checkTransferStart();
|
|
}
|
|
|
|
void FtpSocket::closeDataTransferSocket()
|
|
{
|
|
if (m_dataSsl) {
|
|
m_dataSsl->close();
|
|
delete m_dataSsl;
|
|
m_dataSsl = 0;
|
|
}
|
|
|
|
// Free the buffer and invalidate the socket
|
|
free(m_transferBuffer);
|
|
|
|
m_transferSocket->close();
|
|
delete m_transferSocket;
|
|
m_transferBytes = 0;
|
|
|
|
SpeedLimiter::self()->remove(this);
|
|
}
|
|
|
|
void FtpSocket::transferCompleted()
|
|
{
|
|
// Transfer has been completed, cleanup
|
|
closeDataTransferSocket();
|
|
checkTransferEnd();
|
|
}
|
|
|
|
void FtpSocket::checkTransferStart()
|
|
{
|
|
if (++m_transferStart >= 2) {
|
|
// Setup SSL data connection
|
|
if (getConfigInt("ssl") && (getConfigInt("ssl.prot_mode") == 0 ||
|
|
(getConfigInt("ssl.prot_mode") == 1 && getToplevelCommand() == Commands::CmdList)) && !m_dataSsl) {
|
|
m_dataSsl = new Ssl(m_transferSocket);
|
|
|
|
if (m_dataSsl->connect()) {
|
|
emitEvent(Event::EventMessage, i18n("Data channel secured with %1 bit SSL.").arg(m_dataSsl->connectionInfo().getCipherUsedBits()));
|
|
} else {
|
|
emitEvent(Event::EventMessage, i18n("SSL negotiation for the data channel has failed. Aborting transfer."));
|
|
resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FtpSocket::checkTransferEnd()
|
|
{
|
|
if (++m_transferEnd >= 2) {
|
|
emitEvent(Event::EventMessage, i18n("Transfer completed."));
|
|
resetCommandClass();
|
|
}
|
|
}
|
|
|
|
void FtpSocket::slotDataConnected()
|
|
{
|
|
emitEvent(Event::EventMessage, i18n("Data connection established."));
|
|
|
|
checkTransferStart();
|
|
nextCommand();
|
|
}
|
|
|
|
void FtpSocket::variableBufferUpdate(TQ_LONG size)
|
|
{
|
|
if (size > m_transferBufferSize - 64) {
|
|
if (m_transferBufferSize + 512 <= 32768) {
|
|
m_transferBufferSize += 512;
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
}
|
|
} else if (size < m_transferBufferSize - 65) {
|
|
if (m_transferBufferSize - 512 >= 4096) {
|
|
m_transferBufferSize -= 512;
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FtpSocket::slotDataTryWrite()
|
|
{
|
|
bool updateVariableBuffer = true;
|
|
|
|
// Enforce speed limits
|
|
if (allowedBytes() > -1) {
|
|
m_transferBufferSize = allowedBytes();
|
|
|
|
if (m_transferBufferSize > 32768)
|
|
m_transferBufferSize = 32768;
|
|
else if (m_transferBufferSize == 0)
|
|
return;
|
|
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
updateVariableBuffer = false;
|
|
} else if (m_transferBufferSize == 0) {
|
|
m_transferBufferSize = 4096;
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
}
|
|
|
|
if (!getTransferFile()->isOpen())
|
|
return;
|
|
|
|
// If there is nothing to upload, just close the connection right away
|
|
if (getTransferFile()->size() == 0) {
|
|
transferCompleted();
|
|
return;
|
|
}
|
|
|
|
TQFile::Offset tmpOffset = getTransferFile()->at();
|
|
TQ_LONG readSize = getTransferFile()->readBlock(m_transferBuffer, m_transferBufferSize);
|
|
|
|
TQ_LONG size = 0;
|
|
|
|
if (m_dataSsl)
|
|
size = m_dataSsl->write(m_transferBuffer, readSize);
|
|
else
|
|
size = m_transferSocket->writeBlock(m_transferBuffer, readSize);
|
|
|
|
if (size < 0) {
|
|
getTransferFile()->at(tmpOffset);
|
|
return;
|
|
} else if (size < readSize)
|
|
getTransferFile()->at(tmpOffset + size);
|
|
|
|
m_transferBytes += size;
|
|
updateUsage(size);
|
|
timeoutPing();
|
|
|
|
if (getTransferFile()->atEnd()) {
|
|
// We have reached the end of file, so we should terminate the connection
|
|
transferCompleted();
|
|
return;
|
|
}
|
|
|
|
if (updateVariableBuffer)
|
|
variableBufferUpdate(size);
|
|
}
|
|
|
|
void FtpSocket::slotDataTryRead()
|
|
{
|
|
bool updateVariableBuffer = true;
|
|
|
|
// Enforce speed limits
|
|
if (allowedBytes() > -1) {
|
|
m_transferBufferSize = allowedBytes();
|
|
|
|
if (m_transferBufferSize > 32768)
|
|
m_transferBufferSize = 32768;
|
|
else if (m_transferBufferSize == 0)
|
|
return;
|
|
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
updateVariableBuffer = false;
|
|
} else if (m_transferBufferSize == 0) {
|
|
m_transferBufferSize = 4096;
|
|
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
|
|
}
|
|
|
|
TQ_LONG size = 0;
|
|
|
|
if (m_dataSsl) {
|
|
size = m_dataSsl->read(m_transferBuffer, m_transferBufferSize);
|
|
|
|
if (size == -1) {
|
|
transferCompleted();
|
|
return;
|
|
}
|
|
} else {
|
|
size = m_transferSocket->readBlock(m_transferBuffer, m_transferBufferSize);
|
|
|
|
// Check if the connection has been closed
|
|
if (m_transferSocket->error() != NoError) {
|
|
if (m_transferSocket->error() != WouldBlock) {
|
|
transferCompleted();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (size <= 0) {
|
|
if (!m_dataSsl)
|
|
transferCompleted();
|
|
|
|
return;
|
|
}
|
|
|
|
updateUsage(size);
|
|
timeoutPing();
|
|
|
|
switch (getPreviousCommand()) {
|
|
case Commands::CmdList: {
|
|
// Feed the data to the directory listing parser
|
|
if (m_directoryParser)
|
|
m_directoryParser->addData(m_transferBuffer, size);
|
|
break;
|
|
}
|
|
case Commands::CmdGet: {
|
|
// Write to file
|
|
getTransferFile()->writeBlock(m_transferBuffer, size);
|
|
m_transferBytes += size;
|
|
break;
|
|
}
|
|
default: {
|
|
tqDebug("WARNING: slotDataReadActivity called for an invalid command!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (updateVariableBuffer)
|
|
variableBufferUpdate(size);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* LIST ******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandList : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentCwd,
|
|
SentStat,
|
|
WaitList
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandList, FtpSocket, CmdList)
|
|
|
|
TQString path;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
path = socket()->getConfig("params.list.path");
|
|
|
|
if (socket()->isChained())
|
|
socket()->m_lastDirectoryListing = DirectoryListing();
|
|
|
|
// Change working directory
|
|
currentState = SentCwd;
|
|
socket()->changeWorkingDirectory(path);
|
|
break;
|
|
}
|
|
case SentCwd: {
|
|
if (!socket()->getConfigInt("status.cwd")) {
|
|
// Change directory has failed and we should be silent (=error reporting is off)
|
|
socket()->resetCommandClass();
|
|
return;
|
|
}
|
|
|
|
// Check the directory listing cache
|
|
DirectoryListing cached = Cache::self()->findCached(socket(), socket()->getCurrentDirectory());
|
|
if (cached.isValid()) {
|
|
socket()->emitEvent(Event::EventMessage, i18n("Using cached directory listing."));
|
|
|
|
if (socket()->isChained()) {
|
|
// We don't emit an event, because this list has been called from another
|
|
// command. Just save the listing.
|
|
socket()->m_lastDirectoryListing = cached;
|
|
} else
|
|
socket()->emitEvent(Event::EventDirectoryListing, cached);
|
|
|
|
socket()->resetCommandClass();
|
|
return;
|
|
}
|
|
|
|
socket()->m_directoryParser = new FtpDirectoryParser(socket());
|
|
|
|
// Support for faster stat directory listings over the control connection
|
|
if (socket()->getConfigInt("stat_listings")) {
|
|
currentState = SentStat;
|
|
socket()->sendCommand("STAT .");
|
|
return;
|
|
}
|
|
|
|
// First we have to initialize the data connection, another class will
|
|
// do this for us, so we just add it to the command chain
|
|
socket()->setConfig("params.data_rest_do", 0);
|
|
socket()->setConfig("params.data_type", 'A');
|
|
|
|
if (socket()->getConfigInt("feat.mlsd"))
|
|
socket()->setConfig("params.data_command", "MLSD");
|
|
else
|
|
socket()->setConfig("params.data_command", "LIST -a");
|
|
|
|
currentState = WaitList;
|
|
chainCommandClass(FtpCommandNegotiateData);
|
|
break;
|
|
}
|
|
case SentStat: {
|
|
if (!socket()->isResponse("2")) {
|
|
// The server doesn't support STAT, disable it and fallback
|
|
socket()->setConfig("stat_listings", 0);
|
|
|
|
socket()->setConfig("params.data_rest_do", 0);
|
|
socket()->setConfig("params.data_type", 'A');
|
|
|
|
if (socket()->getConfigInt("feat.mlsd"))
|
|
socket()->setConfig("params.data_command", "MLSD");
|
|
else
|
|
socket()->setConfig("params.data_command", "LIST -a");
|
|
|
|
currentState = WaitList;
|
|
chainCommandClass(FtpCommandNegotiateData);
|
|
return;
|
|
} else if (socket()->isMultiline()) {
|
|
// Some servers put the response code into the multiline reply
|
|
TQString response = socket()->getResponse();
|
|
if (response.left(3) == "211")
|
|
response = response.mid(4);
|
|
|
|
socket()->m_directoryParser->addDataLine(response);
|
|
return;
|
|
}
|
|
|
|
// If we are done, just go on and emit the listing
|
|
}
|
|
case WaitList: {
|
|
// List has been received
|
|
if (socket()->isChained()) {
|
|
// We don't emit an event, because this list has been called from another
|
|
// command. Just save the listing.
|
|
socket()->m_lastDirectoryListing = socket()->m_directoryParser->getListing();
|
|
} else
|
|
socket()->emitEvent(Event::EventDirectoryListing, socket()->m_directoryParser->getListing());
|
|
|
|
// Cache the directory listing
|
|
Cache::self()->addDirectory(socket(), socket()->m_directoryParser->getListing());
|
|
|
|
delete socket()->m_directoryParser;
|
|
socket()->m_directoryParser = 0;
|
|
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoList(const KURL &path)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Fetching directory listing..."));
|
|
emitEvent(Event::EventMessage, i18n("Fetching directory listing..."));
|
|
|
|
// Set the directory that should be listed
|
|
setConfig("params.list.path", path.path());
|
|
|
|
activateCommandClass(FtpCommandList);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* GET *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandGet : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentCwd,
|
|
SentMdtm,
|
|
StatDone,
|
|
DestChecked,
|
|
WaitTransfer
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandGet, FtpSocket, CmdGet)
|
|
|
|
KURL sourceFile;
|
|
KURL destinationFile;
|
|
time_t modificationTime;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
modificationTime = 0;
|
|
sourceFile.setPath(socket()->getConfig("params.get.source"));
|
|
destinationFile.setPath(socket()->getConfig("params.get.destination"));
|
|
|
|
// Attempt to CWD to the parent directory
|
|
currentState = SentCwd;
|
|
socket()->changeWorkingDirectory(sourceFile.directory());
|
|
break;
|
|
}
|
|
case SentCwd: {
|
|
// Send MDTM
|
|
if (socket()->getConfigInt("feat.mdtm")) {
|
|
currentState = SentMdtm;
|
|
socket()->sendCommand("MDTM " + sourceFile.path());
|
|
break;
|
|
} else {
|
|
// Don't break so we will get on to checking for file existance
|
|
}
|
|
}
|
|
case SentMdtm: {
|
|
if (currentState == SentMdtm) {
|
|
if (socket()->isResponse("550")) {
|
|
// The file probably doesn't exist, just ignore it
|
|
} else if (!socket()->isResponse("213")) {
|
|
socket()->setConfig("feat.mdtm", 0);
|
|
} else {
|
|
// Parse MDTM response
|
|
struct tm dt = {0,0,0,0,0,0,0,0,0,0,0};
|
|
TQString tmp(socket()->getResponse());
|
|
|
|
tmp.remove(0, 4);
|
|
dt.tm_year = tmp.left(4).toInt() - 1900;
|
|
dt.tm_mon = tmp.mid(4, 2).toInt() - 1;
|
|
dt.tm_mday = tmp.mid(6, 2).toInt();
|
|
dt.tm_hour = tmp.mid(8, 2).toInt();
|
|
dt.tm_min = tmp.mid(10, 2).toInt();
|
|
dt.tm_sec = tmp.mid(12, 2).toInt();
|
|
modificationTime = mktime(&dt);
|
|
}
|
|
}
|
|
|
|
// Check if the local file exists and stat the remote file if so
|
|
if (TQDir::root().exists(destinationFile.path())) {
|
|
socket()->protoStat(sourceFile);
|
|
currentState = StatDone;
|
|
return;
|
|
} else {
|
|
TDEStandardDirs::makeDir(destinationFile.directory());
|
|
|
|
// Don't break so we will get on to initiating the data connection
|
|
}
|
|
}
|
|
case StatDone: {
|
|
if (currentState == StatDone) {
|
|
DirectoryListing list;
|
|
list.addEntry(socket()->getStatResponse());
|
|
|
|
currentState = DestChecked;
|
|
socket()->emitEvent(Event::EventFileExists, list);
|
|
return;
|
|
}
|
|
}
|
|
case DestChecked: {
|
|
socket()->setConfig("params.data_rest_do", 0);
|
|
|
|
if (isWakeup()) {
|
|
// We have been waken up because a decision has been made
|
|
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
|
|
|
|
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
|
|
event->action = FileExistsWakeupEvent::Overwrite;
|
|
|
|
switch (event->action) {
|
|
case FileExistsWakeupEvent::Rename: {
|
|
// Change the destination filename, otherwise it is the same as overwrite
|
|
destinationFile.setPath(event->newFileName);
|
|
}
|
|
case FileExistsWakeupEvent::Overwrite: {
|
|
socket()->getTransferFile()->setName(destinationFile.path());
|
|
socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
|
|
|
|
if (socket()->getConfigInt("feat.rest")) {
|
|
socket()->setConfig("params.data_rest_do", 1);
|
|
socket()->setConfig("params.data_rest", 0);
|
|
}
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Resume: {
|
|
socket()->getTransferFile()->setName(destinationFile.path());
|
|
socket()->getTransferFile()->open(IO_WriteOnly | IO_Append);
|
|
|
|
// Signal resume
|
|
socket()->emitEvent(Event::EventResumeOffset, socket()->getTransferFile()->size());
|
|
|
|
socket()->setConfig("params.data_rest_do", 1);
|
|
socket()->setConfig("params.data_rest", (filesize_t) socket()->getTransferFile()->size());
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Skip: {
|
|
// Transfer should be aborted
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
socket()->resetCommandClass();
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// The file doesn't exist so we are free to overwrite
|
|
socket()->getTransferFile()->setName(destinationFile.path());
|
|
socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
|
|
}
|
|
|
|
// Check if there was a problem opening the file
|
|
if (!socket()->getTransferFile()->isOpen()) {
|
|
socket()->emitError(FileOpenFailed);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
// First we have to initialize the data connection, another class will
|
|
// do this for us, so we just add it to the command chain
|
|
socket()->setConfig("params.data_type", KFTPCore::Config::self()->ftpMode(sourceFile.path()));
|
|
socket()->setConfig("params.data_command", "RETR " + sourceFile.filename());
|
|
|
|
currentState = WaitTransfer;
|
|
chainCommandClass(FtpCommandNegotiateData);
|
|
break;
|
|
}
|
|
case WaitTransfer: {
|
|
// Transfer has been completed
|
|
socket()->getTransferFile()->close();
|
|
|
|
if (modificationTime != 0) {
|
|
// Use the modification time we got from MDTM
|
|
utimbuf tmp;
|
|
tmp.actime = time(0);
|
|
tmp.modtime = modificationTime;
|
|
utime(destinationFile.path().latin1(), &tmp);
|
|
}
|
|
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoGet(const KURL &source, const KURL &destination)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Transfering..."));
|
|
emitEvent(Event::EventMessage, i18n("Downloading file '%1'...").arg(source.fileName()));
|
|
|
|
// Set the source and destination
|
|
setConfig("params.get.source", source.path());
|
|
setConfig("params.get.destination", destination.path());
|
|
|
|
activateCommandClass(FtpCommandGet);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* CWD *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandCwd : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentCwd,
|
|
SentPwd,
|
|
SentMkd,
|
|
SentCwdEnd
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandCwd, FtpSocket, CmdNone)
|
|
|
|
TQString targetDirectory;
|
|
TQString currentPathPart;
|
|
TQString cached;
|
|
int currentPart;
|
|
int numParts;
|
|
bool shouldCreate;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
targetDirectory = socket()->getConfig("params.cwd.path");
|
|
socket()->setConfig("status.cwd", 1);
|
|
|
|
// If we are already there, no need to CWD
|
|
if (socket()->getCurrentDirectory() == targetDirectory) {
|
|
socket()->resetCommandClass();
|
|
return;
|
|
}
|
|
|
|
cached = Cache::self()->findCachedPath(socket(), targetDirectory);
|
|
if (!cached.isEmpty()) {
|
|
if (socket()->getCurrentDirectory() == cached) {
|
|
// We are already there
|
|
socket()->resetCommandClass();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// First check the toplevel directory and if it exists we are done
|
|
currentState = SentCwd;
|
|
currentPart = 0;
|
|
numParts = targetDirectory.contains('/');
|
|
shouldCreate = socket()->getConfigInt("params.cwd.create");
|
|
|
|
socket()->sendCommand("CWD " + targetDirectory);
|
|
break;
|
|
}
|
|
case SentCwd: {
|
|
if (socket()->isMultiline())
|
|
return;
|
|
|
|
if (socket()->isResponse("250") && currentPart == 0) {
|
|
if (!cached.isEmpty()) {
|
|
socket()->setCurrentDirectory(cached);
|
|
socket()->resetCommandClass();
|
|
} else {
|
|
// Directory exists, check where we are
|
|
currentState = SentPwd;
|
|
socket()->sendCommand("PWD");
|
|
}
|
|
} else {
|
|
// Changing the working directory has failed
|
|
if (shouldCreate) {
|
|
currentPathPart = targetDirectory.section('/', 0, ++currentPart);
|
|
currentState = SentMkd;
|
|
socket()->sendCommand("MKD " + currentPathPart);
|
|
} else if (socket()->errorReporting()) {
|
|
socket()->emitError(socket()->getPreviousCommand() == Commands::CmdList ? ListFailed : FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("status.cwd", 0);
|
|
socket()->resetCommandClass();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SentPwd: {
|
|
// Parse the current working directory
|
|
if (socket()->isResponse("2")) {
|
|
TQString tmp = socket()->getResponse();
|
|
int first = tmp.find('"') + 1;
|
|
tmp = tmp.mid(first, tmp.findRev('"') - first);
|
|
|
|
// Set the current directory and cache it
|
|
socket()->setCurrentDirectory(tmp);
|
|
Cache::self()->addPath(socket(), tmp);
|
|
|
|
socket()->resetCommandClass();
|
|
} else if (socket()->errorReporting()) {
|
|
socket()->emitError(socket()->getPreviousCommand() == Commands::CmdList ? ListFailed : FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("status.cwd", 0);
|
|
socket()->resetCommandClass();
|
|
}
|
|
break;
|
|
}
|
|
case SentMkd: {
|
|
// Invalidate parent cache
|
|
if (socket()->isResponse("2")) {
|
|
Cache::self()->invalidateEntry(socket(), KURL(currentPathPart).directory());
|
|
}
|
|
|
|
if (currentPart == numParts) {
|
|
// We are done, since all directories have been created
|
|
currentState = SentCwdEnd;
|
|
socket()->sendCommand("CWD " + targetDirectory);
|
|
} else {
|
|
currentPathPart = targetDirectory.section('/', 0, ++currentPart);
|
|
currentState = SentMkd;
|
|
socket()->sendCommand("MKD " + currentPathPart);
|
|
}
|
|
break;
|
|
}
|
|
case SentCwdEnd: {
|
|
if (socket()->isMultiline())
|
|
return;
|
|
|
|
// See where we are and set current working directory
|
|
currentState = SentPwd;
|
|
socket()->sendCommand("PWD");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::changeWorkingDirectory(const TQString &path, bool shouldCreate)
|
|
{
|
|
// Set the path to cwd to
|
|
setConfig("params.cwd.path", path);
|
|
setConfig("params.cwd.create", shouldCreate);
|
|
|
|
activateCommandClass(FtpCommandCwd);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* PUT *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandPut : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
WaitCwd,
|
|
SentSize,
|
|
StatDone,
|
|
DestChecked,
|
|
WaitTransfer
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandPut, FtpSocket, CmdPut)
|
|
|
|
KURL sourceFile;
|
|
KURL destinationFile;
|
|
|
|
bool fetchedSize;
|
|
filesize_t destinationSize;
|
|
|
|
void cleanup()
|
|
{
|
|
// Unclean upload termination, be sure to erase the cached stat infos
|
|
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
|
|
}
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
sourceFile.setPath(socket()->getConfig("params.get.source"));
|
|
destinationFile.setPath(socket()->getConfig("params.get.destination"));
|
|
fetchedSize = false;
|
|
|
|
// Check if the local file exists
|
|
if (!TQDir::root().exists(sourceFile.path())) {
|
|
socket()->emitError(FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
// Change to the current working directory, creating any directories that are
|
|
// still missing
|
|
currentState = WaitCwd;
|
|
socket()->changeWorkingDirectory(destinationFile.directory(), true);
|
|
break;
|
|
}
|
|
case WaitCwd: {
|
|
// Check if the remote file exists
|
|
if (socket()->getConfigInt("feat.size")) {
|
|
currentState = SentSize;
|
|
socket()->sendCommand("SIZE " + destinationFile.path());
|
|
} else {
|
|
// SIZE is not available, try stat directly
|
|
currentState = StatDone;
|
|
socket()->protoStat(destinationFile);
|
|
}
|
|
break;
|
|
}
|
|
case SentSize: {
|
|
if (socket()->isResponse("213")) {
|
|
destinationSize = socket()->getResponse().mid(4).toULongLong();
|
|
fetchedSize = true;
|
|
|
|
// File exists, we have to stat to get more data
|
|
currentState = StatDone;
|
|
socket()->protoStat(destinationFile);
|
|
} else if (socket()->isResponse("500") || socket()->getResponse().contains("Operation not permitted", false)) {
|
|
// Yes, some servers don't support the SIZE command :/
|
|
socket()->setConfig("feat.size", 0);
|
|
|
|
currentState = StatDone;
|
|
socket()->protoStat(destinationFile);
|
|
} else {
|
|
currentState = DestChecked;
|
|
process();
|
|
}
|
|
break;
|
|
}
|
|
case StatDone: {
|
|
if (!socket()->getStatResponse().filename().isEmpty()) {
|
|
if (fetchedSize) {
|
|
if (socket()->getStatResponse().size() != destinationSize) {
|
|
// It would seem that the size has changed, cached data is invalid
|
|
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
|
|
|
|
currentState = StatDone;
|
|
socket()->protoStat(destinationFile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remote file exists, emit a request for action
|
|
DirectoryListing list;
|
|
list.addEntry(socket()->getStatResponse());
|
|
|
|
currentState = DestChecked;
|
|
socket()->emitEvent(Event::EventFileExists, list);
|
|
return;
|
|
}
|
|
|
|
// Don't break here
|
|
}
|
|
case DestChecked: {
|
|
socket()->setConfig("params.data_rest_do", 0);
|
|
|
|
if (isWakeup()) {
|
|
// We have been waken up because a decision has been made
|
|
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
|
|
|
|
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
|
|
event->action = FileExistsWakeupEvent::Overwrite;
|
|
|
|
switch (event->action) {
|
|
case FileExistsWakeupEvent::Rename: {
|
|
// Change the destination filename, otherwise it is the same as overwrite
|
|
destinationFile.setPath(event->newFileName);
|
|
}
|
|
case FileExistsWakeupEvent::Overwrite: {
|
|
socket()->getTransferFile()->setName(sourceFile.path());
|
|
socket()->getTransferFile()->open(IO_ReadOnly);
|
|
|
|
if (socket()->getConfigInt("feat.rest")) {
|
|
socket()->setConfig("params.data_rest_do", 1);
|
|
socket()->setConfig("params.data_rest", 0);
|
|
}
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Resume: {
|
|
socket()->getTransferFile()->setName(sourceFile.path());
|
|
socket()->getTransferFile()->open(IO_ReadOnly);
|
|
socket()->getTransferFile()->at(socket()->getStatResponse().size());
|
|
|
|
// Signal resume
|
|
socket()->emitEvent(Event::EventResumeOffset, socket()->getStatResponse().size());
|
|
|
|
socket()->setConfig("params.data_rest_do", 1);
|
|
socket()->setConfig("params.data_rest", (filesize_t) socket()->getStatResponse().size());
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Skip: {
|
|
// Transfer should be aborted
|
|
markClean();
|
|
|
|
socket()->resetCommandClass(UserAbort);
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// The file doesn't exist so we are free to overwrite
|
|
socket()->getTransferFile()->setName(sourceFile.path());
|
|
socket()->getTransferFile()->open(IO_ReadOnly);
|
|
}
|
|
|
|
// Check if there was a problem opening the file
|
|
if (!socket()->getTransferFile()->isOpen()) {
|
|
socket()->emitError(FileOpenFailed);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
// First we have to initialize the data connection, another class will
|
|
// do this for us, so we just add it to the command chain
|
|
socket()->setConfig("params.data_type", KFTPCore::Config::self()->ftpMode(destinationFile.path()));
|
|
socket()->setConfig("params.data_command", "STOR " + destinationFile.filename());
|
|
|
|
currentState = WaitTransfer;
|
|
chainCommandClass(FtpCommandNegotiateData);
|
|
break;
|
|
}
|
|
case WaitTransfer: {
|
|
// Transfer has been completed
|
|
Cache::self()->updateDirectoryEntry(socket(), destinationFile, socket()->getTransferFile()->size());
|
|
socket()->getTransferFile()->close();
|
|
markClean();
|
|
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoPut(const KURL &source, const KURL &destination)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Transfering..."));
|
|
emitEvent(Event::EventMessage, i18n("Uploading file '%1'...").arg(source.fileName()));
|
|
|
|
// Set the source and destination
|
|
setConfig("params.get.source", source.path());
|
|
setConfig("params.get.destination", destination.path());
|
|
|
|
activateCommandClass(FtpCommandPut);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// **************************************** REMOVE *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandRemove : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentCwd,
|
|
SentRemove
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRemove, FtpSocket, CmdNone)
|
|
|
|
TQString destinationPath;
|
|
TQString parentDirectory;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
destinationPath = socket()->getConfig("params.remove.path");
|
|
parentDirectory = socket()->getConfig("params.remove.parent");
|
|
|
|
currentState = SentRemove;
|
|
|
|
if (socket()->getConfigInt("params.remove.directory")) {
|
|
if (socket()->getCurrentDirectory() != parentDirectory) {
|
|
// We should change working directory to parent directory before removing
|
|
currentState = SentCwd;
|
|
socket()->sendCommand("CWD " + parentDirectory);
|
|
} else {
|
|
socket()->sendCommand("RMD " + destinationPath);
|
|
}
|
|
} else {
|
|
socket()->sendCommand("DELE " + destinationPath);
|
|
}
|
|
break;
|
|
}
|
|
case SentCwd: {
|
|
if (socket()->isMultiline())
|
|
return;
|
|
|
|
if (socket()->isResponse("2")) {
|
|
// CWD was successful
|
|
socket()->setCurrentDirectory(parentDirectory);
|
|
}
|
|
|
|
currentState = SentRemove;
|
|
socket()->sendCommand("RMD " + destinationPath);
|
|
break;
|
|
}
|
|
case SentRemove: {
|
|
if (socket()->isMultiline())
|
|
return;
|
|
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
// Invalidate cached parent entry (if any)
|
|
Cache::self()->invalidateEntry(socket(), parentDirectory);
|
|
Cache::self()->invalidatePath(socket(), destinationPath);
|
|
|
|
if (!socket()->isChained())
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoRemove(const KURL &path)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Removing..."));
|
|
|
|
// Set the file to remove
|
|
setConfig("params.remove.parent", path.directory());
|
|
setConfig("params.remove.path", path.path());
|
|
|
|
activateCommandClass(FtpCommandRemove);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// **************************************** RENAME *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandRename : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentRnfr,
|
|
SentRnto
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRename, FtpSocket, CmdRename)
|
|
|
|
TQString sourcePath;
|
|
TQString destinationPath;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
sourcePath = socket()->getConfig("params.rename.source");
|
|
destinationPath = socket()->getConfig("params.rename.destination");
|
|
|
|
currentState = SentRnfr;
|
|
socket()->sendCommand("RNFR " + sourcePath);
|
|
break;
|
|
}
|
|
case SentRnfr: {
|
|
if (socket()->isResponse("3")) {
|
|
currentState = SentRnto;
|
|
socket()->sendCommand("RNTO " + destinationPath);
|
|
} else
|
|
socket()->resetCommandClass(Failed);
|
|
break;
|
|
}
|
|
case SentRnto: {
|
|
if (socket()->isResponse("2")) {
|
|
// Invalidate cached parent entry (if any)
|
|
Cache::self()->invalidateEntry(socket(), KURL(sourcePath).directory());
|
|
Cache::self()->invalidateEntry(socket(), KURL(destinationPath).directory());
|
|
|
|
Cache::self()->invalidatePath(socket(), sourcePath);
|
|
Cache::self()->invalidatePath(socket(), destinationPath);
|
|
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
} else
|
|
socket()->resetCommandClass(Failed);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoRename(const KURL &source, const KURL &destination)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Renaming..."));
|
|
|
|
// Set rename options
|
|
setConfig("params.rename.source", source.path());
|
|
setConfig("params.rename.destination", destination.path());
|
|
|
|
activateCommandClass(FtpCommandRename);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// **************************************** CHMOD ********************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandChmod : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentChmod
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandChmod, FtpSocket, CmdChmod)
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
currentState = SentChmod;
|
|
|
|
TQString chmod;
|
|
chmod.sprintf("SITE CHMOD %.3d %s", socket()->getConfigInt("params.chmod.mode"),
|
|
socket()->getConfig("params.chmod.path").ascii());
|
|
socket()->sendCommand(chmod);
|
|
break;
|
|
}
|
|
case SentChmod: {
|
|
if (!socket()->isResponse("2"))
|
|
socket()->resetCommandClass(Failed);
|
|
else {
|
|
// Invalidate cached parent entry (if any)
|
|
Cache::self()->invalidateEntry(socket(), KURL(socket()->getConfig("params.chmod.path")).directory());
|
|
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoChmodSingle(const KURL &path, int mode)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Changing mode..."));
|
|
|
|
// Set chmod options
|
|
setConfig("params.chmod.path", path.path());
|
|
setConfig("params.chmod.mode", mode);
|
|
|
|
activateCommandClass(FtpCommandChmod);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// **************************************** MKDIR ********************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandMkdir : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentMkdir
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandMkdir, FtpSocket, CmdMkdir)
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
currentState = SentMkdir;
|
|
socket()->changeWorkingDirectory(socket()->getConfig("params.mkdir.path"), true);
|
|
break;
|
|
}
|
|
case SentMkdir: {
|
|
// Invalidate cached parent entry (if any)
|
|
Cache::self()->invalidateEntry(socket(), KURL(socket()->getCurrentDirectory()).directory());
|
|
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoMkdir(const KURL &path)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Making directory..."));
|
|
|
|
setConfig("params.mkdir.path", path.path());
|
|
activateCommandClass(FtpCommandMkdir);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* RAW *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandRaw : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentRaw
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRaw, FtpSocket, CmdRaw)
|
|
|
|
TQString response;
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
currentState = SentRaw;
|
|
socket()->sendCommand(socket()->getConfig("params.raw.command"));
|
|
break;
|
|
}
|
|
case SentRaw: {
|
|
response.append(socket()->getResponse());
|
|
|
|
if (!socket()->isMultiline()) {
|
|
socket()->emitEvent(Event::EventRaw, response);
|
|
socket()->resetCommandClass();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoRaw(const TQString &raw)
|
|
{
|
|
setConfig("params.raw.command", raw);
|
|
activateCommandClass(FtpCommandRaw);
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* FXP *******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandFxp : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
|
|
// Source socket
|
|
SourceSentCwd,
|
|
SourceSentStat,
|
|
SourceDestVerified,
|
|
SourceSentType,
|
|
SourceSentSscn,
|
|
SourceSentProt,
|
|
SourceWaitType,
|
|
SourceSentPret,
|
|
SourceSentPasv,
|
|
SourceDoRest,
|
|
SourceSentRest,
|
|
SourceDoRetr,
|
|
SourceSentRetr,
|
|
SourceWaitTransfer,
|
|
SourceResetProt,
|
|
|
|
// Destination socket
|
|
DestSentStat,
|
|
DestWaitCwd,
|
|
DestDoType,
|
|
DestSentType,
|
|
DestSentSscn,
|
|
DestSentProt,
|
|
DestDoPort,
|
|
DestSentPort,
|
|
DestSentRest,
|
|
DestDoStor,
|
|
DestSentStor,
|
|
DestWaitTransfer,
|
|
DestResetProt
|
|
};
|
|
|
|
enum ProtectionMode {
|
|
ProtClear = 0,
|
|
ProtPrivate = 1,
|
|
ProtSSCN = 2
|
|
};
|
|
|
|
enum TransferMode {
|
|
TransferPASV = 0,
|
|
TransferCPSV = 1
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandFxp, FtpSocket, CmdFxp)
|
|
|
|
FtpSocket *companion;
|
|
|
|
KURL sourceFile;
|
|
KURL destinationFile;
|
|
filesize_t resumeOffset;
|
|
|
|
void cleanup()
|
|
{
|
|
// We have been interrupted, so we have to abort the companion as well
|
|
if (!socket()->getConfigInt("params.fxp.abort")) {
|
|
companion->setConfig("params.fxp.abort", 1);
|
|
companion->protoAbort();
|
|
}
|
|
|
|
// Unclean upload termination, be sure to erase the cached stat infos
|
|
if (!socket()->getConfigInt("params.fxp.keep_cache"))
|
|
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
|
|
}
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
sourceFile.setPath(socket()->getConfig("params.fxp.source"));
|
|
destinationFile.setPath(socket()->getConfig("params.fxp.destination"));
|
|
socket()->setConfig("params.fxp.keep_cache", 0);
|
|
|
|
// Who are we ? Where shall we begin ?
|
|
if (socket()->getConfigInt("params.fxp.companion")) {
|
|
// We are the companion, so we should check the destination
|
|
socket()->setConfig("params.fxp.companion", 0);
|
|
|
|
currentState = DestSentStat;
|
|
socket()->protoStat(destinationFile);
|
|
return;
|
|
} else {
|
|
socket()->setConfig("params.transfer.mode", TransferPASV);
|
|
|
|
if (socket()->getCurrentDirectory() != sourceFile.directory()) {
|
|
// Attempt to CWD to the parent directory
|
|
currentState = SourceSentCwd;
|
|
socket()->sendCommand("CWD " + sourceFile.directory());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
// ***************************** Source socket *******************************
|
|
// ***************************************************************************
|
|
case SourceSentCwd: {
|
|
if (currentState == SourceSentCwd) {
|
|
if (!socket()->isResponse("250")) {
|
|
socket()->emitError(FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
|
|
if (socket()->isMultiline())
|
|
return;
|
|
else
|
|
socket()->setCurrentDirectory(sourceFile.directory());
|
|
}
|
|
|
|
// We are the source socket, let's stat
|
|
currentState = SourceSentStat;
|
|
socket()->protoStat(sourceFile);
|
|
break;
|
|
}
|
|
case SourceSentStat: {
|
|
if (socket()->getStatResponse().filename().isEmpty()) {
|
|
socket()->emitError(FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
// File exists, invoke the companion
|
|
companion->setConfig("params.fxp.companion", 1);
|
|
companion->thread()->siteToSite(socket()->thread(), sourceFile, destinationFile);
|
|
currentState = SourceDestVerified;
|
|
}
|
|
break;
|
|
}
|
|
case SourceDestVerified: {
|
|
if (isWakeup()) {
|
|
// We have been waken up because a decision has been made
|
|
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
|
|
|
|
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
|
|
event->action = FileExistsWakeupEvent::Overwrite;
|
|
|
|
switch (event->action) {
|
|
case FileExistsWakeupEvent::Rename: {
|
|
// Change the destination filename, otherwise it is the same as overwrite
|
|
destinationFile.setPath(event->newFileName);
|
|
}
|
|
case FileExistsWakeupEvent::Overwrite: {
|
|
companion->setConfig("params.fxp.rest", 0);
|
|
resumeOffset = 0;
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Resume: {
|
|
companion->setConfig("params.fxp.rest", companion->getStatResponse().size());
|
|
resumeOffset = companion->getStatResponse().size();
|
|
break;
|
|
}
|
|
case FileExistsWakeupEvent::Skip: {
|
|
// Transfer should be aborted
|
|
companion->setConfig("params.fxp.keep_cache", 1);
|
|
socket()->setConfig("params.fxp.keep_cache", 1);
|
|
|
|
socket()->resetCommandClass(UserAbort);
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
companion->setConfig("params.fxp.rest", 0);
|
|
resumeOffset = 0;
|
|
}
|
|
|
|
// Change type
|
|
currentState = SourceSentType;
|
|
|
|
TQString type = "TYPE ";
|
|
type.append(KFTPCore::Config::self()->ftpMode(sourceFile.path()));
|
|
socket()->sendCommand(type);
|
|
break;
|
|
}
|
|
case SourceSentType: {
|
|
if (socket()->getConfigInt("ssl") && socket()->getConfigInt("ssl.prot_mode") != 2 && !socket()->getConfigInt("sscn.activated")) {
|
|
if (socket()->getConfigInt("ssl.prot_mode") == 0) {
|
|
if (socket()->getConfigInt("feat.sscn")) {
|
|
// We support SSCN
|
|
currentState = SourceSentSscn;
|
|
socket()->sendCommand("SSCN ON");
|
|
companion->setConfig("params.ssl.mode", ProtPrivate);
|
|
} else if (companion->getConfigInt("feat.sscn")) {
|
|
// Companion supports SSCN
|
|
currentState = SourceWaitType;
|
|
companion->setConfig("params.ssl.mode", ProtSSCN);
|
|
companion->nextCommandAsync();
|
|
} else if (socket()->getConfigInt("feat.cpsv")) {
|
|
// We support CPSV
|
|
currentState = SourceWaitType;
|
|
socket()->setConfig("params.transfer.mode", TransferCPSV);
|
|
companion->setConfig("params.ssl.mode", ProtPrivate);
|
|
companion->nextCommandAsync();
|
|
} else {
|
|
// Neither support SSCN, can't do SSL transfer
|
|
socket()->emitEvent(Event::EventMessage, i18n("Neither server supports SSCN/CPSV but SSL data connection requested, aborting transfer!"));
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
}
|
|
} else {
|
|
currentState = SourceSentProt;
|
|
socket()->sendCommand("PROT C");
|
|
companion->setConfig("params.ssl.mode", ProtClear);
|
|
}
|
|
} else {
|
|
currentState = SourceWaitType;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case SourceSentSscn: {
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("sscn.activated", 1);
|
|
socket()->setConfig("params.fxp.changed_prot", 0);
|
|
|
|
currentState = SourceWaitType;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case SourceSentProt: {
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("params.fxp.changed_prot", 1);
|
|
|
|
currentState = SourceWaitType;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case SourceWaitType: {
|
|
// We are ready to invoke file transfer, do PASV
|
|
if (socket()->getConfigInt("feat.pret")) {
|
|
currentState = SourceSentPret;
|
|
socket()->sendCommand("PRET RETR " + sourceFile.filename());
|
|
} else {
|
|
currentState = SourceSentPasv;
|
|
|
|
switch (socket()->getConfigInt("params.transfer.mode")) {
|
|
case TransferPASV: socket()->sendCommand("PASV"); break;
|
|
case TransferCPSV: socket()->sendCommand("CPSV"); break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SourceSentPret: {
|
|
if (!socket()->isResponse("2")) {
|
|
if (socket()->isResponse("550")) {
|
|
socket()->emitError(PermissionDenied);
|
|
socket()->resetCommandClass(Failed);
|
|
return;
|
|
} else if (socket()->isResponse("530")) {
|
|
socket()->emitError(FileNotFound);
|
|
socket()->resetCommandClass(Failed);
|
|
}
|
|
|
|
socket()->setConfig("feat.pret", 0);
|
|
}
|
|
|
|
currentState = SourceSentPasv;
|
|
|
|
switch (socket()->getConfigInt("params.transfer.mode")) {
|
|
case TransferPASV: socket()->sendCommand("PASV"); break;
|
|
case TransferCPSV: socket()->sendCommand("CPSV"); break;
|
|
}
|
|
break;
|
|
}
|
|
case SourceSentPasv: {
|
|
// Parse the PASV response and get it to the companion to issue PORT
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
TQString tmp = socket()->getResponse();
|
|
int pos = tmp.find('(') + 1;
|
|
tmp = tmp.mid(pos, tmp.find(')') - pos);
|
|
|
|
currentState = SourceDoRest;
|
|
companion->setConfig("params.fxp.ip", tmp);
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case SourceDoRest: {
|
|
currentState = SourceSentRest;
|
|
socket()->sendCommand("REST " + TQString::number(resumeOffset));
|
|
break;
|
|
}
|
|
case SourceSentRest: {
|
|
if (!socket()->isResponse("2") && !socket()->isResponse("3")) {
|
|
socket()->setConfig("feat.rest", 0);
|
|
companion->setConfig("params.fxp.rest", 0);
|
|
} else {
|
|
// Signal resume
|
|
socket()->emitEvent(Event::EventResumeOffset, resumeOffset);
|
|
}
|
|
|
|
currentState = SourceDoRetr;
|
|
companion->nextCommandAsync();
|
|
break;
|
|
}
|
|
case SourceDoRetr: {
|
|
currentState = SourceSentRetr;
|
|
socket()->sendCommand("RETR " + sourceFile.filename());
|
|
break;
|
|
}
|
|
case SourceSentRetr: {
|
|
if (!socket()->isResponse("1")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
currentState = SourceWaitTransfer;
|
|
}
|
|
break;
|
|
}
|
|
case SourceWaitTransfer: {
|
|
if (!socket()->isMultiline()) {
|
|
// Transfer has been completed
|
|
if (socket()->getConfigInt("params.fxp.changed_prot")) {
|
|
currentState = SourceResetProt;
|
|
|
|
TQString prot = "PROT ";
|
|
|
|
if (socket()->getConfigInt("ssl.prot_mode") == 0)
|
|
prot.append('P');
|
|
else
|
|
prot.append('C');
|
|
|
|
socket()->sendCommand(prot);
|
|
} else {
|
|
markClean();
|
|
|
|
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
socket()->resetCommandClass();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SourceResetProt: {
|
|
markClean();
|
|
|
|
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
|
|
socket()->emitEvent(Event::EventTransferComplete);
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
// *************************** Destination socket ****************************
|
|
// ***************************************************************************
|
|
case DestSentStat: {
|
|
if (socket()->getStatResponse().filename().isEmpty()) {
|
|
// Change the working directory
|
|
currentState = DestWaitCwd;
|
|
socket()->changeWorkingDirectory(destinationFile.directory(), true);
|
|
} else {
|
|
// The file already exists, request action
|
|
DirectoryListing list;
|
|
list.addEntry(companion->getStatResponse());
|
|
list.addEntry(socket()->getStatResponse());
|
|
|
|
currentState = DestDoType;
|
|
socket()->emitEvent(Event::EventFileExists, list);
|
|
}
|
|
break;
|
|
}
|
|
case DestWaitCwd: {
|
|
// Directory has been changed/created, call back the companion
|
|
currentState = DestDoType;
|
|
companion->nextCommandAsync();
|
|
break;
|
|
}
|
|
case DestDoType: {
|
|
currentState = DestSentType;
|
|
|
|
TQString type = "TYPE ";
|
|
type.append(KFTPCore::Config::self()->ftpMode(sourceFile.path()));
|
|
socket()->sendCommand(type);
|
|
break;
|
|
}
|
|
case DestSentType: {
|
|
if (socket()->getConfigInt("ssl")) {
|
|
// Check what the source socket has instructed us to do
|
|
switch (socket()->getConfigInt("params.ssl.mode")) {
|
|
case ProtClear: {
|
|
// We should use cleartext data channel
|
|
if (socket()->getConfigInt("ssl.prot_mode") != 2) {
|
|
currentState = DestSentProt;
|
|
socket()->sendCommand("PROT C");
|
|
} else {
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case ProtPrivate: {
|
|
// We should use private data channel
|
|
if (socket()->getConfigInt("ssl.prot_mode") != 0) {
|
|
currentState = DestSentProt;
|
|
socket()->sendCommand("PROT P");
|
|
} else {
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case ProtSSCN: {
|
|
// We should initialize SSCN mode
|
|
if (!socket()->getConfigInt("sscn.activated")) {
|
|
currentState = DestSentSscn;
|
|
socket()->sendCommand("SSCN ON");
|
|
} else {
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case DestSentSscn: {
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("sscn.activated", 1);
|
|
socket()->setConfig("params.fxp.changed_prot", 0);
|
|
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case DestSentProt: {
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
socket()->setConfig("params.fxp.changed_prot", 1);
|
|
|
|
currentState = DestDoPort;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case DestDoPort: {
|
|
currentState = DestSentPort;
|
|
socket()->sendCommand("PORT " + socket()->getConfig("params.fxp.ip"));
|
|
break;
|
|
}
|
|
case DestSentPort: {
|
|
if (!socket()->isResponse("2")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
currentState = DestSentRest;
|
|
socket()->sendCommand("REST " + socket()->getConfig("params.fxp.rest"));
|
|
}
|
|
break;
|
|
}
|
|
case DestSentRest: {
|
|
// We are ready for file transfer
|
|
currentState = DestDoStor;
|
|
companion->nextCommandAsync();
|
|
break;
|
|
}
|
|
case DestDoStor: {
|
|
currentState = DestSentStor;
|
|
socket()->sendCommand("STOR " + destinationFile.filename());
|
|
break;
|
|
}
|
|
case DestSentStor: {
|
|
if (!socket()->isResponse("1")) {
|
|
socket()->resetCommandClass(Failed);
|
|
} else {
|
|
currentState = DestWaitTransfer;
|
|
companion->nextCommandAsync();
|
|
}
|
|
break;
|
|
}
|
|
case DestWaitTransfer: {
|
|
if (!socket()->isMultiline()) {
|
|
// Transfer has been completed
|
|
if (socket()->getConfigInt("params.fxp.changed_prot")) {
|
|
currentState = DestResetProt;
|
|
|
|
TQString prot = "PROT ";
|
|
|
|
if (socket()->getConfigInt("ssl.prot_mode") == 0)
|
|
prot.append('P');
|
|
else
|
|
prot.append('C');
|
|
|
|
socket()->sendCommand(prot);
|
|
} else {
|
|
markClean();
|
|
|
|
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case DestResetProt: {
|
|
markClean();
|
|
|
|
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
|
|
socket()->emitEvent(Event::EventReloadNeeded);
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoSiteToSite(Socket *socket, const KURL &source, const KURL &destination)
|
|
{
|
|
emitEvent(Event::EventState, i18n("Transfering..."));
|
|
emitEvent(Event::EventMessage, i18n("Transfering file '%1'...").arg(source.fileName()));
|
|
|
|
// Set the source and destination
|
|
setConfig("params.fxp.abort", 0);
|
|
setConfig("params.fxp.source", source.path());
|
|
setConfig("params.fxp.destination", destination.path());
|
|
|
|
FtpCommandFxp *fxp = new FtpCommandFxp(this);
|
|
fxp->companion = static_cast<FtpSocket*>(socket);
|
|
m_cmdData = fxp;
|
|
m_cmdData->process();
|
|
}
|
|
|
|
// *******************************************************************************************
|
|
// ******************************************* NOOP ******************************************
|
|
// *******************************************************************************************
|
|
|
|
class FtpCommandKeepAlive : public Commands::Base {
|
|
public:
|
|
enum State {
|
|
None,
|
|
SentNoop
|
|
};
|
|
|
|
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandKeepAlive, FtpSocket, CmdKeepAlive)
|
|
|
|
void process()
|
|
{
|
|
switch (currentState) {
|
|
case None: {
|
|
currentState = SentNoop;
|
|
socket()->sendCommand("NOOP");
|
|
break;
|
|
}
|
|
case SentNoop: {
|
|
socket()->resetCommandClass();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FtpSocket::protoKeepAlive()
|
|
{
|
|
emitEvent(Event::EventState, i18n("Transmitting keep-alive..."));
|
|
setCurrentCommand(Commands::CmdKeepAlive);
|
|
activateCommandClass(FtpCommandKeepAlive);
|
|
}
|
|
|
|
}
|
|
|
|
#include "ftpsocket.moc"
|