diff options
author | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2013-07-24 15:21:35 -0500 |
---|---|---|
committer | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2013-07-24 15:21:35 -0500 |
commit | 093ed975800ab1e5c0d73759f07fedf8d5aa2ca6 (patch) | |
tree | d625ba687b185a3984a410f56d9c2e0dade7b114 /kftpgrabber/src/engine/socket.cpp | |
download | kftpgrabber-093ed975800ab1e5c0d73759f07fedf8d5aa2ca6.tar.gz kftpgrabber-093ed975800ab1e5c0d73759f07fedf8d5aa2ca6.zip |
Initial import of kftpgrabber 0.8.1 from sources
Diffstat (limited to 'kftpgrabber/src/engine/socket.cpp')
-rw-r--r-- | kftpgrabber/src/engine/socket.cpp | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/kftpgrabber/src/engine/socket.cpp b/kftpgrabber/src/engine/socket.cpp new file mode 100644 index 0000000..370de1b --- /dev/null +++ b/kftpgrabber/src/engine/socket.cpp @@ -0,0 +1,866 @@ +/* + * 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 "socket.h" +#include "thread.h" +#include "connectionretry.h" +#include "speedlimiter.h" +#include "cache.h" + +#include "misc/config.h" + +#include <klocale.h> + +namespace KFTPEngine { + +Socket::Socket(Thread *thread, const QString &protocol) + : m_remoteEncoding(new KRemoteEncoding()), + m_cmdData(0), + m_thread(thread), + m_transferBytes(0), + m_speedLastTime(0), + m_speedLastBytes(0), + m_protocol(protocol), + m_currentCommand(Commands::CmdNone), + m_errorReporting(true), + m_shouldAbort(false) +{ + m_commandChain.setAutoDelete(true); +} + +Socket::~Socket() +{ + delete m_remoteEncoding; + + if (m_connectionRetry) + delete m_connectionRetry; +} + +void Socket::initConfig() +{ + m_config.clear(); + + // Fill in some default values + setConfig("feat.epsv", 1); + setConfig("feat.eprt", 1); + setConfig("feat.pasv", 1); + setConfig("feat.size", 1); + setConfig("ssl.prot_mode", 2); + setConfig("keepalive.enabled", 1); + setConfig("keepalive.timeout", 60); +} + +void Socket::emitError(ErrorCode code, const QString ¶m1) +{ + // Intercept connect and login errors and pass them on to the ConnectionRetry class (if enabled) + if (getConfigInt("retry") && (code == ConnectFailed || code == LoginFailed)) { + if (!m_connectionRetry) + m_connectionRetry = new ConnectionRetry(this); + + m_connectionRetry->startRetry(); + return; + } + + QValueList<EventParameter> params; + params.append(EventParameter(code)); + params.append(EventParameter(param1)); + + // Dispatch the event via socket thread + m_thread->event(Event::EventError, params); +} + +void Socket::emitEvent(Event::Type type, const QString ¶m1, const QString ¶m2) +{ + QValueList<EventParameter> params; + params.append(EventParameter(param1)); + params.append(EventParameter(param2)); + + // Dispatch the event via socket thread + m_thread->event(type, params); +} + +void Socket::emitEvent(Event::Type type, DirectoryListing param1) +{ + QValueList<EventParameter> params; + params.append(EventParameter(param1)); + + // Dispatch the event via socket thread + m_thread->event(type, params); +} + +void Socket::emitEvent(Event::Type type, filesize_t param1) +{ + QValueList<EventParameter> params; + params.append(EventParameter(param1)); + + // Dispatch the event via socket thread + m_thread->event(type, params); +} + +void Socket::emitEvent(Event::Type type, void *param1) +{ + QValueList<EventParameter> params; + params.append(EventParameter(param1)); + + // Dispatch the event via socket thread + m_thread->event(type, params); +} + +void Socket::changeEncoding(const QString &encoding) +{ + // Alter encoding and change socket config + m_remoteEncoding->setEncoding(encoding.ascii()); + setConfig("encoding", encoding); +} + +void Socket::protoDisconnect() +{ + resetCommandClass(UserAbort); + + emitEvent(Event::EventMessage, i18n("Disconnected.")); + emitEvent(Event::EventDisconnect); +} + +void Socket::timeoutWait(bool start) +{ + if (start) { + m_timeoutCounter.start(); + } else { + m_timeoutCounter = QTime(); + } +} + +void Socket::timeoutPing() +{ + m_timeoutCounter.restart(); +} + +void Socket::timeoutCheck() +{ + if (!isConnected()) + return; + + if (!m_timeoutCounter.isNull()) { + Commands::Type command = getCurrentCommand(); + int timeout = 0; + + // Ignore timeouts for FXP transfers, since there is no way to do pings + if (command == Commands::CmdFxp) + return; + + if (command == Commands::CmdGet || command == Commands::CmdPut) + timeout = KFTPCore::Config::dataTimeout(); + else + timeout = KFTPCore::Config::controlTimeout(); + + if (timeout > 0 && m_timeoutCounter.elapsed() > (timeout * 1000)) { + timeoutWait(false); + + // We have a timeout, let's abort + emitEvent(Event::EventMessage, i18n("Connection timed out.")); + protoDisconnect(); + } + } +} + +void Socket::keepaliveStart() +{ + m_keepaliveCounter.start(); +} + +void Socket::keepaliveCheck() +{ + // Ignore keepalive if the socket is busy + if (isBusy() || !isConnected()) { + m_keepaliveCounter.restart(); + return; + } + + if (getConfigInt("keepalive.enabled") && m_keepaliveCounter.elapsed() > getConfigInt("keepalive.timeout") * 1000) { + protoKeepAlive(); + + // Reset the counter + m_keepaliveCounter.restart(); + } +} + +Commands::Type Socket::getCurrentCommand() +{ + if (m_commandChain.count() > 0) { + QPtrList<Commands::Base>::iterator chainEnd = m_commandChain.end(); + for (QPtrList<Commands::Base>::iterator i = m_commandChain.begin(); i != chainEnd; i++) { + if ((*i)->command() != Commands::CmdNone) + return (*i)->command(); + } + } + + return m_currentCommand; +} + +Commands::Type Socket::getToplevelCommand() +{ + return m_currentCommand; +} + +Commands::Type Socket::getPreviousCommand() +{ + if (!isChained()) + return Commands::CmdNone; + + if (m_commandChain.count() > 1) { + Commands::Base *previous = m_commandChain.prev(); + m_commandChain.next(); + + return previous->command(); + } else { + return m_currentCommand; + } +} + +void Socket::resetCommandClass(ResetCode code) +{ + if (m_commandChain.count() > 0) { + Commands::Base *current = m_commandChain.current(); + + if (current->isProcessing()) { + current->autoDestruct(code); + return; + } else { + if (!current->isClean()) + current->cleanup(); + + m_commandChain.remove(); + } + + if (code == Ok) { + nextCommandAsync(); + } else { + // Command has completed with an error code. We should abort the + // complete chain. + resetCommandClass(code); + } + } else { + if (m_cmdData) { + if (m_cmdData->isProcessing()) { + m_cmdData->autoDestruct(code); + return; + } else { + if (!m_cmdData->isClean()) + m_cmdData->cleanup(); + + delete m_cmdData; + m_cmdData = 0; + } + } + + if (code == Failed) + emitError(OperationFailed); + + // Reset current command and emit a ready event + if (getCurrentCommand() != Commands::CmdConnectRetry) { + setCurrentCommand(Commands::CmdNone); + emitEvent(Event::EventReady); + emitEvent(Event::EventState, i18n("Idle.")); + } + + setErrorReporting(true); + m_shouldAbort = false; + } +} + +void Socket::nextCommand() +{ + if (m_commandChain.count() > 0) { + Commands::Base *current = m_commandChain.current(); + + current->setProcessing(true); + current->process(); + current->setProcessing(false); + + if (current->isDestructable()) + resetCommandClass(current->resetCode()); + } else if (m_cmdData) { + m_cmdData->setProcessing(true); + m_cmdData->process(); + m_cmdData->setProcessing(false); + + if (m_cmdData->isDestructable()) + resetCommandClass(m_cmdData->resetCode()); + } +} + +void Socket::nextCommandAsync() +{ + m_thread->m_commandQueue.append(Commands::CmdNext); +} + +void Socket::wakeup(WakeupEvent *event) +{ + if (m_commandChain.count() > 0) { + Commands::Base *current = m_commandChain.current(); + + if (current->isProcessing()) { + qDebug("WARNING: Attempted to wakeup a processing socket!"); + return; + } + + current->setProcessing(true); + current->wakeup(event); + current->setProcessing(false); + + if (current->isDestructable()) + resetCommandClass(current->resetCode()); + } else if (m_cmdData) { + if (m_cmdData->isProcessing()) { + qDebug("WARNING: Attempted to wakeup a processing socket!"); + return; + } + + m_cmdData->setProcessing(true); + m_cmdData->wakeup(event); + m_cmdData->setProcessing(false); + + if (m_cmdData->isDestructable()) + resetCommandClass(m_cmdData->resetCode()); + } +} + +filesize_t Socket::getTransferSpeed() +{ + time_t timeDelta = time(0) - m_speedLastTime; + + if (timeDelta == 0) + return 0; + + if (m_speedLastBytes > m_transferBytes) + m_speedLastBytes = 0; + + filesize_t speed = (m_transferBytes - m_speedLastBytes)/(time(0) - m_speedLastTime); + + m_speedLastBytes = m_transferBytes; + m_speedLastTime = time(0); + + return speed; +} + +void Socket::protoAbort() +{ + m_shouldAbort = true; + + if (m_connectionRetry && !m_cmdData) + m_connectionRetry->abortRetry(); +} + +// ******************************************************************************************* +// ******************************************* STAT ****************************************** +// ******************************************************************************************* + +class FtpCommandStat : public Commands::Base { +public: + enum State { + None, + WaitList + }; + + ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandStat, Socket, CmdNone) + + KURL path; + + void process() + { + switch (currentState) { + case None: { + // Issue a list of the parent directory + currentState = WaitList; + socket()->setErrorReporting(false); + socket()->protoList(path.directory()); + break; + } + case WaitList: { + // Now just extract what we need + QValueList<DirectoryEntry> list = socket()->getLastDirectoryListing().list(); + QValueList<DirectoryEntry>::iterator listEnd = list.end(); + for (QValueList<DirectoryEntry>::iterator i = list.begin(); i != listEnd; i++) { + if ((*i).filename() == path.fileName()) { + socket()->m_lastStatResponse = *i; + socket()->resetCommandClass(); + return; + } + } + + // We found no such file + socket()->m_lastStatResponse = DirectoryEntry(); + socket()->resetCommandClass(); + break; + } + } + } +}; + +void Socket::protoStat(const KURL &path) +{ + // Lookup the cache first and don't even try to list if cached + DirectoryListing cached = Cache::self()->findCached(this, path.directory()); + if (cached.isValid()) { + QValueList<DirectoryEntry> list = cached.list(); + QValueList<DirectoryEntry>::iterator listEnd = list.end(); + for (QValueList<DirectoryEntry>::iterator i = list.begin(); i != listEnd; i++) { + if ((*i).filename() == path.fileName()) { + m_lastStatResponse = *i; + nextCommandAsync(); + return; + } + } + + // Cached is valid but file can't be found + m_lastStatResponse = DirectoryEntry(); + nextCommandAsync(); + return; + } + + // Not cached, let's do a real listing + FtpCommandStat *stat = new FtpCommandStat(this); + stat->path = path; + addToCommandChain(stat); + nextCommand(); +} + +// ******************************************************************************************* +// ****************************************** SCAN ******************************************* +// ******************************************************************************************* + +class FtpCommandScan : public Commands::Base { +public: + enum State { + None, + SentList, + ProcessList, + ScannedDir + }; + + ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandScan, Socket, CmdNone) + + QValueList<DirectoryEntry> currentList; + QValueList<DirectoryEntry>::const_iterator currentEntry; + + QString currentDirectory; + DirectoryTree *currentTree; + + void cleanup() + { + // We didn't emit the tree, so we should free it + if (!socket()->isChained()) + delete currentTree; + } + + void process() + { + // NOTE: The missing breaks are mising for a purpuse! Do not dare to add them ;) + switch (currentState) { + case None: { + // We would like to disable error reporting + socket()->setErrorReporting(false); + + // Issue a directory listing on the given URL + currentState = SentList; + socket()->protoList(currentDirectory); + break; + } + case SentList: { + currentList = socket()->getLastDirectoryListing().list(); + qHeapSort(currentList); + + currentEntry = currentList.begin(); + currentState = ProcessList; + + // Empty listing, we are done + if (currentEntry == currentList.end()) { + if (socket()->isChained()) { + socket()->resetCommandClass(); + } else { + // We are the toplevel scan command + markClean(); + + socket()->emitEvent(Event::EventScanComplete, currentTree); + socket()->emitEvent(Event::EventMessage, i18n("Scan complete.")); + socket()->resetCommandClass(); + } + + return; + } + } + case ProcessList: { + if ((*currentEntry).isDirectory()) { + // A directory entry + DirectoryTree *tree = currentTree->addDirectory(*currentEntry); + currentState = ScannedDir; + + FtpCommandScan *scan = new FtpCommandScan(socket()); + scan->currentDirectory = currentDirectory + "/" + (*currentEntry).filename(); + scan->currentTree = tree; + socket()->addToCommandChain(scan); + socket()->nextCommandAsync(); + return; + } else { + // A file entry + currentTree->addFile(*currentEntry); + } + } + case ScannedDir: { + currentState = ProcessList; + + if (++currentEntry == currentList.end()) { + // We are done + if (socket()->isChained()) { + socket()->resetCommandClass(); + } else { + // We are the toplevel scan command + markClean(); + + socket()->emitEvent(Event::EventScanComplete, currentTree); + socket()->emitEvent(Event::EventMessage, i18n("Scan complete.")); + socket()->resetCommandClass(); + } + } else { + socket()->nextCommandAsync(); + } + break; + } + } + } +}; + +void Socket::protoScan(const KURL &path) +{ + emitEvent(Event::EventMessage, i18n("Starting recursive directory scan...")); + + // We have to create a new command class manually, since we need to set the + // currentTree parameter + FtpCommandScan *scan = new FtpCommandScan(this); + scan->currentDirectory = path.path(); + scan->currentTree = new DirectoryTree(DirectoryEntry()); + m_cmdData = scan; + m_cmdData->process(); +} + +// ******************************************************************************************* +// ***************************************** DELETE ****************************************** +// ******************************************************************************************* + +class FtpCommandDelete : public Commands::Base { +public: + enum State { + None, + VerifyDir, + SimpleRemove, + SentList, + ProcessList, + DeletedDir, + DeletedFile + }; + + ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandDelete, Socket, CmdDelete) + + QValueList<DirectoryEntry> currentList; + QValueList<DirectoryEntry>::const_iterator currentEntry; + + KURL destinationPath; + + void process() + { + switch (currentState) { + case None: { + // We have to determine if the destination is a file or a directory + // TODO use cached information + if (socket()->isChained()) { + // We know that it is a directory + currentState = SentList; + socket()->protoList(destinationPath); + } else { + currentState = VerifyDir; + socket()->protoStat(destinationPath); + } + break; + } + case VerifyDir: { + DirectoryEntry entry = socket()->getStatResponse(); + + if (entry.filename().isEmpty()) { + // The file doesn't exist, abort + socket()->resetCommandClass(Failed); + } else { + if (entry.isDirectory()) { + // It is a directory, remove recursively + currentState = SentList; + socket()->protoList(destinationPath); + } else { + // A single file, a simple remove + currentState = SimpleRemove; + socket()->setConfig("params.remove.directory", 0); + socket()->protoRemove(destinationPath); + } + } + break; + } + case SimpleRemove: { + if (!socket()->isChained()) + socket()->emitEvent(Event::EventReloadNeeded); + socket()->resetCommandClass(); + break; + } + case SentList: { + currentList = socket()->getLastDirectoryListing().list(); + currentEntry = currentList.begin(); + currentState = ProcessList; + + // Empty listing, we are done + if (currentEntry == currentList.end()) { + if (socket()->isChained()) + socket()->resetCommandClass(); + else { + // We are the top level command class, remove the destination dir + currentState = SimpleRemove; + socket()->setConfig("params.remove.directory", 1); + socket()->protoRemove(destinationPath); + } + + return; + } + } + case ProcessList: { + KURL childPath = destinationPath; + childPath.addPath((*currentEntry).filename()); + + if ((*currentEntry).isDirectory()) { + // A directory, chain another delete command + currentState = DeletedDir; + + // Chain manually, since we need to set some parameters + FtpCommandDelete *del = new FtpCommandDelete(socket()); + del->destinationPath = childPath; + socket()->addToCommandChain(del); + socket()->nextCommand(); + } else { + // A file entry - remove + currentState = DeletedFile; + socket()->setConfig("params.remove.directory", 0); + socket()->protoRemove(childPath); + } + break; + } + case DeletedDir: { + // We have to remove the empty directory + KURL childPath = destinationPath; + childPath.addPath((*currentEntry).filename()); + + currentState = DeletedFile; + socket()->setConfig("params.remove.directory", 1); + socket()->protoRemove(childPath); + break; + } + case DeletedFile: { + currentState = ProcessList; + + if (++currentEntry == currentList.end()) { + if (socket()->isChained()) + socket()->resetCommandClass(); + else { + // We are the top level command class, remove the destination dir + currentState = SimpleRemove; + socket()->setConfig("params.remove.directory", 1); + socket()->protoRemove(destinationPath); + } + } else + socket()->nextCommand(); + break; + } + } + } +}; + +void Socket::protoDelete(const KURL &path) +{ + // We have to create a new command class manually to set some parameter + FtpCommandDelete *del = new FtpCommandDelete(this); + del->destinationPath = path; + m_cmdData = del; + m_cmdData->process(); +} + +// ******************************************************************************************* +// ***************************************** CHMOD ******************************************* +// ******************************************************************************************* + +class FtpCommandRecursiveChmod : public Commands::Base { +public: + enum State { + None, + VerifyDir, + SimpleChmod, + SentList, + ProcessList, + ChmodedDir, + ChmodedFile + }; + + ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRecursiveChmod, Socket, CmdChmod) + + QValueList<DirectoryEntry> currentList; + QValueList<DirectoryEntry>::const_iterator currentEntry; + + KURL destinationPath; + int mode; + + void process() + { + switch (currentState) { + case None: { + // We have to determine if the destination is a file or a directory + if (socket()->isChained()) { + // We know that it is a directory + currentState = SentList; + socket()->protoList(destinationPath); + } else { + currentState = VerifyDir; + socket()->protoStat(destinationPath); + } + break; + } + case VerifyDir: { + DirectoryEntry entry = socket()->getStatResponse(); + + if (entry.filename().isEmpty()) { + // The file doesn't exist, abort + socket()->resetCommandClass(Failed); + } else { + if (entry.isDirectory()) { + // It is a directory, chmod recursively + currentState = SentList; + socket()->protoList(destinationPath); + } else { + // A single file, a simple chmod + currentState = SimpleChmod; + socket()->protoChmodSingle(destinationPath, mode); + } + } + break; + } + case SimpleChmod: { + socket()->resetCommandClass(); + break; + } + case SentList: { + currentList = socket()->getLastDirectoryListing().list(); + currentEntry = currentList.begin(); + currentState = ProcessList; + + // Empty listing, we are done + if (currentEntry == currentList.end()) { + if (socket()->isChained()) + socket()->resetCommandClass(); + else { + // We are the top level command class, chmod the destination dir + currentState = SimpleChmod; + socket()->protoChmodSingle(destinationPath, mode); + } + + return; + } + } + case ProcessList: { + KURL childPath = destinationPath; + childPath.addPath((*currentEntry).filename()); + + if ((*currentEntry).isDirectory()) { + // A directory, chain another recursive chmod command + currentState = ChmodedDir; + + // Chain manually, since we need to set some parameters + FtpCommandRecursiveChmod *cm = new FtpCommandRecursiveChmod(socket()); + cm->destinationPath = childPath; + cm->mode = mode; + socket()->addToCommandChain(cm); + socket()->nextCommand(); + } else { + // A file entry - remove + currentState = ChmodedFile; + socket()->protoChmodSingle(childPath, mode); + } + break; + } + case ChmodedDir: { + // We have to chmod the directory + KURL childPath = destinationPath; + childPath.addPath((*currentEntry).filename()); + + currentState = ChmodedFile; + socket()->protoChmodSingle(childPath, mode); + break; + } + case ChmodedFile: { + currentState = ProcessList; + + if (++currentEntry == currentList.end()) { + if (socket()->isChained()) + socket()->resetCommandClass(); + else { + // We are the top level command class, chmod the destination dir + currentState = SimpleChmod; + socket()->protoChmodSingle(destinationPath, mode); + } + } else + socket()->nextCommand(); + break; + } + } + } +}; + +void Socket::protoChmod(const KURL &path, int mode, bool recursive) +{ + if (recursive) { + // We have to create a new command class manually to set some parameters + FtpCommandRecursiveChmod *cm = new FtpCommandRecursiveChmod(this); + cm->destinationPath = path; + cm->mode = mode; + m_cmdData = cm; + m_cmdData->process(); + } else { + // No recursive, just chmod a single file + protoChmodSingle(path, mode); + } +} + +} |