summaryrefslogtreecommitdiffstats
path: root/kftpgrabber/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'kftpgrabber/src/engine')
-rw-r--r--kftpgrabber/src/engine/Makefile.am11
-rw-r--r--kftpgrabber/src/engine/cache.cpp175
-rw-r--r--kftpgrabber/src/engine/cache.h176
-rw-r--r--kftpgrabber/src/engine/commands.cpp78
-rw-r--r--kftpgrabber/src/engine/commands.h136
-rw-r--r--kftpgrabber/src/engine/connectionretry.cpp112
-rw-r--r--kftpgrabber/src/engine/connectionretry.h86
-rw-r--r--kftpgrabber/src/engine/directorylisting.cpp188
-rw-r--r--kftpgrabber/src/engine/directorylisting.h141
-rw-r--r--kftpgrabber/src/engine/event.cpp166
-rw-r--r--kftpgrabber/src/engine/event.h372
-rw-r--r--kftpgrabber/src/engine/ftpdirectoryparser.cpp1144
-rw-r--r--kftpgrabber/src/engine/ftpdirectoryparser.h92
-rw-r--r--kftpgrabber/src/engine/ftpsocket.cpp2749
-rw-r--r--kftpgrabber/src/engine/ftpsocket.h154
-rw-r--r--kftpgrabber/src/engine/sftpsocket.cpp775
-rw-r--r--kftpgrabber/src/engine/sftpsocket.h92
-rw-r--r--kftpgrabber/src/engine/socket.cpp866
-rw-r--r--kftpgrabber/src/engine/socket.h605
-rw-r--r--kftpgrabber/src/engine/speedlimiter.cpp240
-rw-r--r--kftpgrabber/src/engine/speedlimiter.h158
-rw-r--r--kftpgrabber/src/engine/ssl.cpp264
-rw-r--r--kftpgrabber/src/engine/ssl.h176
-rw-r--r--kftpgrabber/src/engine/thread.cpp346
-rw-r--r--kftpgrabber/src/engine/thread.h133
25 files changed, 9435 insertions, 0 deletions
diff --git a/kftpgrabber/src/engine/Makefile.am b/kftpgrabber/src/engine/Makefile.am
new file mode 100644
index 0000000..ad6adb1
--- /dev/null
+++ b/kftpgrabber/src/engine/Makefile.am
@@ -0,0 +1,11 @@
+INCLUDES = -I.. -I$(srcdir)/.. \
+ -I../misc -I$(srcdir)/../misc \
+ $(all_includes)
+METASOURCES = AUTO
+noinst_LIBRARIES = libengine.a
+noinst_HEADERS = socket.h thread.h directorylisting.h commands.h event.h \
+ ftpsocket.h ftpdirectoryparser.h cache.h sftpsocket.h connectionretry.h \
+ speedlimiter.h ssl.h
+libengine_a_SOURCES = socket.cpp thread.cpp directorylisting.cpp commands.cpp \
+ event.cpp ftpsocket.cpp ftpdirectoryparser.cpp cache.cpp sftpsocket.cpp \
+ connectionretry.cpp speedlimiter.cpp ssl.cpp
diff --git a/kftpgrabber/src/engine/cache.cpp b/kftpgrabber/src/engine/cache.cpp
new file mode 100644
index 0000000..1b28dd6
--- /dev/null
+++ b/kftpgrabber/src/engine/cache.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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 "cache.h"
+#include "socket.h"
+
+#include <kstaticdeleter.h>
+
+namespace KFTPEngine {
+
+Cache *Cache::m_self = 0;
+static KStaticDeleter<Cache> staticCacheDeleter;
+
+Cache *Cache::self()
+{
+ if (!m_self) {
+ staticCacheDeleter.setObject(m_self, new Cache());
+ }
+
+ return m_self;
+}
+
+Cache::Cache()
+{
+}
+
+Cache::~Cache()
+{
+ if (m_self == this)
+ staticCacheDeleter.setObject(m_self, 0, false);
+}
+
+void Cache::addDirectory(KURL &url, DirectoryListing listing)
+{
+ url.adjustPath(-1);
+ m_listingCache[url] = listing;
+}
+
+void Cache::addDirectory(Socket *socket, DirectoryListing listing)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(socket->getCurrentDirectory());
+
+ addDirectory(url, listing);
+}
+
+void Cache::updateDirectoryEntry(Socket *socket, KURL &path, filesize_t filesize)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(path.directory());
+ url.adjustPath(-1);
+
+ if (m_listingCache.contains(url)) {
+ DirectoryListing listing = m_listingCache[url];
+ listing.updateEntry(path.fileName(), filesize);
+
+ m_listingCache.replace(url, listing);
+ }
+}
+
+void Cache::addPath(KURL &url, const QString &target)
+{
+ url.adjustPath(-1);
+ m_pathCache[url] = target;
+}
+
+void Cache::addPath(Socket *socket, const QString &target)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(socket->getCurrentDirectory());
+
+ addPath(url, target);
+}
+
+void Cache::invalidateEntry(KURL &url)
+{
+ url.adjustPath(-1);
+ m_listingCache.remove(url);
+}
+
+void Cache::invalidateEntry(Socket *socket, const QString &path)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(path);
+
+ invalidateEntry(url);
+}
+
+void Cache::invalidatePath(KURL &url)
+{
+ url.adjustPath(-1);
+ m_pathCache.remove(url);
+}
+
+void Cache::invalidatePath(Socket *socket, const QString &path)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(path);
+
+ invalidatePath(url);
+}
+
+DirectoryListing Cache::findCached(KURL &url)
+{
+ url.adjustPath(-1);
+
+ if (m_listingCache.contains(url))
+ return m_listingCache[url];
+
+ DirectoryListing invalid;
+ invalid.setValid(false);
+
+ return invalid;
+}
+
+DirectoryListing Cache::findCached(Socket *socket, const QString &path)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(path);
+
+ return findCached(url);
+}
+
+QString Cache::findCachedPath(KURL &url)
+{
+ url.adjustPath(-1);
+
+ if (m_pathCache.contains(url))
+ return m_pathCache[url];
+
+ return QString::null;
+}
+
+QString Cache::findCachedPath(Socket *socket, const QString &path)
+{
+ KURL url = socket->getCurrentUrl();
+ url.setPath(path);
+
+ return findCachedPath(url);
+}
+
+}
diff --git a/kftpgrabber/src/engine/cache.h b/kftpgrabber/src/engine/cache.h
new file mode 100644
index 0000000..7b6cf21
--- /dev/null
+++ b/kftpgrabber/src/engine/cache.h
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+#ifndef KFTPENGINECACHE_H
+#define KFTPENGINECACHE_H
+
+#include <qmap.h>
+#include <kurl.h>
+
+#include "directorylisting.h"
+
+namespace KFTPEngine {
+
+class Socket;
+
+/**
+ * This class provides a cache of paths and directory listings to be used for
+ * faster operations.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class Cache {
+public:
+ static Cache *self();
+ ~Cache();
+
+ /**
+ * Cache a directory listing.
+ *
+ * @param url The listing url (including host information)
+ * @param listing The directory listing to cache
+ */
+ void addDirectory(KURL &url, DirectoryListing listing);
+
+ /**
+ * Cache a directory listing, extracting the host information from the
+ * socket and using the current directory path.
+ *
+ * @param socket The socket to extract the host info from
+ * @param listing The directory listing to cache
+ */
+ void addDirectory(Socket *socket, DirectoryListing listing);
+
+ /**
+ * Updates a single directory entry.
+ *
+ * @param socket The socket to extract the host info from
+ * @param path Entry location
+ * @param filesize New file size
+ */
+ void updateDirectoryEntry(Socket *socket, KURL &path, filesize_t filesize);
+
+ /**
+ * Cache path information.
+ *
+ * @param url The url (including host information)
+ * @param target Actual target directory
+ */
+ void addPath(KURL &url, const QString &target);
+
+ /**
+ * Cache path information, extracting the host information from the
+ * socket and using the current directory path.
+ *
+ * @param socket The socket to extract the host info from
+ * @param target Actual target directory
+ */
+ void addPath(Socket *socket, const QString &target);
+
+ /**
+ * Invalidate a cached entry.
+ *
+ * @param url Url of the entry
+ */
+ void invalidateEntry(KURL &url);
+
+ /**
+ * Invalidate a cached entry.
+ *
+ * @param socket The socket to extract the host info from
+ * @param path Path of the entry
+ */
+ void invalidateEntry(Socket *socket, const QString &path);
+
+ /**
+ * Invalidate a cached path.
+ *
+ * @param url Url of the entry
+ */
+ void invalidatePath(KURL &url);
+
+ /**
+ * Invalidate a cached path.
+ *
+ * @param socket The socket to extract the host info from
+ * @param path Path of the entry
+ */
+ void invalidatePath(Socket *socket, const QString &path);
+
+ /**
+ * Retrieve a cached directory listing.
+ *
+ * @param url Url of the entry
+ * @return A valid DirectoryListing if found, an empty DirectoryListing otherwise
+ */
+ DirectoryListing findCached(KURL &url);
+
+ /**
+ * Retrieve a cached directory listing.
+ *
+ * @param socket The socket to extract the host info from
+ * @param path Path of the entry
+ * @return A valid DirectoryListing if found, an empty DirectoryListing otherwise
+ */
+ DirectoryListing findCached(Socket *socket, const QString &path);
+
+ /**
+ * Retrieve a cached path.
+ *
+ * @param url Url of the entry
+ * @return A target path if found, QString::null otherwise
+ */
+ QString findCachedPath(KURL &url);
+
+ /**
+ * Retrieve a cached path.
+ *
+ * @param socket The socket to extract the host info from
+ * @param path Path of the entry
+ * @return A target path if found, QString::null otherwise
+ */
+ QString findCachedPath(Socket *socket, const QString &path);
+protected:
+ Cache();
+ static Cache *m_self;
+private:
+ QMap<KURL, DirectoryListing> m_listingCache;
+ QMap<KURL, QString> m_pathCache;
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/commands.cpp b/kftpgrabber/src/engine/commands.cpp
new file mode 100644
index 0000000..5e0569f
--- /dev/null
+++ b/kftpgrabber/src/engine/commands.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "commands.h"
+
+namespace KFTPEngine {
+
+namespace Commands {
+
+Base::Base(Socket *socket, Type command)
+ : m_command(command),
+ m_socket(socket),
+ m_wakeupEvent(0),
+ m_processing(false),
+ m_autoDestruct(false),
+ m_clean(false)
+{
+}
+
+void Base::setProcessing(bool value)
+{
+ if (value)
+ m_processing++;
+ else
+ m_processing--;
+}
+
+void Base::autoDestruct(ResetCode code)
+{
+ m_autoDestruct = true;
+ m_resetCode = code;
+}
+
+void Base::wakeup(WakeupEvent *event)
+{
+ // The default implementation just calls process()
+ m_wakeupEvent = event;
+ process();
+ m_wakeupEvent = 0;
+}
+
+}
+
+}
+
diff --git a/kftpgrabber/src/engine/commands.h b/kftpgrabber/src/engine/commands.h
new file mode 100644
index 0000000..679c673
--- /dev/null
+++ b/kftpgrabber/src/engine/commands.h
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#ifndef COMMANDS_H
+#define COMMANDS_H
+
+#include "event.h"
+
+#define ENGINE_STANDARD_COMMAND_CONSTRUCTOR(class, type, cmd) public: \
+ class(type *socket) : Commands::Base(socket, Commands::cmd), currentState(None) {} \
+ private: \
+ State currentState; \
+ \
+ type *socket() {\
+ return static_cast<type*>(m_socket);\
+ }\
+ public:
+
+#define ENGINE_CANCELLATION_POINT { if (isDestructable()) \
+ return; }
+
+#define setupCommandClass(class) if (m_cmdData) \
+ delete m_cmdData; \
+ m_cmdData = new class(this);
+
+#define chainCommandClass(class) Commands::Base *_cmd = new class(socket()); \
+ socket()->addToCommandChain(_cmd); \
+ socket()->nextCommand(); \
+ return;
+
+#define activateCommandClass(class) if (m_cmdData) { \
+ Commands::Base *_cmd = new class(this); \
+ addToCommandChain(_cmd); \
+ nextCommand(); \
+ } else { \
+ m_cmdData = new class(this); \
+ m_cmdData->process(); \
+ }
+
+namespace KFTPEngine {
+
+class Socket;
+
+namespace Commands {
+
+enum Type {
+ CmdNone,
+ CmdNext,
+
+ // Actual commands
+ CmdConnect,
+ CmdConnectRetry,
+ CmdDisconnect,
+ CmdList,
+ CmdScan,
+ CmdGet,
+ CmdPut,
+ CmdDelete,
+ CmdRename,
+ CmdMkdir,
+ CmdChmod,
+ CmdRaw,
+ CmdFxp,
+ CmdKeepAlive
+};
+
+class Base {
+public:
+ Base(Socket *socket, Type type);
+
+ void setProcessing(bool value);
+ bool isProcessing() { return m_processing > 0; }
+
+ void autoDestruct(ResetCode code);
+ bool isDestructable() { return m_autoDestruct && !isProcessing(); }
+ ResetCode resetCode() { return m_resetCode; }
+
+ bool isClean() { return m_clean; }
+
+ Type command() { return m_command; }
+
+ bool isWakeup() { return m_wakeupEvent != 0; }
+ virtual void wakeup(WakeupEvent *event);
+ virtual void process() = 0;
+ virtual void cleanup() {}
+protected:
+ void markClean() { m_clean = true; }
+protected:
+ Type m_command;
+ Socket *m_socket;
+ WakeupEvent *m_wakeupEvent;
+
+ int m_processing;
+ bool m_autoDestruct;
+ ResetCode m_resetCode;
+ bool m_clean;
+};
+
+}
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/connectionretry.cpp b/kftpgrabber/src/engine/connectionretry.cpp
new file mode 100644
index 0000000..e93a1b9
--- /dev/null
+++ b/kftpgrabber/src/engine/connectionretry.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 "connectionretry.h"
+#include "socket.h"
+#include "thread.h"
+#include "event.h"
+
+#include <klocale.h>
+
+namespace KFTPEngine {
+
+ConnectionRetry::ConnectionRetry(Socket *socket)
+ : QObject(),
+ m_socket(socket),
+ m_delay(socket->getConfigInt("retry_delay")),
+ m_max(socket->getConfigInt("max_retries")),
+ m_iteration(0)
+{
+ m_timer = new QTimer(this);
+
+ connect(m_timer, SIGNAL(timeout()), this, SLOT(slotShouldRetry()));
+ connect(m_socket->thread()->eventHandler(), SIGNAL(engineEvent(KFTPEngine::Event*)), this, SLOT(slotEngineEvent(KFTPEngine::Event*)));
+}
+
+void ConnectionRetry::startRetry()
+{
+ if ((m_iteration++ >= m_max && m_max != 0) || m_delay < 1) {
+ abortRetry();
+ return;
+ }
+
+ m_socket->setCurrentCommand(Commands::CmdConnectRetry);
+ m_socket->emitEvent(Event::EventMessage, i18n("Waiting %1 seconds before reconnect...").arg(m_delay));
+ m_socket->emitEvent(Event::EventState, i18n("Waiting..."));
+
+ m_timer->start(1000 * m_delay, true);
+}
+
+void ConnectionRetry::slotShouldRetry()
+{
+ m_socket->setCurrentCommand(Commands::CmdNone);
+ if (m_max > 0)
+ m_socket->emitEvent(Event::EventMessage, i18n("Retrying connection (%1/%2)...").arg(m_iteration).arg(m_max));
+ else
+ m_socket->emitEvent(Event::EventMessage, i18n("Retrying connection...").arg(m_iteration).arg(m_max));
+
+ // Reconnect
+ Thread *thread = m_socket->thread();
+ thread->connect(m_socket->getCurrentUrl());
+}
+
+void ConnectionRetry::abortRetry()
+{
+ m_timer->stop();
+
+ // Disable retry so we avoid infinite loops
+ m_socket->setConfig("retry", 0);
+
+ m_socket->setCurrentCommand(Commands::CmdNone);
+ m_socket->emitEvent(Event::EventMessage, i18n("Retry aborted."));
+ m_socket->emitEvent(Event::EventState, i18n("Idle."));
+ m_socket->emitEvent(Event::EventReady);
+ m_socket->emitError(ConnectFailed);
+
+ // This object should be automagicly removed
+ QObject::deleteLater();
+}
+
+void ConnectionRetry::slotEngineEvent(KFTPEngine::Event *event)
+{
+ if (event->type() == Event::EventConnect) {
+ m_socket->emitEvent(Event::EventRetrySuccess);
+
+ // This object should be automagicly removed
+ QObject::deleteLater();
+ }
+}
+
+}
diff --git a/kftpgrabber/src/engine/connectionretry.h b/kftpgrabber/src/engine/connectionretry.h
new file mode 100644
index 0000000..59a351d
--- /dev/null
+++ b/kftpgrabber/src/engine/connectionretry.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+#ifndef KFTPENGINECONNECTIONRETRY_H
+#define KFTPENGINECONNECTIONRETRY_H
+
+#include <qobject.h>
+#include <qtimer.h>
+
+namespace KFTPEngine {
+
+class Socket;
+class Event;
+
+/**
+ * This class will retry to reconnect to the currently set URL for the
+ * socket specified in constructor.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class ConnectionRetry : public QObject
+{
+Q_OBJECT
+public:
+ /**
+ * Constructs a new ConnectionRetry class instance.
+ */
+ ConnectionRetry(Socket *socket);
+
+ /**
+ * Start the reconnect cycle.
+ */
+ void startRetry();
+
+ /**
+ * Abort the running reconnect cycle and schedule this object's
+ * destruction.
+ */
+ void abortRetry();
+private:
+ Socket *m_socket;
+ int m_delay;
+ int m_max;
+ int m_iteration;
+
+ QTimer *m_timer;
+private slots:
+ void slotShouldRetry();
+ void slotEngineEvent(KFTPEngine::Event *event);
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/directorylisting.cpp b/kftpgrabber/src/engine/directorylisting.cpp
new file mode 100644
index 0000000..1647bb2
--- /dev/null
+++ b/kftpgrabber/src/engine/directorylisting.cpp
@@ -0,0 +1,188 @@
+/*
+ * 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 "directorylisting.h"
+#include "misc/filter.h"
+
+#include <qdatetime.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kmimetype.h>
+
+#include <sys/stat.h>
+
+using namespace KFTPCore::Filter;
+using namespace KIO;
+
+namespace KFTPEngine {
+
+DirectoryEntry::DirectoryEntry()
+{
+}
+
+KIO::UDSEntry DirectoryEntry::toUdsEntry() const
+{
+ bool directory = m_type == 'd';
+ UDSAtom atom;
+ UDSEntry entry;
+
+ atom.m_uds = UDS_NAME;
+ atom.m_str = m_filename;
+ entry.append(atom);
+
+ atom.m_uds = UDS_SIZE;
+ atom.m_long = m_size;
+ entry.append(atom);
+
+ atom.m_uds = UDS_MODIFICATION_TIME;
+ atom.m_long = m_time;
+ entry.append(atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_str = m_owner;
+ entry.append(atom);
+
+ atom.m_uds = UDS_GROUP;
+ atom.m_str = m_group;
+ entry.append(atom);
+
+ atom.m_uds = UDS_ACCESS;
+ atom.m_long = m_permissions;
+ entry.append(atom);
+
+ if (!m_link.isEmpty()) {
+ atom.m_uds = UDS_LINK_DEST;
+ atom.m_str = m_link;
+ entry.append(atom);
+
+ KMimeType::Ptr mime = KMimeType::findByURL(KURL("ftp://host/" + m_filename));
+ if (mime->name() == KMimeType::defaultMimeType()) {
+ atom.m_uds = UDS_GUESSED_MIME_TYPE;
+ atom.m_str = "inode/directory";
+ entry.append(atom);
+
+ directory = true;
+ }
+ }
+
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_long = directory ? S_IFDIR : S_IFREG;
+ entry.append(atom);
+
+ return entry;
+}
+
+QString DirectoryEntry::timeAsString()
+{
+ QDateTime dt;
+ dt.setTime_t(time());
+
+ return KGlobal::locale()->formatDateTime(dt);
+}
+
+bool DirectoryEntry::operator<(const DirectoryEntry &entry) const
+{
+ const Action *firstAction = Filters::self()->process(*this, Action::Priority);
+ const Action *secondAction = Filters::self()->process(entry, Action::Priority);
+
+ int priorityFirst = firstAction ? firstAction->value().toInt() : 0;
+ int prioritySecond = secondAction ? secondAction->value().toInt() : 0;
+
+ if (priorityFirst == prioritySecond) {
+ if (isDirectory() != entry.isDirectory())
+ return isDirectory();
+
+ return m_filename < entry.m_filename;
+ }
+
+ return priorityFirst > prioritySecond;
+}
+
+DirectoryTree::DirectoryTree(DirectoryEntry entry)
+ : m_entry(entry)
+{
+ m_directories.setAutoDelete(true);
+}
+
+void DirectoryTree::addFile(DirectoryEntry entry)
+{
+ m_files.append(entry);
+}
+
+DirectoryTree *DirectoryTree::addDirectory(DirectoryEntry entry)
+{
+ DirectoryTree *tree = new DirectoryTree(entry);
+ m_directories.append(tree);
+
+ return tree;
+}
+
+DirectoryListing::DirectoryListing(const KURL &path)
+ : m_valid(true),
+ m_path(path)
+{
+}
+
+DirectoryListing::~DirectoryListing()
+{
+ m_list.clear();
+}
+
+void DirectoryListing::addEntry(DirectoryEntry entry)
+{
+ m_list.append(entry);
+}
+
+void DirectoryListing::updateEntry(const QString &filename, ::filesize_t size)
+{
+ QValueList<DirectoryEntry>::iterator listEnd = m_list.end();
+ for (QValueList<DirectoryEntry>::iterator i = m_list.begin(); i != listEnd; i++) {
+ if ((*i).filename() == filename) {
+ (*i).setSize(size);
+ return;
+ }
+ }
+
+ // Entry not found, add one
+ DirectoryEntry entry;
+ entry.setFilename(filename);
+ entry.setSize(size);
+
+ addEntry(entry);
+}
+
+}
diff --git a/kftpgrabber/src/engine/directorylisting.h b/kftpgrabber/src/engine/directorylisting.h
new file mode 100644
index 0000000..b332d37
--- /dev/null
+++ b/kftpgrabber/src/engine/directorylisting.h
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#ifndef KFTPNETWORKDIRECTORYLISTING_H
+#define KFTPNETWORKDIRECTORYLISTING_H
+
+#include <kio/global.h>
+#include <kurl.h>
+
+#include <qvaluelist.h>
+#include <qptrlist.h>
+
+#include <time.h>
+#include <sys/time.h>
+
+typedef unsigned long long int filesize_t;
+
+namespace KFTPEngine {
+
+class DirectoryEntry {
+public:
+ DirectoryEntry();
+
+ void setFilename(const QString &filename) { m_filename = filename; }
+ void setOwner(const QString &owner) { m_owner = owner; }
+ void setGroup(const QString &group) { m_group = group; }
+ void setLink(const QString &link) { m_link = link; }
+ void setPermissions(int permissions) { m_permissions = permissions; }
+ void setSize(filesize_t size) { m_size = size; }
+ void setType(char type) { m_type = type; }
+ void setTime(time_t time) { m_time = time; }
+
+ QString filename() const { return m_filename; }
+ QString owner() const { return m_owner; }
+ QString group() const { return m_group; }
+ QString link() const { return m_link; }
+ int permissions() const { return m_permissions; }
+ filesize_t size() const { return m_size; }
+ char type() const { return m_type; }
+ time_t time() const { return m_time; }
+ QString timeAsString();
+
+ bool isDirectory() const { return m_type == 'd'; }
+ bool isFile() const { return m_type == 'f'; }
+ bool isDevice() const { return m_type == 'c' || m_type == 'b'; }
+ bool isSymlink() const { return !m_link.isEmpty(); }
+
+ KIO::UDSEntry toUdsEntry() const;
+
+ struct tm timeStruct;
+
+ bool operator<(const DirectoryEntry &entry) const;
+private:
+ QString m_filename;
+ QString m_owner;
+ QString m_group;
+ QString m_link;
+
+ int m_permissions;
+ filesize_t m_size;
+ char m_type;
+ time_t m_time;
+};
+
+class DirectoryTree {
+public:
+ typedef QValueList<DirectoryEntry>::ConstIterator FileIterator;
+ typedef QPtrList<DirectoryTree>::ConstIterator DirIterator;
+
+ DirectoryTree() {}
+ DirectoryTree(DirectoryEntry entry);
+
+ void addFile(DirectoryEntry entry);
+ DirectoryTree *addDirectory(DirectoryEntry entry);
+
+ DirectoryEntry info() { return m_entry; }
+
+ QValueList<DirectoryEntry> *files() { return &m_files; }
+ QPtrList<DirectoryTree> *directories() { return &m_directories; }
+private:
+ DirectoryEntry m_entry;
+ QValueList<DirectoryEntry> m_files;
+ QPtrList<DirectoryTree> m_directories;
+};
+
+/**
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class DirectoryListing {
+public:
+ DirectoryListing(const KURL &path = KURL());
+ ~DirectoryListing();
+
+ void addEntry(DirectoryEntry entry);
+ void updateEntry(const QString &filename, filesize_t size);
+ QValueList<DirectoryEntry> list() { return m_list; }
+
+ void setValid(bool value) { m_valid = value; }
+ bool isValid() { return m_valid; }
+private:
+ bool m_valid;
+ KURL m_path;
+ QValueList<DirectoryEntry> m_list;
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/event.cpp b/kftpgrabber/src/engine/event.cpp
new file mode 100644
index 0000000..bfbf6f6
--- /dev/null
+++ b/kftpgrabber/src/engine/event.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 "event.h"
+#include "thread.h"
+
+namespace KFTPEngine {
+
+EventParameter::EventParameter()
+{
+}
+
+EventParameter::EventParameter(const QString &string)
+{
+ m_type = ParamString;
+ m_string = string;
+}
+
+EventParameter::EventParameter(const KURL &url)
+{
+ m_type = ParamUrl;
+ m_url = url;
+}
+
+EventParameter::EventParameter(DirectoryListing listing)
+{
+ m_type = ParamDirListing;
+ m_directoryListing = listing;
+}
+
+EventParameter::EventParameter(DirectoryTree tree)
+{
+ m_type = ParamDirTree;
+ m_directoryTree = tree;
+}
+
+EventParameter::EventParameter(ErrorCode error)
+{
+ m_type = ParamErrorCode;
+ m_errorCode = error;
+}
+
+EventParameter::EventParameter(filesize_t size)
+{
+ m_type = ParamSize;
+ m_fileSize = size;
+}
+
+EventParameter::EventParameter(void *data)
+{
+ m_type = ParamData;
+ m_data = data;
+}
+
+QString EventParameter::asString() const
+{
+ return m_string;
+}
+
+KURL EventParameter::asUrl() const
+{
+ return m_url;
+}
+
+DirectoryListing EventParameter::asDirectoryListing() const
+{
+ return m_directoryListing;
+}
+
+DirectoryTree EventParameter::asDirectoryTree() const
+{
+ return m_directoryTree;
+}
+
+ErrorCode EventParameter::asErrorCode() const
+{
+ return m_errorCode;
+}
+
+filesize_t EventParameter::asFileSize() const
+{
+ return m_fileSize;
+}
+
+bool EventParameter::asBoolean() const
+{
+ return (bool) m_fileSize;
+}
+
+void *EventParameter::asData() const
+{
+ return m_data;
+}
+
+Event::Event(Type type, QValueList<EventParameter> params)
+ : QCustomEvent(65123),
+ m_type(type),
+ m_params(params)
+{
+}
+
+Event::~Event()
+{
+}
+
+EventHandler::EventHandler(Thread *thread)
+ : QObject(),
+ m_thread(thread)
+{
+}
+
+void EventHandler::customEvent(QCustomEvent *e)
+{
+ if (e->type() == 65123) {
+ Event *ev = static_cast<Event*>(e);
+
+ emit engineEvent(ev);
+
+ switch (ev->type()) {
+ case Event::EventConnect: emit connected(); break;
+ case Event::EventDisconnect: emit disconnected(); break;
+ case Event::EventResponse:
+ case Event::EventMultiline: {
+ emit gotResponse(ev->getParameter(0).asString());
+ break;
+ }
+ case Event::EventRaw: emit gotRawResponse(ev->getParameter(0).asString()); break;
+ default: break;
+ }
+ }
+}
+
+}
diff --git a/kftpgrabber/src/engine/event.h b/kftpgrabber/src/engine/event.h
new file mode 100644
index 0000000..e21a45e
--- /dev/null
+++ b/kftpgrabber/src/engine/event.h
@@ -0,0 +1,372 @@
+/*
+ * 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.
+ */
+
+#ifndef KFTPNETWORKEVENT_H
+#define KFTPNETWORKEVENT_H
+
+#include <qobject.h>
+#include <qevent.h>
+#include <qshared.h>
+
+#include "directorylisting.h"
+
+namespace KFTPEngine {
+
+/**
+ * Engine reset codes. TODO description of each reset code.
+ */
+enum ResetCode {
+ Ok,
+ UserAbort,
+ Failed,
+ FailedSilently
+};
+
+/**
+ * Engine error codes. TODO: description of each error code.
+ */
+enum ErrorCode {
+ ConnectFailed,
+ LoginFailed,
+ PermissionDenied,
+ FileNotFound,
+ OperationFailed,
+ ListFailed,
+ FileOpenFailed
+};
+
+/**
+ * This class is used for event parameter passing between the socket
+ * thread and the main thread. It supports multiple parameter types.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class EventParameter {
+public:
+ /**
+ * Parameter type enum.
+ */
+ enum Type {
+ ParamString,
+ ParamUrl,
+ ParamDirListing,
+ ParamDirTree,
+ ParamErrorCode,
+ ParamSize,
+ ParamData
+ };
+
+ EventParameter();
+
+ /**
+ * Constructs a new string parameter.
+ *
+ * @param string The QString value
+ */
+ EventParameter(const QString &string);
+
+ /**
+ * Construct a new url parameter.
+ *
+ * @param url The KURL value
+ */
+ EventParameter(const KURL &url);
+
+ /**
+ * Construct a new directory listing parameter.
+ *
+ * @param listing The DirectoryListing value
+ */
+ EventParameter(DirectoryListing listing);
+
+ /**
+ * Construct a new directory tree parameter.
+ *
+ * @param tree The DirectoryTree value
+ */
+ EventParameter(DirectoryTree tree);
+
+ /**
+ * Construct a new error code parameter.
+ *
+ * @param error The ErrorCode value
+ */
+ EventParameter(ErrorCode error);
+
+ /**
+ * Construct a new filesize parameter.
+ *
+ * @param size The filesize_t value
+ */
+ EventParameter(filesize_t size);
+
+ /**
+ * Constructs a new data parameter.
+ *
+ * @param data A pointer to some data.
+ */
+ EventParameter(void *data);
+
+ /**
+ * Returns the parameter's string value.
+ *
+ * @return Parameter's string value
+ */
+ QString asString() const;
+
+ /**
+ * Returns the parameter's url value.
+ *
+ * @return Parameter's url value
+ */
+ KURL asUrl() const;
+
+ /**
+ * Returns the parameter's directory listing value.
+ *
+ * @return Parameter's directory listing value
+ */
+ DirectoryListing asDirectoryListing() const;
+
+ /**
+ * Returns the parameter's directory tree value.
+ *
+ * @return Parameter's directory tree value.
+ */
+ DirectoryTree asDirectoryTree() const;
+
+ /**
+ * Returns the parameter's error code value.
+ *
+ * @return Parameter's error code value
+ */
+ ErrorCode asErrorCode() const;
+
+ /**
+ * Returns the parameter's filesize value.
+ *
+ * @return Parameter's filesize value
+ */
+ filesize_t asFileSize() const;
+
+ /**
+ * Returns the parameter's boolean value.
+ *
+ * @return Parameter's boolean value
+ */
+ bool asBoolean() const;
+
+ /**
+ * Returns raw parameter data pointer.
+ *
+ * @return Raw parameter data pointer
+ */
+ void *asData() const;
+private:
+ Type m_type;
+
+ QString m_string;
+ KURL m_url;
+ DirectoryListing m_directoryListing;
+ DirectoryTree m_directoryTree;
+ ErrorCode m_errorCode;
+ filesize_t m_fileSize;
+ void *m_data;
+};
+
+/**
+ * A wakeup event is a special type event used to transfer some response from
+ * the GUI to the engine that has been temporarly suspended. After receiving
+ * this event, the current command handler's wakeup() method will be called
+ * with this event as a parameter.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class WakeupEvent {
+public:
+ /**
+ * Possible wakeup event types. Each type should subclass this class to
+ * provide any custom methods needed.
+ */
+ enum Type {
+ WakeupFileExists,
+ WakeupPubkey
+ };
+
+ /**
+ * Constructs a new wakeup event of specified type.
+ *
+ * @param type Event type
+ */
+ WakeupEvent(Type type) : m_type(type) {}
+private:
+ Type m_type;
+};
+
+/**
+ * A file exists wakeup event that is used to continue pending transfers.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class FileExistsWakeupEvent : public WakeupEvent {
+public:
+ /**
+ * Possible actions the engine can take.
+ */
+ enum Action {
+ Overwrite,
+ Rename,
+ Resume,
+ Skip
+ };
+
+ /**
+ * Constructs a new file exists wakeup event with Skip action as default.
+ */
+ FileExistsWakeupEvent() : WakeupEvent(WakeupFileExists), action(Skip) {}
+
+ Action action;
+ QString newFileName;
+};
+
+/**
+ * A public key password request event for SFTP connections.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class PubkeyWakeupEvent : public WakeupEvent {
+public:
+ /**
+ * Constructs a new public key wakeup event.
+ */
+ PubkeyWakeupEvent() : WakeupEvent(WakeupPubkey) {}
+
+ QString password;
+};
+
+/**
+ * This class represents an event that is passed to the EventHandler for
+ * processing. It can have multiple EventParameters.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class Event : public QCustomEvent {
+public:
+ enum Type {
+ EventMessage,
+ EventCommand,
+ EventResponse,
+ EventMultiline,
+ EventRaw,
+ EventDirectoryListing,
+ EventDisconnect,
+ EventError,
+ EventConnect,
+ EventReady,
+ EventState,
+ EventScanComplete,
+ EventRetrySuccess,
+ EventReloadNeeded,
+
+ // Transfer events
+ EventTransferComplete,
+ EventResumeOffset,
+
+ // Events that require wakeup events
+ EventFileExists,
+ EventPubkeyPassword
+ };
+
+ /**
+ * Construct a new event with a parameter list.
+ *
+ * @param params Parameter list
+ */
+ Event(Type type, QValueList<EventParameter> params);
+ ~Event();
+
+ /**
+ * Return the event's type.
+ *
+ * @return Event's type
+ */
+ Type type() { return m_type; }
+
+ /**
+ * Returns the parameter with a specific index.
+ *
+ * @param index Parameter's index
+ * @return An EventParameter object
+ */
+ EventParameter getParameter(int index) { return m_params[index]; }
+protected:
+ Type m_type;
+ QValueList<EventParameter> m_params;
+};
+
+class Thread;
+
+/**
+ * This class handles events receieved from the thread and passes them
+ * on to the GUI as normal Qt signals.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class EventHandler : public QObject {
+Q_OBJECT
+public:
+ /**
+ * Construct a new event handler.
+ *
+ * @param thread The thread this event handler belongs to
+ */
+ EventHandler(Thread *thread);
+protected:
+ void customEvent(QCustomEvent *e);
+protected:
+ Thread *m_thread;
+signals:
+ void engineEvent(KFTPEngine::Event *event);
+
+ void connected();
+ void disconnected();
+ void gotResponse(const QString &text);
+ void gotRawResponse(const QString &text);
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/ftpdirectoryparser.cpp b/kftpgrabber/src/engine/ftpdirectoryparser.cpp
new file mode 100644
index 0000000..074328c
--- /dev/null
+++ b/kftpgrabber/src/engine/ftpdirectoryparser.cpp
@@ -0,0 +1,1144 @@
+/*
+ * This file is part of the KFTPGrabber project
+ *
+ * Copyright (C) 2003-2004 by the KFTPGrabber developers
+ * Copyright (C) 2003-2004 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 "ftpdirectoryparser.h"
+#include "ftpsocket.h"
+
+#include <qvaluevector.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+
+#include <time.h>
+#include <sys/stat.h>
+
+namespace KFTPEngine {
+
+class DToken {
+public:
+ enum TokenTypeInfo {
+ Unknown,
+ Yes,
+ No
+ };
+
+ DToken()
+ : m_token(QString::null),
+ m_valid(false)
+ {
+ }
+
+ DToken(const QString &token, int start = 0)
+ : m_token(token),
+ m_length(token.length()),
+ m_start(start),
+ m_valid(true),
+ m_numeric(Unknown),
+ m_leftNumeric(Unknown),
+ m_rightNumeric(Unknown)
+ {
+ }
+
+ int getStart()
+ {
+ return m_start;
+ }
+
+ QString getToken()
+ {
+ return m_token;
+ }
+
+ int getLength()
+ {
+ return m_length;
+ }
+
+ QString getString(int type = 0)
+ {
+ switch (type) {
+ case 0: return m_token; break;
+ case 1: {
+ if (!isRightNumeric() || isNumeric())
+ return QString::null;
+
+ int pos = m_length - 1;
+ while (m_token[pos] >= '0' && m_token[pos] <= '9')
+ pos--;
+
+ return m_token.mid(0, pos + 1);
+ break;
+ }
+ case 2: {
+ if (!isLeftNumeric() || isNumeric())
+ return QString::null;
+
+ int len = 0;
+ while (m_token[len] >= '0' && m_token[len] <= '9')
+ len++;
+
+ return m_token.mid(0, len);
+ break;
+ }
+ }
+
+ return QString::null;
+ }
+
+ int find(const char *chr, unsigned int start = 0) const
+ {
+ if (!chr)
+ return -1;
+
+ for (unsigned int i = start; i < m_length; i++) {
+ for (int c = 0; chr[c]; c++) {
+ if (m_token[i] == chr[c])
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ unsigned long long getInteger()
+ {
+ return m_token.toULongLong();
+ }
+
+ unsigned long long getInteger(unsigned int start, int len)
+ {
+ return m_token.mid(start, len).toULongLong();
+ }
+
+ bool isValid()
+ {
+ return m_valid;
+ }
+
+ bool isNumeric()
+ {
+ if (m_numeric == Unknown) {
+ bool ok;
+ (void) m_token.toInt(&ok);
+
+ m_numeric = ok ? Yes : No;
+ }
+
+ return m_numeric == Yes;
+ }
+
+
+ bool isNumeric(unsigned int start, unsigned int len)
+ {
+ len = start + len < m_length ? start + len : m_length;
+
+ for (unsigned int i = start; i < len; i++) {
+ if (m_token[i] < '0' || m_token[i] > '9')
+ return false;
+ }
+
+ return true;
+ }
+
+ bool isLeftNumeric()
+ {
+ if (m_leftNumeric == Unknown) {
+ if (m_length < 2)
+ m_leftNumeric = No;
+ else if (m_token[0] < '0' || m_token[0] > '9')
+ m_leftNumeric = No;
+ else
+ m_leftNumeric = Yes;
+ }
+
+ return m_leftNumeric == Yes;
+ }
+
+ bool isRightNumeric()
+ {
+ if (m_rightNumeric == Unknown) {
+ if (m_length < 2)
+ m_rightNumeric = No;
+ else if (m_token[m_length - 1] < '0' || m_token[m_length - 1] > '9')
+ m_rightNumeric = No;
+ else
+ m_rightNumeric = Yes;
+ }
+
+ return m_rightNumeric == Yes;
+ }
+
+ QChar operator[](unsigned int n) const
+ {
+ return m_token[n];
+ }
+private:
+ QString m_token;
+ unsigned int m_length;
+ int m_start;
+ bool m_valid;
+
+ TokenTypeInfo m_numeric;
+ TokenTypeInfo m_leftNumeric;
+ TokenTypeInfo m_rightNumeric;
+};
+
+class DLine {
+public:
+ DLine(const QString &line)
+ : m_line(line.stripWhiteSpace()),
+ m_parsePos(0)
+ {
+ }
+
+ bool getToken(unsigned int index, DToken &token, bool toEnd = false)
+ {
+ if (!toEnd) {
+ if (m_tokens.count() > index) {
+ token = m_tokens[index];
+ return true;
+ }
+
+ unsigned int start = m_parsePos;
+ while (m_parsePos < m_line.length()) {
+ if (m_line[m_parsePos] == ' ') {
+ m_tokens.append(DToken(m_line.mid(start, m_parsePos - start), start));
+
+ while (m_line[m_parsePos] == ' ' && m_parsePos < m_line.length())
+ m_parsePos++;
+
+ if (m_tokens.count() > index) {
+ token = m_tokens[index];
+ return true;
+ }
+
+ start = m_parsePos;
+ }
+
+ m_parsePos++;
+ }
+
+ if (m_parsePos != start) {
+ m_tokens.append(DToken(m_line.mid(start, m_parsePos - start), start));
+ }
+
+ if (m_tokens.count() > index) {
+ token = m_tokens[index];
+ return true;
+ }
+
+ return false;
+ } else {
+ if (m_endLineTokens.count() > index) {
+ token = m_endLineTokens[index];
+ return true;
+ }
+
+ if (m_tokens.count() <= index && !getToken(index, token))
+ return false;
+
+ for (unsigned int i = m_endLineTokens.count(); i <= index; i++) {
+ m_endLineTokens.append(DToken(m_line.mid(m_tokens[i].getStart())));
+ }
+
+ token = m_endLineTokens[index];
+ return true;
+ }
+ }
+private:
+ QStringList m_stringList;
+ QValueVector<DToken> m_tokens;
+ QValueVector<DToken> m_endLineTokens;
+ QString m_line;
+ unsigned int m_parsePos;
+};
+
+FtpDirectoryParser::FtpDirectoryParser(FtpSocket *socket)
+ : m_socket(socket),
+ m_listing(DirectoryListing(socket->getCurrentDirectory()))
+{
+ // Populate month names as they appear in the listing
+ m_monthNameMap["jan"] = 1;
+ m_monthNameMap["feb"] = 2;
+ m_monthNameMap["mar"] = 3;
+ m_monthNameMap["apr"] = 4;
+ m_monthNameMap["may"] = 5;
+ m_monthNameMap["jun"] = 6;
+ m_monthNameMap["june"] = 6;
+ m_monthNameMap["jul"] = 7;
+ m_monthNameMap["july"] = 7;
+ m_monthNameMap["aug"] = 8;
+ m_monthNameMap["sep"] = 9;
+ m_monthNameMap["sept"] = 9;
+ m_monthNameMap["oct"] = 10;
+ m_monthNameMap["nov"] = 11;
+ m_monthNameMap["dec"] = 12;
+
+ m_monthNameMap["1"] = 1;
+ m_monthNameMap["01"] = 1;
+ m_monthNameMap["2"] = 2;
+ m_monthNameMap["02"] = 2;
+ m_monthNameMap["3"] = 3;
+ m_monthNameMap["03"] = 3;
+ m_monthNameMap["4"] = 4;
+ m_monthNameMap["04"] = 4;
+ m_monthNameMap["5"] = 5;
+ m_monthNameMap["05"] = 5;
+ m_monthNameMap["6"] = 6;
+ m_monthNameMap["06"] = 6;
+ m_monthNameMap["7"] = 7;
+ m_monthNameMap["07"] = 7;
+ m_monthNameMap["8"] = 8;
+ m_monthNameMap["08"] = 8;
+ m_monthNameMap["9"] = 9;
+ m_monthNameMap["09"] = 9;
+ m_monthNameMap["10"] = 10;
+ m_monthNameMap["11"] = 11;
+ m_monthNameMap["12"] = 12;
+}
+
+void FtpDirectoryParser::addDataLine(const QString &line)
+{
+ QString tmp(line);
+ tmp.append("\n");
+ addData(tmp.ascii(), tmp.length());
+}
+
+void FtpDirectoryParser::addData(const char *data, int len)
+{
+ // Append new data to the buffer and check for any new lines
+ m_buffer.append(QString::fromAscii(data, len));
+
+ int pos;
+ while ((pos = m_buffer.find('\n')) > -1) {
+ DirectoryEntry entry;
+ QString line = m_buffer.mid(0, pos).stripWhiteSpace();
+ line = m_socket->remoteEncoding()->decode(QCString(line.ascii()));
+
+ if (parseLine(line, entry) && !entry.filename().isEmpty()) {
+ if (entry.type() == '-')
+ entry.setType('f');
+
+ m_listing.addEntry(entry);
+ }
+
+ // Remove what we just parsed
+ m_buffer.remove(0, pos + 1);
+ }
+}
+
+bool FtpDirectoryParser::parseMlsd(const QString &line, DirectoryEntry &entry)
+{
+ QStringList facts = QStringList::split(';', line);
+ QStringList::Iterator end = facts.end();
+
+ for (QStringList::Iterator i = facts.begin(); i != end; ++i) {
+ if ((*i).contains('=')) {
+ QString key = (*i).section('=', 0, 0).lower();
+ QString value = (*i).section('=', 1, 1);
+
+ if (key == "type") {
+ if (value == "file")
+ entry.setType('f');
+ else if (value == "dir")
+ entry.setType('d');
+ } else if (key == "size") {
+ entry.setSize(value.toULongLong());
+ } else if (key == "modify") {
+ struct tm dt;
+
+ dt.tm_year = value.left(4).toInt() - 1900;
+ dt.tm_mon = value.mid(4, 2).toInt() - 1;
+ dt.tm_mday = value.mid(6, 2).toInt();
+ dt.tm_hour = value.mid(8, 2).toInt();
+ dt.tm_min = value.mid(10, 2).toInt();
+ dt.tm_sec = value.mid(12, 2).toInt();
+ entry.setTime(mktime(&dt));
+ } else if (key == "unix.mode") {
+ entry.setPermissions(value.toInt(0, 8));
+ } else if (key == "unix.uid") {
+ entry.setOwner(value);
+ } else if (key == "unix.gid") {
+ entry.setGroup(value);
+ }
+ } else {
+ entry.setFilename((*i).stripWhiteSpace());
+ }
+ }
+
+ return true;
+}
+
+bool FtpDirectoryParser::parseUnixPermissions(const QString &permissions, DirectoryEntry &entry)
+{
+ int p = 0;
+
+ if (permissions[1] == 'r') p |= S_IRUSR;
+ if (permissions[2] == 'w') p |= S_IWUSR;
+ if (permissions[3] == 'x' || permissions[3] == 's') p |= S_IXUSR;
+
+ if (permissions[4] == 'r') p |= S_IRGRP;
+ if (permissions[5] == 'w') p |= S_IWGRP;
+ if (permissions[6] == 'x' || permissions[6] == 's') p |= S_IXGRP;
+
+ if (permissions[7] == 'r') p |= S_IROTH;
+ if (permissions[8] == 'w') p |= S_IWOTH;
+ if (permissions[9] == 'x' || permissions[9] == 't') p |= S_IXOTH;
+
+ if (permissions[3] == 's' || permissions[3] == 'S') p |= S_ISUID;
+ if (permissions[6] == 's' || permissions[6] == 'S') p |= S_ISGID;
+ if (permissions[9] == 't' || permissions[9] == 'T') p |= S_ISVTX;
+
+ entry.setPermissions(p);
+}
+
+bool FtpDirectoryParser::parseLine(const QString &line, DirectoryEntry &entry)
+{
+ DLine *tLine = new DLine(line);
+ bool done = false;
+
+ // Invalidate timestamp
+ entry.setTime(-1);
+ entry.timeStruct.tm_year = 0;
+ entry.timeStruct.tm_mon = 0;
+ entry.timeStruct.tm_hour = 0;
+ entry.timeStruct.tm_mday = 0;
+ entry.timeStruct.tm_min = 0;
+ entry.timeStruct.tm_sec = 0;
+ entry.timeStruct.tm_wday = 0;
+ entry.timeStruct.tm_yday = 0;
+ entry.timeStruct.tm_isdst = 0;
+
+ // Attempt machine friendly format first, when socket supports MLSD
+ if (m_socket->getConfigInt("feat.mlsd"))
+ done = parseMlsd(line, entry);
+
+ if (!done)
+ done = parseUnix(tLine, entry);
+ if (!done)
+ done = parseDos(tLine, entry);
+ if (!done)
+ done = parseVms(tLine, entry);
+
+ if (done) {
+ // Convert datetime to UNIX epoch
+ if (entry.time() == -1) {
+ // Correct format for mktime
+ entry.timeStruct.tm_year -= 1900;
+ entry.timeStruct.tm_mon -= 1;
+ entry.setTime(mktime(&entry.timeStruct));
+ }
+
+ // Add symlink if any
+ if (entry.filename().contains(" -> ") > 0) {
+ int pos = entry.filename().findRev(" -> ");
+
+ entry.setLink(entry.filename().mid(pos + 4));
+ entry.setFilename(entry.filename().mid(0, pos));
+ }
+
+ // Parse owner into group/owner
+ if (entry.owner().contains(" ") > 0) {
+ int pos = entry.owner().find(" ");
+
+ entry.setGroup(entry.owner().mid(pos + 1));
+ entry.setOwner(entry.owner().mid(0, pos));
+ }
+
+ // Remove unwanted names
+ if (entry.filename() == "." || entry.filename() == "..") {
+ entry.setFilename(QString::null);
+ }
+ }
+
+ delete tLine;
+ return done;
+}
+
+bool FtpDirectoryParser::parseUnix(DLine *line, DirectoryEntry &entry)
+{
+ int index = 0;
+ DToken token;
+
+ if (!line->getToken(index, token))
+ return false;
+
+
+ char chr = token[0];
+ if (chr != 'b' &&
+ chr != 'c' &&
+ chr != 'd' &&
+ chr != 'l' &&
+ chr != 'p' &&
+ chr != 's' &&
+ chr != '-')
+ return false;
+
+ QString permissions = token.getString();
+ entry.setType(chr);
+
+ // Check for netware servers, which split the permissions into two parts
+ bool netware = false;
+ if (token.getLength() == 1) {
+ if (!line->getToken(++index, token))
+ return false;
+
+ permissions += " " + token.getString();
+ netware = true;
+ }
+
+ parseUnixPermissions(permissions, entry);
+
+ int numOwnerGroup = 3;
+ if (!netware) {
+ // Filter out groupid, we don't need it
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (!token.isNumeric())
+ index--;
+ }
+
+ // Repeat until numOwnerGroup is 0 since not all servers send every possible field
+ int startindex = index;
+ do {
+ // Reset index
+ index = startindex;
+
+ entry.setOwner(QString::null);
+ for (int i = 0; i < numOwnerGroup; i++) {
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (i)
+ entry.setOwner(entry.owner() + " ");
+
+ entry.setOwner(entry.owner() + token.getString());
+ }
+
+ if (!line->getToken(++index, token))
+ return false;
+
+
+ // Check for concatenated groupname and size fields
+ filesize_t size;
+ if (!parseComplexFileSize(token, size)) {
+ if (!token.isRightNumeric())
+ continue;
+
+ entry.setSize(token.getInteger());
+ } else {
+ entry.setSize(size);
+ }
+
+ // Append missing group to ownerGroup
+ if (!token.isNumeric() && token.isRightNumeric()) {
+ if (!entry.owner().isEmpty())
+ entry.setOwner(entry.owner() + " ");
+
+ entry.setOwner(entry.owner() + token.getString(1));
+ }
+
+ if (!parseUnixDateTime(line, index, entry))
+ continue;
+
+ // Get the filename
+ if (!line->getToken(++index, token, true))
+ continue;
+
+ entry.setFilename(token.getString());
+
+ // Filter out cpecial chars at the end of the filenames
+ chr = token[token.getLength() - 1];
+ if (chr == '/' ||
+ chr == '|' ||
+ chr == '*')
+ entry.setFilename(entry.filename().mid(0, entry.filename().length() - 1));
+
+ return true;
+ } while (--numOwnerGroup);
+
+ return false;
+}
+
+bool FtpDirectoryParser::parseUnixDateTime(DLine *line, int &index, DirectoryEntry &entry)
+{
+ DToken token;
+
+ // Get the month date field
+ QString dateMonth;
+ if (!line->getToken(++index, token))
+ return false;
+
+ // Some servers use the following date formats:
+ // 26-05 2002, 2002-10-14, 01-jun-99
+ // slashes instead of dashes are also possible
+ int pos = token.find("-/");
+
+ if (pos != -1) {
+ int pos2 = token.find("-/", pos + 1);
+
+ if (pos2 == -1) {
+ // something like 26-05 2002
+ int day = token.getInteger(pos + 1, token.getLength() - pos - 1);
+
+ if (day < 1 || day > 31)
+ return false;
+
+ entry.timeStruct.tm_mday = day;
+ dateMonth = token.getString().left(pos);
+ } else if (!parseShortDate(token, entry)) {
+ return false;
+ }
+ } else {
+ dateMonth = token.getString();
+ }
+
+ bool bHasYearAndTime = false;
+ if (!entry.timeStruct.tm_mday) {
+ // Get day field
+ if (!line->getToken(++index, token))
+ return false;
+
+ int dateDay;
+
+ // Check for non-numeric day
+ if (!token.isNumeric() && !token.isLeftNumeric()) {
+ if (dateMonth.right(1) == ".")
+ dateMonth.remove(dateMonth.length() - 1, 1);
+
+ bool tmp;
+ dateDay = dateMonth.toInt(&tmp);
+ if (!tmp)
+ return false;
+
+ dateMonth = token.getString();
+ } else {
+ dateDay = token.getInteger();
+
+ if (token[token.getLength() - 1] == ',')
+ bHasYearAndTime = true;
+ }
+
+ if (dateDay < 1 || dateDay > 31)
+ return false;
+
+ entry.timeStruct.tm_mday = dateDay;
+ }
+
+ if (!entry.timeStruct.tm_mon) {
+ // Check month name
+ if (dateMonth.right(1) == "," || dateMonth.right(1) == ".")
+ dateMonth.remove(dateMonth.length() - 1, 1);
+
+ dateMonth = dateMonth.lower();
+
+ QMap<QString, int>::iterator iter = m_monthNameMap.find(dateMonth);
+ if (iter == m_monthNameMap.end())
+ return false;
+
+ entry.timeStruct.tm_mon = iter.data();
+ }
+
+ // Get time/year field
+ if (!line->getToken(++index, token))
+ return false;
+
+ pos = token.find(":.-");
+ if (pos != -1) {
+ // token is a time
+ if (!pos || static_cast<size_t>(pos) == (token.getLength() - 1))
+ return false;
+
+ QString str = token.getString();
+ bool tmp;
+ int hour = str.left(pos).toInt(&tmp);
+ if (!tmp)
+ return false;
+
+ int minute = str.mid(pos + 1).toInt(&tmp);
+ if (!tmp)
+ return false;
+
+ if (hour < 0 || hour > 23)
+ return false;
+
+ if (minute < 0 || minute > 59)
+ return false;
+
+ entry.timeStruct.tm_hour = hour;
+ entry.timeStruct.tm_min = minute;
+
+ // Some servers use times only for files nweer than 6 months,
+ int year = QDate::currentDate().year();
+ int now = QDate::currentDate().day() + 31 * QDate::currentDate().month();
+ int file = entry.timeStruct.tm_mon * 31 + entry.timeStruct.tm_mday;
+
+ if (now >= file)
+ entry.timeStruct.tm_year = year;
+ else
+ entry.timeStruct.tm_year = year - 1;
+ } else if (!entry.timeStruct.tm_year) {
+ // token is a year
+ if (!token.isNumeric() && !token.isLeftNumeric())
+ return false;
+
+ int year = token.getInteger();
+ if (year > 3000)
+ return false;
+
+ if (year < 1000)
+ year += 1900;
+
+ entry.timeStruct.tm_year = year;
+
+ if (bHasYearAndTime) {
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (token.find(":") == 2 && token.getLength() == 5 && token.isLeftNumeric() && token.isRightNumeric()) {
+ int pos = token.find(":");
+
+ // token is a time
+ if (!pos || static_cast<size_t>(pos) == (token.getLength() - 1))
+ return false;
+
+ QString str = token.getString();
+ bool tmp;
+ long hour = str.left(pos).toInt(&tmp);
+ if (!tmp)
+ return false;
+
+ long minute = str.mid(pos + 1).toInt(&tmp);
+ if (!tmp)
+ return false;
+
+ if (hour < 0 || hour > 23)
+ return false;
+
+ if (minute < 0 || minute > 59)
+ return false;
+
+ entry.timeStruct.tm_hour = hour;
+ entry.timeStruct.tm_min = minute;
+ } else {
+ index--;
+ }
+ }
+ } else {
+ index--;
+ }
+
+ return true;
+}
+
+bool FtpDirectoryParser::parseShortDate(DToken &token, DirectoryEntry &entry)
+{
+ if (token.getLength() < 1)
+ return false;
+
+ bool gotYear = false;
+ bool gotMonth = false;
+ bool gotDay = false;
+ bool gotMonthName = false;
+
+ int value = 0;
+
+ int pos = token.find("-./");
+ if (pos < 1)
+ return false;
+
+ if (!token.isNumeric(0, pos)) {
+ // Seems to be monthname-dd-yy
+
+ // Check month name
+ QString dateMonth = token.getString().mid(0, pos);
+ dateMonth = dateMonth.lower();
+
+ QMap<QString, int>::iterator iter = m_monthNameMap.find(dateMonth);
+ if (iter == m_monthNameMap.end())
+ return false;
+
+ entry.timeStruct.tm_mon = iter.data();
+ gotMonth = true;
+ gotMonthName = true;
+ } else if (pos == 4) {
+ // Seems to be yyyy-mm-dd
+ int year = token.getInteger(0, pos);
+
+ if (year < 1900 || year > 3000)
+ return false;
+
+ entry.timeStruct.tm_year = year;
+ gotYear = true;
+ } else if (pos <= 2) {
+ int value = token.getInteger(0, pos);
+
+ if (token[pos] == '.') {
+ // Maybe dd.mm.yyyy
+ if (value < 1900 || value > 3000)
+ return false;
+
+ entry.timeStruct.tm_mday = value;
+ gotDay = true;
+ } else {
+ // Detect mm-dd-yyyy or mm/dd/yyyy and
+ // dd-mm-yyyy or dd/mm/yyyy
+ if (value < 1)
+ return false;
+
+ if (value > 12) {
+ if (value > 31)
+ return false;
+
+ entry.timeStruct.tm_mday = value;
+ gotDay = true;
+ } else {
+ entry.timeStruct.tm_mon = value;
+ gotMonth = true;
+ }
+ }
+ } else {
+ return false;
+ }
+
+
+ int pos2 = token.find("-./", pos + 1);
+
+ if (pos2 == -1 || (pos2 - pos) == 1)
+ return false;
+
+ if (static_cast<size_t>(pos2) == (token.getLength() - 1))
+ return false;
+
+ // If we already got the month and the second field is not numeric,
+ // change old month into day and use new token as month
+ if (!token.isNumeric(pos + 1, pos2 - pos - 1) && gotMonth) {
+ if (gotMonthName)
+ return false;
+
+ if (gotDay)
+ return false;
+
+ gotDay = true;
+ gotMonth = false;
+ entry.timeStruct.tm_mday = entry.timeStruct.tm_mon;
+ }
+
+ if (gotYear || gotDay) {
+ // Month field in yyyy-mm-dd or dd-mm-yyyy
+ // Check month name
+ QString dateMonth = token.getString().mid(pos + 1, pos2 - pos - 1);
+ dateMonth = dateMonth.lower();
+
+ QMap<QString, int>::iterator iter = m_monthNameMap.find(dateMonth);
+ if (iter == m_monthNameMap.end())
+ return false;
+
+ entry.timeStruct.tm_mon = iter.data();
+ gotMonth = true;
+ } else {
+ int value = token.getInteger(pos + 1, pos2 - pos - 1);
+
+ // Day field in mm-dd-yyyy
+ if (value < 1 || value > 31)
+ return false;
+
+ entry.timeStruct.tm_mday = value;
+ gotDay = true;
+ }
+
+ value = token.getInteger(pos2 + 1, token.getLength() - pos2 - 1);
+ if (gotYear) {
+ // Day field in yyy-mm-dd
+ if (!value || value > 31)
+ return false;
+
+ entry.timeStruct.tm_mday = value;
+ gotDay = true;
+ } else {
+ if (value < 0)
+ return false;
+
+ if (value < 50) {
+ value += 2000;
+ } else if (value < 1000) {
+ value += 1900;
+ }
+
+ entry.timeStruct.tm_year = value;
+ gotYear = true;
+ }
+
+ if (!gotMonth || !gotDay || !gotYear)
+ return false;
+
+ return true;
+}
+
+bool FtpDirectoryParser::parseDos(DLine *line, DirectoryEntry &entry)
+{
+ int index = 0;
+ DToken token;
+
+ // Get first token, has to be a valid date
+ if (!line->getToken(index, token))
+ return false;
+
+ if (!parseShortDate(token, entry))
+ return false;
+
+ // Extract time
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (!parseTime(token, entry))
+ return false;
+
+ // If next token is <DIR>, entry is a directory
+ // else, it should be the filesize.
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (token.getString() == "<DIR>") {
+ entry.setType('d');
+ entry.setSize(0);
+ } else if (token.isNumeric() || token.isLeftNumeric()) {
+ // Convert size, filter out separators
+ unsigned long size = 0;
+ int len = token.getLength();
+
+ for (int i = 0; i < len; i++) {
+ char chr = token[i];
+
+ if (chr == ',' || chr == '.')
+ continue;
+
+ if (chr < '0' || chr > '9')
+ return false;
+
+ size *= 10;
+ size += chr - '0';
+ }
+
+ entry.setSize(size);
+ entry.setType('f');
+ } else {
+ return false;
+ }
+
+ // Extract filename
+ if (!line->getToken(++index, token, true))
+ return false;
+
+ entry.setFilename(token.getString());
+
+ return true;
+}
+
+
+bool FtpDirectoryParser::parseTime(DToken &token, DirectoryEntry &entry)
+{
+ int pos = token.find(":");
+ if (pos < 1 || static_cast<unsigned int>(pos) >= (token.getLength() - 1))
+ return false;
+
+ int hour = token.getInteger(0, pos);
+ if (hour < 0 || hour > 23)
+ return false;
+
+ int minute = token.getInteger(pos + 1, 2);
+ if (minute < 0 || minute > 59)
+ return false;
+
+ // Convert to 24h format
+ if (!token.isRightNumeric()) {
+ if (token[token.getLength() - 2] == 'P') {
+ if (hour < 12) {
+ hour += 12;
+ }
+ } else if (hour == 12) {
+ hour = 0;
+ }
+ }
+
+ entry.timeStruct.tm_hour = hour;
+ entry.timeStruct.tm_min = minute;
+
+ return true;
+}
+
+bool FtpDirectoryParser::parseVms(DLine *line, DirectoryEntry &entry)
+{
+ DToken token;
+ int index = 0;
+
+ if (!line->getToken(index, token))
+ return false;
+
+ int pos = token.find(";");
+
+ if (pos == -1)
+ return false;
+
+ if (pos > 4 && token.getString().mid(pos - 4, 4) == ".DIR") {
+ entry.setType('d');
+ entry.setFilename(token.getString().left(pos - 4) + token.getString().mid(pos));
+ } else {
+ entry.setType('f');
+ entry.setFilename(token.getString());
+ }
+
+ // Get size
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (!token.isNumeric() && !token.isLeftNumeric())
+ return false;
+
+ entry.setSize(token.getInteger());
+
+ // Get date
+ if (!line->getToken(++index, token))
+ return false;
+
+ if (!parseShortDate(token, entry))
+ return false;
+
+ // Get time
+ if (!line->getToken(++index, token))
+ return true;
+
+ if (!parseTime(token, entry)) {
+ int len = token.getLength();
+
+ if (token[0] == '[' && token[len] != ']')
+ return false;
+ if (token[0] == '(' && token[len] != ')')
+ return false;
+ if (token[0] != '[' && token[len] == ']')
+ return false;
+ if (token[0] != '(' && token[len] == ')')
+ return false;
+
+ index--;
+ }
+
+ // Owner / group
+ while (line->getToken(++index, token)) {
+ int len = token.getLength();
+
+ if (len > 2 && token[0] == '(' && token[len - 1] == ')')
+ entry.setPermissions(0);
+ else if (len > 2 && token[0] == '[' && token[len - 1] == ']')
+ entry.setOwner(token.getString().mid(1, len - 2));
+ else
+ entry.setPermissions(0);
+ }
+
+ return true;
+}
+
+bool FtpDirectoryParser::parseComplexFileSize(DToken &token, filesize_t &size)
+{
+ if (token.isNumeric()) {
+ size = token.getInteger();
+ return true;
+ }
+
+ int len = token.getLength() - 1;
+
+ char last = token[len];
+ if (last == 'B' || last == 'b') {
+ char c = token[len];
+
+ if (c < '0' || c > '9') {
+ last = token[len];
+ len--;
+ }
+ }
+
+ size = 0;
+
+ int dot = -1;
+ for (int i = 0; i < len; i++) {
+ char c = token[i];
+
+ if (c >= '0' && c <= '9') {
+ size *= 10;
+ size += c - '0';
+ } else if (c == '.') {
+ if (dot != -1)
+ return false;
+
+ dot = len - i - 1;
+ } else {
+ return false;
+ }
+ }
+
+ switch (last) {
+ case 'k':
+ case 'K': {
+ size *= 1000;
+ break;
+ }
+ case 'm':
+ case 'M': {
+ size *= 1000 * 1000;
+ break;
+ }
+ case 'g':
+ case 'G': {
+ size *= 1000 * 1000 * 1000;
+ break;
+ }
+ case 't':
+ case 'T': {
+ size *= 1000 * 1000;
+ size *= 1000 * 1000;
+ break;
+ }
+ case 'b':
+ case 'B': break;
+ default: return false;
+ }
+
+ while (dot-- > 0)
+ size /= 10;
+
+ return true;
+}
+
+}
diff --git a/kftpgrabber/src/engine/ftpdirectoryparser.h b/kftpgrabber/src/engine/ftpdirectoryparser.h
new file mode 100644
index 0000000..1b56831
--- /dev/null
+++ b/kftpgrabber/src/engine/ftpdirectoryparser.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of the KFTPGrabber project
+ *
+ * Copyright (C) 2003-2004 by the KFTPGrabber developers
+ * Copyright (C) 2003-2004 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.
+ */
+
+#ifndef KFTPENGINEFTPDIRECTORYPARSER_H
+#define KFTPENGINEFTPDIRECTORYPARSER_H
+
+#include <qmap.h>
+
+#include "directorylisting.h"
+
+namespace KFTPEngine {
+
+class FtpSocket;
+
+class DToken;
+class DLine;
+
+/**
+ * This class can parse multiple directory formats. Some code portions have
+ * been taken from a windows FTP client "FileZilla by Tim Kosse" - the
+ * logic is mostly the same, the code has just been ported so it is more Qt
+ * and so it integrates nicely with the rest of the engine.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ * @author Tim Kosse <tim.kosse@gmx.de>
+ */
+class FtpDirectoryParser {
+public:
+ FtpDirectoryParser(FtpSocket *socket);
+
+ void addData(const char *data, int len);
+ void addDataLine(const QString &line);
+
+ bool parseLine(const QString &line, DirectoryEntry &entry);
+ DirectoryListing getListing() { return m_listing; }
+private:
+ FtpSocket *m_socket;
+ QString m_buffer;
+ DirectoryListing m_listing;
+
+ QMap<QString, int> m_monthNameMap;
+
+ bool parseMlsd(const QString &line, DirectoryEntry &entry);
+ bool parseUnix(DLine *line, DirectoryEntry &entry);
+ bool parseDos(DLine *line, DirectoryEntry &entry);
+ bool parseVms(DLine *line, DirectoryEntry &entry);
+
+ bool parseUnixDateTime(DLine *line, int &index, DirectoryEntry &entry);
+ bool parseShortDate(DToken &token, DirectoryEntry &entry);
+ bool parseTime(DToken &token, DirectoryEntry &entry);
+
+ bool parseComplexFileSize(DToken &token, filesize_t &size);
+
+ bool parseUnixPermissions(const QString &permissions, DirectoryEntry &entry);
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/ftpsocket.cpp b/kftpgrabber/src/engine/ftpsocket.cpp
new file mode 100644
index 0000000..2741f4d
--- /dev/null
+++ b/kftpgrabber/src/engine/ftpsocket.cpp
@@ -0,0 +1,2749 @@
+/*
+ * 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/config.h"
+
+#include <qdir.h>
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <ksocketdevice.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 QTimer's slot when ported to Qt 4
+ timeoutCheck();
+ keepaliveCheck();
+}
+
+void FtpSocket::slotControlTryRead()
+{
+ QString tmpStr;
+ Q_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) {
+ QString line = m_buffer.mid(0, pos);
+ line = m_remoteEncoding->decode(QCString(line.ascii()));
+ parseLine(line);
+
+ // Remove what we just parsed
+ m_buffer.remove(0, pos + 1);
+ }
+}
+
+void FtpSocket::parseLine(const QString &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 QString &code)
+{
+ QString ref;
+
+ if (isMultiline())
+ ref = m_multiLineCode;
+ else
+ ref = m_response;
+
+ return ref.left(code.length()) == code;
+}
+
+void FtpSocket::sendCommand(const QString &command)
+{
+ emitEvent(Event::EventCommand, command);
+ QCString 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.
+ QString 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;
+ QString 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"
+ QString 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()
+ {
+ QString 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(), QString::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();
+
+ QString 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 " + QString::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|)
+ 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(QString::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];
+ char *begin = strchr(socket()->getResponse().ascii(), '(');
+
+ // Some stinky servers don't respect RFC and do it on their own
+ if (!begin)
+ 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
+ QString 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::KSocketAddress address = socket()->setupActiveTransferSocket();
+ if (address.address()) {
+ if (socket()->getConfigInt("feat.eprt")) {
+ QString ianaFamily = QString::number(address.ianaFamily());
+
+ socket()->sendCommand("EPRT |" + ianaFamily + "|" + address.nodeName() + "|" + address.serviceName() + "|");
+ } else if (address.ianaFamily() == 1) {
+ QString format = address.nodeName().replace(".", ",");
+
+ format.append(",");
+ format.append(QString::number((unsigned char) address.address()->sa_data[0]));
+ format.append(",");
+ format.append(QString::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 QString &host, int port)
+{
+ // Use the host from control connection if empty
+ QString 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, QString::number(port));
+}
+
+KNetwork::KSocketAddress FtpSocket::setupActiveTransferSocket()
+{
+ if (!m_serverSocket)
+ m_serverSocket = new KNetwork::KServerSocket();
+
+ 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(QString::number(port));
+ bool success = m_serverSocket->listen();
+
+ if (found = (success && m_serverSocket->error() == KSocketBase::NoError))
+ break;
+
+ m_serverSocket->close();
+ }
+
+ if (!found) {
+ emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
+ resetCommandClass(Failed);
+ return KNetwork::KSocketAddress();
+ }
+ } else {
+ m_serverSocket->setAddress("0");
+
+ if (!m_serverSocket->listen()) {
+ emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
+ resetCommandClass(Failed);
+ return KNetwork::KSocketAddress();
+ }
+ }
+
+ KNetwork::KSocketAddress serverAddr = m_serverSocket->localAddress();
+ KNetwork::KSocketAddress controlAddr = localAddress();
+ KNetwork::KSocketAddress request;
+
+ if (KFTPCore::Config::portForceIp() && !getConfigInt("active.no_force_ip")) {
+ QString 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(Q_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;
+ }
+
+ QFile::Offset tmpOffset = getTransferFile()->at();
+ Q_LONG readSize = getTransferFile()->readBlock(m_transferBuffer, m_transferBufferSize);
+
+ Q_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);
+ }
+
+ Q_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: {
+ qDebug("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)
+
+ QString 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
+ QString 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};
+ QString 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 (QDir::root().exists(destinationFile.path())) {
+ socket()->protoStat(sourceFile);
+ currentState = StatDone;
+ return;
+ } else {
+ KStandardDirs::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)
+
+ QString targetDirectory;
+ QString currentPathPart;
+ QString 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")) {
+ QString 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 QString &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 (!QDir::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)
+
+ QString destinationPath;
+ QString 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)
+
+ QString sourcePath;
+ QString 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;
+
+ QString 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)
+
+ QString 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 QString &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;
+
+ QString 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 {
+ QString 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 " + QString::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;
+
+ QString 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;
+
+ QString 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;
+
+ QString 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);
+}
+
+}
diff --git a/kftpgrabber/src/engine/ftpsocket.h b/kftpgrabber/src/engine/ftpsocket.h
new file mode 100644
index 0000000..1762bfd
--- /dev/null
+++ b/kftpgrabber/src/engine/ftpsocket.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#ifndef KFTPENGINEFTPSOCKET_H
+#define KFTPENGINEFTPSOCKET_H
+
+#include <kstreamsocket.h>
+#include <kserversocket.h>
+#include <kssl.h>
+
+#include <qguardedptr.h>
+#include <qfile.h>
+
+#include "speedlimiter.h"
+#include "socket.h"
+
+namespace KFTPEngine {
+
+class FtpDirectoryParser;
+class Ssl;
+
+/**
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class FtpSocket : public KNetwork::KStreamSocket, public Socket, public SpeedLimiterItem
+{
+Q_OBJECT
+friend class Commands::Base;
+friend class FtpCommandConnect;
+friend class FtpCommandNegotiateData;
+friend class FtpCommandList;
+public:
+ FtpSocket(Thread *thread);
+ ~FtpSocket();
+
+ void protoConnect(const KURL &url);
+ void protoDisconnect();
+ void protoAbort();
+ void protoGet(const KURL &source, const KURL &destination);
+ void protoPut(const KURL &source, const KURL &destination);
+ void protoRemove(const KURL &path);
+ void protoRename(const KURL &source, const KURL &destination);
+ void protoChmodSingle(const KURL &path, int mode);
+ void protoMkdir(const KURL &path);
+ void protoList(const KURL &path);
+ void protoRaw(const QString &raw);
+ void protoSiteToSite(Socket *socket, const KURL &source, const KURL &destination);
+ void protoKeepAlive();
+
+ void changeWorkingDirectory(const QString &path, bool shouldCreate = false);
+
+ void poll();
+
+ int features() { return SF_FXP_TRANSFER | SF_RAW_COMMAND; }
+
+ bool isConnected() { return m_login; }
+ bool isEncrypted() { return isConnected() && getConfigInt("ssl"); }
+
+ void setSslClientCertificate(KSSLPKCS12 *certificate) { m_clientCert = certificate; }
+
+ bool isResponse(const QString &code);
+ QString getResponse() { return m_response; }
+ bool isMultiline() { return !m_multiLineCode.isEmpty(); }
+
+ void sendCommand(const QString &command);
+ void resetCommandClass(ResetCode code = Ok);
+
+ void setupPassiveTransferSocket(const QString &host, int port);
+ KNetwork::KSocketAddress setupActiveTransferSocket();
+
+ QFile *getTransferFile() { return &m_transferFile; }
+
+ void checkTransferEnd();
+ void checkTransferStart();
+ void resetTransferStart() { m_transferStart = 0; }
+protected:
+ void processBuffer();
+ void parseLine(const QString &line);
+ void variableBufferUpdate(Q_LONG size);
+ void closeDataTransferSocket();
+ void initializeTransferSocket();
+ void transferCompleted();
+private:
+ bool m_login;
+
+ QString m_buffer;
+ QString m_multiLineCode;
+ QString m_response;
+
+ QGuardedPtr<KNetwork::KStreamSocket> m_transferSocket;
+ QGuardedPtr<KNetwork::KServerSocket> m_serverSocket;
+ FtpDirectoryParser *m_directoryParser;
+
+ char m_controlBuffer[1024];
+
+ QFile m_transferFile;
+ char *m_transferBuffer;
+ int m_transferBufferSize;
+ int m_transferStart;
+ int m_transferEnd;
+
+ bool m_transferConnecting;
+ bool m_controlConnecting;
+
+ Ssl *m_controlSsl;
+ Ssl *m_dataSsl;
+ KSSLPKCS12 *m_clientCert;
+protected slots:
+ void slotConnected();
+ void slotControlTryRead();
+ void slotError();
+
+ void slotDataAccept(KNetwork::KStreamSocket *socket);
+ void slotDataConnected();
+ void slotDataTryRead();
+ void slotDataTryWrite();
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/sftpsocket.cpp b/kftpgrabber/src/engine/sftpsocket.cpp
new file mode 100644
index 0000000..a683723
--- /dev/null
+++ b/kftpgrabber/src/engine/sftpsocket.cpp
@@ -0,0 +1,775 @@
+/*
+ * 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 "sftpsocket.h"
+#include "cache.h"
+#include "misc/config.h"
+
+#include <qdir.h>
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kio/job.h>
+#include <kio/renamedlg.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+namespace KFTPEngine {
+
+SftpSocket::SftpSocket(Thread *thread)
+ : Socket(thread, "sftp"),
+ m_login(false)
+{
+}
+
+SftpSocket::~SftpSocket()
+{
+}
+
+int addPermInt(int &x, int n, int add)
+{
+ if (x >= n) {
+ x -= n;
+ return add;
+ } else {
+ return 0;
+ }
+}
+
+int SftpSocket::intToPosix(int permissions)
+{
+ int posix = 0;
+ QString str = QString::number(permissions);
+
+ int user = str.mid(0, 1).toInt();
+ int group = str.mid(1, 1).toInt();
+ int other = str.mid(2, 1).toInt();
+
+ posix |= addPermInt(user, 4, S_IRUSR);
+ posix |= addPermInt(user, 2, S_IWUSR);
+ posix |= addPermInt(user, 1, S_IXUSR);
+
+ posix |= addPermInt(group, 4, S_IRGRP);
+ posix |= addPermInt(group, 2, S_IWGRP);
+ posix |= addPermInt(group, 1, S_IXGRP);
+
+ posix |= addPermInt(other, 4, S_IROTH);
+ posix |= addPermInt(other, 2, S_IWOTH);
+ posix |= addPermInt(other, 1, S_IXOTH);
+
+ return posix;
+}
+
+
+// *******************************************************************************************
+// ***************************************** CONNECT *****************************************
+// *******************************************************************************************
+
+class SftpCommandConnect : public Commands::Base {
+public:
+ enum State {
+ None,
+ ConnectComplete,
+ LoginComplete
+ };
+
+ ENGINE_STANDARD_COMMAND_CONSTRUCTOR(SftpCommandConnect, SftpSocket, CmdConnect)
+
+ void process()
+ {
+ KURL url = socket()->getCurrentUrl();
+
+ switch (currentState) {
+ case None: {
+ // Set connection info
+ SSH_OPTIONS *sshOptions = options_new();
+ options_set_username(sshOptions, (char*) url.user().ascii());
+ options_set_host(sshOptions, url.host().ascii());
+ options_set_port(sshOptions, url.port());
+ options_set_timeout(sshOptions, 10, 0);
+
+ socket()->m_sftpSession = 0;
+ socket()->m_sshSession = ssh_connect(sshOptions);
+
+ if (!socket()->sshSession()) {
+ socket()->emitEvent(Event::EventMessage, i18n("Unable to establish SSH connection (%1)").arg(ssh_get_error(0)));
+ socket()->emitError(ConnectFailed);
+ return;
+ }
+
+ socket()->emitEvent(Event::EventState, i18n("Logging in..."));
+ socket()->emitEvent(Event::EventMessage, i18n("Connected with server, attempting to login..."));
+
+ currentState = ConnectComplete;
+ }
+ case ConnectComplete: {
+ SSH_SESSION *sshSession = socket()->sshSession();
+ QString password;
+
+ // Check if a public key password was supplied using the wakeup event
+ if (isWakeup()) {
+ PubkeyWakeupEvent *event = static_cast<PubkeyWakeupEvent*>(m_wakeupEvent);
+ password = event->password;
+ }
+
+ // Try the public key auth with the set password (if any)
+ int pkey_ret = ssh_userauth_autopubkey(sshSession, (char*) password.ascii());
+ if (pkey_ret == -666) {
+ // Make a password request
+ socket()->emitEvent(Event::EventPubkeyPassword);
+ return;
+ } else if (pkey_ret != SSH_AUTH_SUCCESS) {
+ // First let's try the keyboard-interactive authentification
+ if (keyboardInteractiveLogin() != SSH_AUTH_SUCCESS) {
+ // If this fails, let's try the password authentification
+ if (ssh_userauth_password(sshSession, NULL, (char*) url.pass().ascii()) != SSH_AUTH_SUCCESS) {
+ socket()->emitEvent(Event::EventMessage, i18n("Login has failed."));
+ socket()->emitError(LoginFailed);
+
+ socket()->protoAbort();
+ return;
+ }
+ } else {
+ socket()->emitEvent(Event::EventMessage, i18n("Keyboard-interactive authentication succeeded."));
+ }
+ } else {
+ socket()->emitEvent(Event::EventMessage, i18n("Public key authentication succeeded."));
+ }
+
+ currentState = LoginComplete;
+ }
+ case LoginComplete: {
+ socket()->m_sftpSession = sftp_new(socket()->sshSession());
+
+ if (!socket()->sftpSession()) {
+ socket()->emitEvent(Event::EventMessage, i18n("Unable to initialize SFTP channel."));
+ socket()->emitError(LoginFailed);
+
+ socket()->protoAbort();
+ return;
+ }
+
+ if (sftp_init(socket()->sftpSession())) {
+ socket()->emitEvent(Event::EventMessage, i18n("Unable to initialize SFTP."));
+ socket()->emitError(LoginFailed);
+
+ socket()->protoAbort();
+ return;
+ }
+
+ // Get the current directory
+ char *cwd = sftp_canonicalize_path(socket()->sftpSession(), "./");
+ socket()->setDefaultDirectory(socket()->remoteEncoding()->decode(cwd));
+ socket()->setCurrentDirectory(socket()->remoteEncoding()->decode(cwd));
+ delete cwd;
+
+ socket()->emitEvent(Event::EventMessage, i18n("Connected."));
+ socket()->emitEvent(Event::EventConnect);
+ socket()->m_login = true;
+
+ socket()->resetCommandClass();
+ break;
+ }
+ }
+ }
+
+ int keyboardInteractiveLogin()
+ {
+ int err = ssh_userauth_kbdint(socket()->sshSession(), NULL, NULL);
+ char *name, *instruction, *prompt;
+ int i, n;
+ char echo;
+
+ while (err == SSH_AUTH_INFO) {
+ name = ssh_userauth_kbdint_getname(socket()->sshSession());
+ instruction = ssh_userauth_kbdint_getinstruction(socket()->sshSession());
+ n = ssh_userauth_kbdint_getnprompts(socket()->sshSession());
+
+ // FIXME Name and instruction are currently ignored. The libssh API reference
+ // suggests displaying an interactive dialog box for the user to supply the
+ // information requested from the server.
+
+ for(i = 0; i < n; ++i) {
+ prompt = ssh_userauth_kbdint_getprompt(socket()->sshSession(), i, &echo);
+
+ if (!echo) {
+ // We should send the password (since only the password should be masked)
+ ssh_userauth_kbdint_setanswer(socket()->sshSession(), i, (char*) socket()->getCurrentUrl().pass().ascii());
+ } else {
+ // FIXME Server requests something else ?
+ }
+ }
+
+ err = ssh_userauth_kbdint(socket()->sshSession(), NULL, NULL);
+ }
+
+ return err;
+ }
+};
+
+void SftpSocket::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"));
+
+ // Connect to the remote host
+ setCurrentUrl(url);
+ activateCommandClass(SftpCommandConnect);
+}
+
+// *******************************************************************************************
+// **************************************** DISCONNECT ***************************************
+// *******************************************************************************************
+
+void SftpSocket::protoDisconnect()
+{
+ Socket::protoDisconnect();
+
+ if (m_sftpSession)
+ sftp_free(m_sftpSession);
+
+ ssh_disconnect(m_sshSession);
+ m_sshSession = 0;
+
+ m_login = false;
+}
+
+void SftpSocket::protoAbort()
+{
+ Socket::protoAbort();
+
+ if (getCurrentCommand() == Commands::CmdGet || getCurrentCommand() == Commands::CmdPut) {
+ // Abort current command
+ resetCommandClass(UserAbort);
+ emitEvent(Event::EventMessage, i18n("Aborted."));
+ }
+}
+
+// *******************************************************************************************
+// ******************************************* LIST ******************************************
+// *******************************************************************************************
+
+class SftpCommandList : public Commands::Base {
+public:
+ enum State {
+ None
+ };
+
+ ENGINE_STANDARD_COMMAND_CONSTRUCTOR(SftpCommandList, SftpSocket, CmdList)
+
+ void process()
+ {
+ // 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_lastDirectoryListing = DirectoryListing(socket()->getCurrentDirectory());
+
+ SFTP_DIR *m_dir = sftp_opendir(socket()->sftpSession(), socket()->remoteEncoding()->encode(socket()->getCurrentDirectory()).data());
+ if (!m_dir) {
+ if (socket()->errorReporting()) {
+ socket()->emitError(ListFailed);
+ socket()->resetCommandClass(Failed);
+ } else
+ socket()->resetCommandClass();
+ return;
+ }
+
+ // Read the specified directory
+ SFTP_ATTRIBUTES *file;
+ DirectoryEntry entry;
+
+ while ((file = sftp_readdir(socket()->sftpSession(), m_dir))) {
+ entry.setFilename(file->name);
+
+ if (entry.filename() != "." && entry.filename() != "..") {
+ entry.setFilename(socket()->remoteEncoding()->decode(entry.filename().ascii()));
+ entry.setOwner(file->owner);
+ entry.setGroup(file->group);
+ entry.setTime(file->mtime);
+ entry.setSize(file->size);
+ entry.setPermissions(file->permissions);
+
+ if (file->permissions & S_IFDIR)
+ entry.setType('d');
+ else
+ entry.setType('f');
+
+ socket()->m_lastDirectoryListing.addEntry(entry);
+ }
+
+ sftp_attributes_free(file);
+ }
+
+ sftp_dir_close(m_dir);
+
+ // Cache the directory listing
+ Cache::self()->addDirectory(socket(), socket()->m_lastDirectoryListing);
+
+ if (!socket()->isChained())
+ socket()->emitEvent(Event::EventDirectoryListing, socket()->m_lastDirectoryListing);
+ socket()->resetCommandClass();
+ }
+};
+
+void SftpSocket::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
+ setCurrentDirectory(path.path());
+
+ activateCommandClass(SftpCommandList);
+}
+
+// *******************************************************************************************
+// ******************************************* GET *******************************************
+// *******************************************************************************************
+
+class SftpCommandGet : public Commands::Base {
+public:
+ enum State {
+ None,
+ WaitStat,
+ DestChecked
+ };
+
+ ENGINE_STANDARD_COMMAND_CONSTRUCTOR(SftpCommandGet, SftpSocket, CmdGet)
+
+ KURL sourceFile;
+ KURL destinationFile;
+ filesize_t resumeOffset;
+
+ void process()
+ {
+ switch (currentState) {
+ case None: {
+ // Stat source file
+ resumeOffset = 0;
+ sourceFile.setPath(socket()->getConfig("params.get.source"));
+ destinationFile.setPath(socket()->getConfig("params.get.destination"));
+
+ currentState = WaitStat;
+ socket()->protoStat(sourceFile);
+ break;
+ }
+ case WaitStat: {
+ socket()->emitEvent(Event::EventState, i18n("Transfering..."));
+
+ if (socket()->getStatResponse().filename().isEmpty()) {
+ socket()->emitError(FileNotFound);
+ socket()->resetCommandClass(Failed);
+ return;
+ }
+
+ if (QDir::root().exists(destinationFile.path())) {
+ DirectoryListing list;
+ list.addEntry(socket()->getStatResponse());
+
+ currentState = DestChecked;
+ socket()->emitEvent(Event::EventFileExists, list);
+ return;
+ } else
+ KStandardDirs::makeDir(destinationFile.directory());
+ }
+ case DestChecked: {
+ QFile file;
+
+ if (isWakeup()) {
+ // We have been waken up because a decision has been made
+ FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
+
+ switch (event->action) {
+ case FileExistsWakeupEvent::Rename: {
+ // Change the destination filename, otherwise it is the same as overwrite
+ destinationFile.setPath(event->newFileName);
+ }
+ case FileExistsWakeupEvent::Overwrite: {
+ file.setName(destinationFile.path());
+ file.open(IO_WriteOnly | IO_Truncate);
+ break;
+ }
+ case FileExistsWakeupEvent::Resume: {
+ file.setName(destinationFile.path());
+ file.open(IO_WriteOnly | IO_Append);
+
+ // Signal resume
+ resumeOffset = file.size();
+ socket()->emitEvent(Event::EventResumeOffset, resumeOffset);
+ 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
+ file.setName(destinationFile.path());
+ file.open(IO_WriteOnly | IO_Truncate);
+ }
+
+ // Download the file
+ SFTP_FILE *rfile = sftp_open(socket()->sftpSession(), socket()->remoteEncoding()->encode(sourceFile.path()).data(), O_RDONLY, 0);
+ if (!rfile) {
+ file.close();
+ socket()->resetCommandClass(Failed);
+ return;
+ }
+
+ if (resumeOffset > 0)
+ sftp_seek(rfile, resumeOffset);
+
+ char buffer[16384];
+ int size;
+
+ do {
+ size = sftp_read(rfile, buffer, sizeof(buffer));
+
+ if (size > 0) {
+ file.writeBlock(buffer, size);
+ socket()->m_transferBytes += size;
+ }
+
+ if (socket()->shouldAbort())
+ break;
+ } while (size);
+
+ sftp_file_close(rfile);
+ file.close();
+
+ socket()->emitEvent(Event::EventTransferComplete);
+ socket()->resetCommandClass();
+ break;
+ }
+ }
+ }
+};
+
+void SftpSocket::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());
+
+ m_transferBytes = 0;
+
+ m_speedLastTime = time(0);
+ m_speedLastBytes = 0;
+
+ activateCommandClass(SftpCommandGet);
+}
+
+// *******************************************************************************************
+// ******************************************* PUT *******************************************
+// *******************************************************************************************
+
+class SftpCommandPut : public Commands::Base {
+public:
+ enum State {
+ None,
+ WaitStat,
+ DestChecked
+ };
+
+ ENGINE_STANDARD_COMMAND_CONSTRUCTOR(SftpCommandPut, SftpSocket, CmdPut)
+
+ KURL sourceFile;
+ KURL destinationFile;
+ filesize_t resumeOffset;
+
+ void process()
+ {
+ switch (currentState) {
+ case None: {
+ // Stat source file
+ resumeOffset = 0;
+ sourceFile.setPath(socket()->getConfig("params.get.source"));
+ destinationFile.setPath(socket()->getConfig("params.get.destination"));
+
+ if (!QDir::root().exists(sourceFile.path())) {
+ socket()->emitError(FileNotFound);
+ socket()->resetCommandClass(Failed);
+ return;
+ }
+
+ currentState = WaitStat;
+ socket()->protoStat(destinationFile);
+ break;
+ }
+ case WaitStat: {
+ socket()->emitEvent(Event::EventState, i18n("Transfering..."));
+
+ if (!socket()->getStatResponse().filename().isEmpty()) {
+ DirectoryListing list;
+ list.addEntry(socket()->getStatResponse());
+
+ currentState = DestChecked;
+ socket()->emitEvent(Event::EventFileExists, list);
+ return;
+ } else {
+ // Create destination directories
+ socket()->setErrorReporting(false);
+
+ QString destinationDir = destinationFile.directory();
+ QString fullPath;
+
+ for (register int i = 1; i <= destinationDir.contains('/'); i++) {
+ fullPath += "/" + destinationDir.section('/', i, i);
+
+ // Create the directory
+ socket()->protoMkdir(fullPath);
+ }
+ }
+ }
+ case DestChecked: {
+ QFile file;
+
+ if (isWakeup()) {
+ // We have been waken up because a decision has been made
+ FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
+
+ switch (event->action) {
+ case FileExistsWakeupEvent::Rename: {
+ // Change the destination filename, otherwise it is the same as overwrite
+ destinationFile.setPath(event->newFileName);
+ }
+ case FileExistsWakeupEvent::Overwrite: {
+ file.setName(sourceFile.path());
+ file.open(IO_ReadOnly);
+ break;
+ }
+ case FileExistsWakeupEvent::Resume: {
+ resumeOffset = socket()->getStatResponse().size();
+
+ file.setName(sourceFile.path());
+ file.open(IO_ReadOnly);
+ file.at(resumeOffset);
+
+ // Signal resume
+ socket()->emitEvent(Event::EventResumeOffset, resumeOffset);
+ 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
+ file.setName(sourceFile.path());
+ file.open(IO_ReadOnly);
+ }
+
+ // Download the file
+ SFTP_FILE *rfile;
+
+ if (resumeOffset > 0) {
+ rfile = sftp_open(socket()->sftpSession(), socket()->remoteEncoding()->encode(destinationFile.path()).data(), O_WRONLY | O_APPEND, 0);
+ sftp_seek(rfile, resumeOffset);
+ } else
+ rfile = sftp_open(socket()->sftpSession(), socket()->remoteEncoding()->encode(destinationFile.path()).data(), O_WRONLY | O_CREAT, 0);
+
+ if (!rfile) {
+ file.close();
+ socket()->resetCommandClass(Failed);
+ return;
+ }
+
+ char buffer[16384];
+ int size;
+
+ do {
+ size = file.readBlock(buffer, sizeof(buffer));
+
+ if (size > 0) {
+ sftp_write(rfile, buffer, size);
+ socket()->m_transferBytes += size;
+ }
+
+ if (socket()->shouldAbort())
+ break;
+ } while (size);
+
+ sftp_file_close(rfile);
+ file.close();
+
+ socket()->emitEvent(Event::EventTransferComplete);
+ socket()->resetCommandClass();
+ break;
+ }
+ }
+ }
+};
+
+void SftpSocket::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());
+
+ m_transferBytes = 0;
+
+ m_speedLastTime = time(0);
+ m_speedLastBytes = 0;
+
+ activateCommandClass(SftpCommandPut);
+}
+
+// *******************************************************************************************
+// **************************************** REMOVE *******************************************
+// *******************************************************************************************
+
+void SftpSocket::protoRemove(const KURL &path)
+{
+ emitEvent(Event::EventState, i18n("Removing..."));
+
+ // Remove a file or directory
+ int result = 0;
+
+ if (getConfigInt("params.remove.directory"))
+ result = sftp_rmdir(m_sftpSession, remoteEncoding()->encode(path.path()).data());
+ else
+ result = sftp_rm(m_sftpSession, remoteEncoding()->encode(path.path()).data());
+
+ if (result < 0) {
+ resetCommandClass(Failed);
+ } else {
+ // Invalidate cached parent entry (if any)
+ Cache::self()->invalidateEntry(this, path.directory());
+
+ emitEvent(Event::EventReloadNeeded);
+ resetCommandClass();
+ }
+}
+
+// *******************************************************************************************
+// **************************************** RENAME *******************************************
+// *******************************************************************************************
+
+void SftpSocket::protoRename(const KURL &source, const KURL &destination)
+{
+ emitEvent(Event::EventState, i18n("Renaming..."));
+
+ if (sftp_rename(m_sftpSession, remoteEncoding()->encode(source.path()).data(), remoteEncoding()->encode(destination.path()).data()) < 0) {
+ resetCommandClass(Failed);
+ } else {
+ // Invalidate cached parent entry (if any)
+ Cache::self()->invalidateEntry(this, source.directory());
+ Cache::self()->invalidateEntry(this, destination.directory());
+
+ emitEvent(Event::EventReloadNeeded);
+ resetCommandClass();
+ }
+}
+
+// *******************************************************************************************
+// **************************************** CHMOD ********************************************
+// *******************************************************************************************
+
+void SftpSocket::protoChmodSingle(const KURL &path, int mode)
+{
+ emitEvent(Event::EventState, i18n("Changing mode..."));
+
+ SFTP_ATTRIBUTES *attrs = static_cast<SFTP_ATTRIBUTES*>(new SFTP_ATTRIBUTES);
+ memset(attrs, 0, sizeof(*attrs));
+
+ attrs->permissions = intToPosix(mode);
+ attrs->flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ sftp_setstat(m_sftpSession, remoteEncoding()->encode(path.path()).data(), attrs);
+ sftp_attributes_free(attrs);
+
+ // Invalidate cached parent entry (if any)
+ Cache::self()->invalidateEntry(this, path.directory());
+
+ emitEvent(Event::EventReloadNeeded);
+ resetCommandClass();
+}
+
+// *******************************************************************************************
+// **************************************** MKDIR ********************************************
+// *******************************************************************************************
+
+void SftpSocket::protoMkdir(const KURL &path)
+{
+ SFTP_ATTRIBUTES *attrs = static_cast<SFTP_ATTRIBUTES*>(new SFTP_ATTRIBUTES);
+ memset(attrs, 0, sizeof(*attrs));
+
+ if (sftp_mkdir(m_sftpSession, remoteEncoding()->encode(path.path()).data(), attrs) < 0) {
+ if (errorReporting())
+ resetCommandClass(Failed);
+ } else {
+ // Invalidate cached parent entry (if any)
+ Cache::self()->invalidateEntry(this, path.directory());
+
+ if (errorReporting()) {
+ emitEvent(Event::EventReloadNeeded);
+ resetCommandClass();
+ }
+ }
+
+ delete attrs;
+}
+
+}
diff --git a/kftpgrabber/src/engine/sftpsocket.h b/kftpgrabber/src/engine/sftpsocket.h
new file mode 100644
index 0000000..f34a896
--- /dev/null
+++ b/kftpgrabber/src/engine/sftpsocket.h
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#ifndef KFTPENGINESFTPSOCKET_H
+#define KFTPENGINESFTPSOCKET_H
+
+// LibSSH includes
+#include "misc/libs/ssh/libssh.h"
+#include "misc/libs/ssh/sftp.h"
+
+#include "socket.h"
+
+namespace KFTPEngine {
+
+/**
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class SftpSocket : public Socket {
+friend class SftpCommandConnect;
+friend class SftpCommandList;
+friend class SftpCommandGet;
+friend class SftpCommandPut;
+public:
+ SftpSocket(Thread *thread);
+ ~SftpSocket();
+
+ void protoConnect(const KURL &url);
+ void protoDisconnect();
+ void protoAbort();
+ void protoGet(const KURL &source, const KURL &destination);
+ void protoPut(const KURL &source, const KURL &destination);
+ void protoRemove(const KURL &path);
+ void protoRename(const KURL &source, const KURL &destination);
+ void protoChmodSingle(const KURL &path, int mode);
+ void protoMkdir(const KURL &path);
+ void protoList(const KURL &path);
+
+ void poll() {};
+
+ int features() { return 0; }
+
+ bool isConnected() { return m_login; }
+ bool isEncrypted() { return true; }
+
+ SSH_SESSION *sshSession() { return m_sshSession; }
+ SFTP_SESSION *sftpSession() { return m_sftpSession; }
+private:
+ QString posixToString(int permissions);
+ int intToPosix(int permissions);
+private:
+ SSH_SESSION *m_sshSession;
+ SFTP_SESSION *m_sftpSession;
+
+ bool m_login;
+};
+
+}
+
+#endif
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 &param1)
+{
+ // 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 &param1, const QString &param2)
+{
+ 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);
+ }
+}
+
+}
diff --git a/kftpgrabber/src/engine/socket.h b/kftpgrabber/src/engine/socket.h
new file mode 100644
index 0000000..3c2296a
--- /dev/null
+++ b/kftpgrabber/src/engine/socket.h
@@ -0,0 +1,605 @@
+/*
+ * 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.
+ */
+#ifndef KFTPENGINESOCKET_H
+#define KFTPENGINESOCKET_H
+
+#include <kurl.h>
+#include <kremoteencoding.h>
+
+#include <qptrlist.h>
+#include <qguardedptr.h>
+#include <qdatetime.h>
+
+#include "commands.h"
+
+namespace KFTPEngine {
+
+class ConnectionRetry;
+
+enum SocketFeatures {
+ SF_FXP_TRANSFER = 1,
+ SF_RAW_COMMAND = 2
+};
+
+/**
+ * The socket class provides an abstract class for all implemented protocols. It
+ * provides basic methods and also some remote operations (recursive scan,
+ * recursive removal).
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class Socket
+{
+friend class Thread;
+friend class FtpCommandStat;
+public:
+ /**
+ * Constructs a new socket.
+ *
+ * @param thread The thread that created this socket
+ * @param protocol The protocol name
+ */
+ Socket(Thread *thread, const QString &protocol);
+ ~Socket();
+
+ /**
+ * Set an internal config value.
+ *
+ * @param key Key
+ * @param value Value
+ */
+ void setConfig(const QString &key, const QString &value) { m_config[key] = value; }
+
+ /**
+ * Set an internal config value.
+ *
+ * @param key Key
+ * @param value Value
+ */
+ void setConfig(const QString &key, int value) { m_config[key] = QString::number(value); }
+
+ /**
+ * Set an internal config value.
+ *
+ * @param key Key
+ * @param value Value
+ */
+ void setConfig(const QString &key, filesize_t value) { m_config[key] = QString::number(value); }
+
+ /**
+ * Get an internal config value as string.
+ *
+ * @param key Key
+ * @return The key's value or an empty string if the key doesn't exist
+ */
+ QString getConfig(const QString &key) { return m_config[key]; }
+
+ /**
+ * Get an internal config value as an integer.
+ *
+ * @param key Key
+ * @return The key's value or 0 if the key doesn't exist
+ */
+ int getConfigInt(const QString &key) { return m_config[key].toInt(); }
+
+ /**
+ * Get an internal config value as filesize.
+ *
+ * @param key Key
+ * @return The key's value or 0 if the key doesn't exist
+ */
+ filesize_t getConfigFs(const QString &key) { return m_config[key].toULongLong(); }
+
+ /**
+ * This method should initialize the configuration map.
+ */
+ virtual void initConfig();
+
+ /**
+ * This method should trigger the connection process.
+ *
+ * @param url Remote url to connect to
+ */
+ virtual void protoConnect(const KURL &url) = 0;
+
+ /**
+ * This method should disconnect from the remote host.
+ */
+ virtual void protoDisconnect();
+
+ /**
+ * This method should abort any ongoing action.
+ */
+ virtual void protoAbort();
+
+ /**
+ * This method should download a remote file and save it localy.
+ *
+ * @param source The source url
+ * @param destination The destination url
+ */
+ virtual void protoGet(const KURL &source, const KURL &destination) = 0;
+
+ /**
+ * This method should upload a local file and save it remotely.
+ *
+ * @param source The source url
+ * @param destination The destination url
+ */
+ virtual void protoPut(const KURL &source, const KURL &destination) = 0;
+
+ /**
+ * Each protocol should implement this method. It should remove just one
+ * single entry. A config variable "params.remove.directory" will be set
+ * to 1 if the entry to remove is a directory and to 0 if it should expect
+ * a file.
+ *
+ * @warning You should NOT use this method directly! Use @ref protoDelete
+ * instead!
+ * @param path The path to the entry to remove
+ */
+ virtual void protoRemove(const KURL &path) = 0;
+
+ /**
+ * This method should rename/move a remote file.
+ *
+ * @param source The source file path
+ * @param destination The destination file path
+ */
+ virtual void protoRename(const KURL &source, const KURL &destination) = 0;
+
+ /**
+ * This method should change file's mode.
+ *
+ * * @warning You should NOT use this method directly! Use @ref protoChmod
+ * instead!
+ * @param path The file's path
+ * @param mode The new file mode
+ */
+ virtual void protoChmodSingle(const KURL &path, int mode) = 0;
+
+ /**
+ * This method should create a new remote directory.
+ *
+ * @param path Path of the newly created remote directory
+ */
+ virtual void protoMkdir(const KURL &path) = 0;
+
+ /**
+ * This method should fetch the remote directory listing for a specified
+ * directory. Note that this method could be called as a chained command,
+ * so it MUST NOT emit an EventDirectoryListing event if isChained returns
+ * true! In this case it should save the directory listing to the
+ * m_lastDirectoryListing member variable.
+ *
+ * @param path The path to list
+ */
+ virtual void protoList(const KURL &path) = 0;
+
+ /**
+ * This method should fetch the information about the given path. It is
+ * usualy called as a chained command.
+ *
+ * @param path The path to stat
+ */
+ virtual void protoStat(const KURL &path);
+
+ /**
+ * This method should send a raw command in case the protocol supports it
+ * (the SF_RAW_COMMAND is among features).
+ *
+ * @param command The command to send
+ */
+ virtual void protoRaw(const QString&) {}
+
+ /**
+ * This method should initiate a site to site transfer in case the protocol
+ * supports it (the SF_FXP_TRANSFER is among features).
+ *
+ * @param socket The destination socket
+ * @param source The source url
+ * @param destination The destination url
+ */
+ virtual void protoSiteToSite(Socket*, const KURL&, const KURL&) {}
+
+ /**
+ * Send a packet to keep the connection alive.
+ */
+ virtual void protoKeepAlive() {}
+
+ /**
+ * Recursively scan a directory and emit a DirectoryTree that can be used to
+ * create new transfers for addition to the queue.
+ *
+ * @param path The path to recursively scan
+ */
+ void protoScan(const KURL &path);
+
+ /**
+ * Identify if the remote path is a file or a directory and recursively remove
+ * it if so. The difference between this command and @ref protoRemove is, that
+ * protoRemove removes just one entry, and doesn't identify file type.
+ *
+ * @param path The path to remove
+ */
+ void protoDelete(const KURL &path);
+
+ /**
+ * Change file or directory mode. Also supports recursive mode changes.
+ *
+ * @param path The file's path
+ * @param mode The new file mode
+ * @param recursive Should the mode be recursively changed
+ */
+ void protoChmod(const KURL &path, int mode, bool recursive);
+
+ /**
+ * Returns this socket's parent thread.
+ *
+ * @return Socket's parent thread
+ */
+ Thread *thread() { return m_thread; }
+
+ /**
+ * Returns the protocol name of this socket.
+ *
+ * @return This socket's protocol name
+ */
+ QString protocolName() { return m_protocol; }
+
+ /**
+ * This method should return the socket's features by or-ing the values in
+ * SocketFeatures enum.
+ *
+ * @return Socket's features
+ */
+ virtual int features() = 0;
+
+ /**
+ * This method should return true if this socket is connected.
+ *
+ * @return True if the socket has successfully connected
+ */
+ virtual bool isConnected() = 0;
+
+ /**
+ * This method should return true if the connection is encrypted by some method.
+ *
+ * @return True if the connection is encrypted
+ */
+ virtual bool isEncrypted() = 0;
+
+ /**
+ * Returns true if the socket is currently busy performing an action.
+ *
+ * @return True if the socket is busy
+ */
+ virtual bool isBusy() { return m_currentCommand != Commands::CmdNone; }
+
+ /**
+ * Emit an engine error code.
+ *
+ * @param code The error code
+ * @param param1 Optional string parameter
+ */
+ void emitError(ErrorCode code, const QString &param1 = 0);
+
+ /**
+ * Emit an engine event.
+ *
+ * @param type Event type
+ * @param param1 Optional string parameter
+ * @param param2 Optional string parameter
+ */
+ void emitEvent(Event::Type type, const QString &param1 = 0, const QString &param2 = 0);
+
+ /**
+ * Emit an engine event containing a directory listing.
+ *
+ * @param type Event type
+ * @param param1 The DirectoryListing parameter
+ */
+ void emitEvent(Event::Type type, DirectoryListing param1);
+
+ /**
+ * Emit an engine event containing a filesize.
+ *
+ * @param type Event type
+ * @param param1 The filesize parameter
+ */
+ void emitEvent(Event::Type type, filesize_t param1);
+
+ /**
+ * Emit an engine event containing a custom pointer.
+ *
+ * @param type Event type
+ * @param param1 The custom pointer parameter
+ */
+ void emitEvent(Event::Type type, void *param1);
+
+ /**
+ * This method will set the socket's remote encoding which will be used when
+ * converting filenames into UTF-8 and back.
+ *
+ * @param encoding A valid encoding name
+ */
+ virtual void changeEncoding(const QString &encoding);
+
+ /**
+ * Retrieve the KRemoteEncoding object for this socket set to the appropriate
+ * encoding.
+ *
+ * @return The KRemoteEncoding object
+ */
+ KRemoteEncoding *remoteEncoding() { return m_remoteEncoding; }
+
+ /**
+ * Sets the current directory path.
+ *
+ * @param path The current directory path
+ */
+ void setCurrentDirectory(const QString &path) { m_currentDirectory = path; }
+
+ /**
+ * Get the current directory path.
+ *
+ * @return The current directory path.
+ */
+ virtual QString getCurrentDirectory() { return m_currentDirectory; }
+
+ /**
+ * Sets the default directory path (like a remote home directory).
+ *
+ * @param path The default directory path
+ */
+ void setDefaultDirectory(const QString &path) { m_defaultDirectory = path; }
+
+ /**
+ * Get the default directory path.
+ *
+ * @return The default directory path
+ */
+ virtual QString getDefaultDirectory() { return m_defaultDirectory; }
+
+ /**
+ * Sets the url this socket is connected to.
+ *
+ * @param url The url this socket is connected to
+ */
+ void setCurrentUrl(const KURL &url) { m_currentUrl = url; }
+
+ /**
+ * Get the url this socket is connected to.
+ *
+ * @return The url this socket is currently connected to
+ */
+ KURL getCurrentUrl() { return m_currentUrl; }
+
+ /**
+ * Sets the command the socket is currently executing.
+ *
+ * @param type Command type
+ */
+ void setCurrentCommand(Commands::Type type) { m_currentCommand = type; }
+
+ /**
+ * Get the current socket command.
+ *
+ * @return The current socket command
+ */
+ Commands::Type getCurrentCommand();
+
+ /**
+ * Get the toplevel socket command in the command chain.
+ *
+ * @return The toplevel socket command
+ */
+ Commands::Type getToplevelCommand();
+
+ /**
+ * Get the command that executed the current command. Note that this
+ * is valid only if the current command is chained. Otherwise this
+ * method returns Commands::CmdNone.
+ *
+ * @return The previous command
+ */
+ Commands::Type getPreviousCommand();
+
+ /**
+ * Get the last directory listing made by protoList.
+ *
+ * @return The last directory listing
+ */
+ DirectoryListing getLastDirectoryListing() { return m_lastDirectoryListing; }
+
+ /**
+ * Get the last stat response made by protoStat.
+ *
+ * @return The last stat response
+ */
+ DirectoryEntry getStatResponse() { return m_lastStatResponse; }
+
+ /**
+ * Get the number of bytes transfered from the beginning of the transfer.
+ *
+ * @return The number of bytes transfered
+ */
+ filesize_t getTransferBytes() { return m_transferBytes; }
+
+ /**
+ * Get the current transfer speed.
+ *
+ * @return The current transfer speed.
+ */
+ filesize_t getTransferSpeed();
+
+ /**
+ * This method will be called every cycle. It should be usually used to
+ * poll the data transfer socket.
+ */
+ virtual void poll() = 0;
+
+ /**
+ * Wakeup the last command processor with a specific wakeup event. This
+ * is used for async two-way communication between the engine and the
+ * GUI (wakeup event is a reply from the GUI).
+ *
+ * By default this method just passes the event to the currently active
+ * command processor.
+ *
+ * @param event The wakeup event that should be passed to the command class
+ */
+ virtual void wakeup(WakeupEvent *event);
+
+ /**
+ * Reset the current command class, possibly invoking the calling chained
+ * command class or completing the operation.
+ *
+ * @param code The result code
+ */
+ virtual void resetCommandClass(ResetCode code = Ok);
+
+ /**
+ * Add a command class to the command chain so that it will be executed next.
+ *
+ * @param cmd The command class to add
+ */
+ void addToCommandChain(Commands::Base *cmd) { m_commandChain.append(cmd); }
+
+ /**
+ * Execute the next command.
+ */
+ void nextCommand();
+
+ /**
+ * Schedule the execution of the next command in the next thread loop.
+ */
+ void nextCommandAsync();
+
+ /**
+ * Returns true if the current command has been chained from another command class.
+ *
+ * @return True if the current command has been chained
+ */
+ bool isChained() { return m_commandChain.count() > 0; }
+
+ /**
+ * Set the error reporting on or off. This variable is then used by some
+ * command classes do determine if they should emit errors and reset with
+ * failure or if they should just silently ignore the error and reset
+ * the command class with an Ok code.
+ *
+ * @param value Error reporting value
+ */
+ void setErrorReporting(bool value) { m_errorReporting = value; }
+
+ /**
+ * Get the current error reporting setting. This only makes sense if the
+ * class is chained - otherwise this allways returns true.
+ *
+ * @return The current error reporting setting
+ */
+ bool errorReporting() { return m_errorReporting || !isChained(); }
+
+ /**
+ * Returns true if the current operation should abort. This method should be
+ * used when the underlying socket implementation is doing blocking operations.
+ *
+ * @return True if the operation should be aborted
+ */
+ bool shouldAbort() { return m_shouldAbort; }
+protected:
+ /**
+ * Call this method when a long wait period has started or ended. If the wait
+ * isn't nulled before the timeout is reached the current action will be aborted
+ * and the socket will be disconnected.
+ *
+ * @param start True if the wait period should start, false if it should end
+ */
+ void timeoutWait(bool start);
+
+ /**
+ * Reset the timeout counter. Call this once in a while during long wait periods
+ * to notify the engine that the socket is still responsive.
+ */
+ void timeoutPing();
+
+ /**
+ * Check if we should timeout. This method might cause a disconnect if the timeout
+ * value is reached.
+ */
+ void timeoutCheck();
+
+ /**
+ * Enable the issue of keepalive packets.
+ */
+ void keepaliveStart();
+
+ /**
+ * Check if we should transmit a new keepalive packet.
+ */
+ void keepaliveCheck();
+protected:
+ KRemoteEncoding *m_remoteEncoding;
+
+ Commands::Base *m_cmdData;
+ QPtrList<Commands::Base> m_commandChain;
+
+ Thread *m_thread;
+ DirectoryListing m_lastDirectoryListing;
+ DirectoryEntry m_lastStatResponse;
+
+ filesize_t m_transferBytes;
+ time_t m_speedLastTime;
+ filesize_t m_speedLastBytes;
+
+ QTime m_timeoutCounter;
+ QTime m_keepaliveCounter;
+private:
+ QMap<QString, QString> m_config;
+ QString m_currentDirectory;
+ QString m_defaultDirectory;
+ KURL m_currentUrl;
+ QString m_protocol;
+ Commands::Type m_currentCommand;
+ bool m_errorReporting;
+ bool m_shouldAbort;
+ QGuardedPtr<ConnectionRetry> m_connectionRetry;
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/speedlimiter.cpp b/kftpgrabber/src/engine/speedlimiter.cpp
new file mode 100644
index 0000000..85f2b72
--- /dev/null
+++ b/kftpgrabber/src/engine/speedlimiter.cpp
@@ -0,0 +1,240 @@
+/*
+ * This file is part of the KFTPGrabber project
+ *
+ * Copyright (C) 2003-2007 by the KFTPGrabber developers
+ * Copyright (C) 2003-2007 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 "speedlimiter.h"
+#include "misc/config.h"
+
+#include <kstaticdeleter.h>
+
+using namespace KFTPCore;
+
+namespace KFTPEngine {
+
+static const int tickDelay = 250;
+static int bucketSize = 1000 / tickDelay;
+
+SpeedLimiter *SpeedLimiter::m_self = 0;
+static KStaticDeleter<SpeedLimiter> staticSpeedLimiterDeleter;
+
+SpeedLimiter *SpeedLimiter::self()
+{
+ if (!m_self) {
+ staticSpeedLimiterDeleter.setObject(m_self, new SpeedLimiter());
+ }
+
+ return m_self;
+}
+
+SpeedLimiter::SpeedLimiter()
+ : m_timer(false)
+{
+ // Reset limits and token debts
+ m_limits[0] = 0;
+ m_limits[1] = 0;
+
+ m_tokenDebt[0] = 0;
+ m_tokenDebt[1] = 0;
+
+ // Subscribe to config updates and update the limits
+ connect(Config::self(), SIGNAL(configChanged()), this, SLOT(updateLimits()));
+ updateLimits();
+}
+
+SpeedLimiter::~SpeedLimiter()
+{
+ if (m_self == this)
+ staticSpeedLimiterDeleter.setObject(m_self, 0, false);
+}
+
+void SpeedLimiter::updateLimits()
+{
+ setLimit(SpeedLimiter::Download, Config::downloadSpeedLimit() * 1024);
+ setLimit(SpeedLimiter::Upload, Config::uploadSpeedLimit() * 1024);
+}
+
+void SpeedLimiter::setLimit(Type type, int limit)
+{
+ m_limits[type] = limit;
+}
+
+void SpeedLimiter::append(SpeedLimiterItem *item, Type type)
+{
+ m_objects[type].append(item);
+
+ int limit = m_limits[type];
+ if (limit > 0) {
+ int tokens = limit * tickDelay / 1000;
+ tokens /= m_objects[type].count();
+
+ if (m_tokenDebt[type] > 0) {
+ if (tokens >= m_tokenDebt[type]) {
+ tokens -= m_tokenDebt[type];
+ m_tokenDebt[type] = 0;
+ } else {
+ tokens = 0;
+ }
+ }
+
+ item->m_availableBytes = tokens;
+ } else {
+ item->m_availableBytes = -1;
+ }
+
+ // Fire the timer if not running
+ if (!m_timer) {
+ startTimer(tickDelay);
+ m_timer = true;
+ }
+}
+
+void SpeedLimiter::remove(SpeedLimiterItem *item)
+{
+ remove(item, Download);
+ remove(item, Upload);
+}
+
+void SpeedLimiter::remove(SpeedLimiterItem *item, Type type)
+{
+ if (m_objects[type].containsRef(item)) {
+ int tokens = m_limits[type] * tickDelay / 1000;
+ tokens /= m_objects[type].count();
+
+ if (item->m_availableBytes < tokens)
+ m_tokenDebt[type] += tokens - item->m_availableBytes;
+
+ m_objects[type].removeRef(item);
+ }
+
+ item->m_availableBytes = -1;
+}
+
+void SpeedLimiter::timerEvent(QTimerEvent*)
+{
+ QPtrList<SpeedLimiterItem> pendingWakeup;
+
+ for (int i = 0; i < 2; i++) {
+ m_tokenDebt[i] = 0;
+
+ int limit = m_limits[i];
+ if (!limit) {
+ // There is no limit, reset all items
+ for (SpeedLimiterItem *item = m_objects[i].first(); item; item = m_objects[i].next()) {
+ item->m_availableBytes = -1;
+ }
+
+ continue;
+ }
+
+ // If there are no objects, just skip it
+ if (m_objects[i].isEmpty())
+ continue;
+
+ int tokens = limit * tickDelay / 1000;
+ if (!tokens)
+ tokens = 1;
+
+ int maxTokens = tokens * bucketSize;
+
+ // Get amount of tokens for each object
+ int tokensPerObject = tokens / m_objects[i].count();
+ if (!tokensPerObject)
+ tokensPerObject = 1;
+
+ tokens = 0;
+
+ QPtrList<SpeedLimiterItem> unsaturatedObjects;
+
+ for (SpeedLimiterItem *item = m_objects[i].first(); item; item = m_objects[i].next()) {
+ if (item->m_availableBytes == -1) {
+ item->m_availableBytes = tokensPerObject;
+ unsaturatedObjects.append(item);
+ } else {
+ item->m_availableBytes += tokensPerObject;
+
+ if (item->m_availableBytes > maxTokens) {
+ tokens += item->m_availableBytes - maxTokens;
+ item->m_availableBytes = maxTokens;
+ } else {
+ unsaturatedObjects.append(item);
+ }
+ }
+ }
+
+ // Assign any left-overs to unsaturated sources
+ while (tokens && !unsaturatedObjects.isEmpty()) {
+ tokensPerObject = tokens / unsaturatedObjects.count();
+ if (!tokensPerObject)
+ break;
+
+ tokens = 0;
+
+ for (SpeedLimiterItem *item = unsaturatedObjects.first(); item; item = unsaturatedObjects.next()) {
+ item->m_availableBytes += tokensPerObject;
+
+ if (item->m_availableBytes > maxTokens) {
+ tokens += item->m_availableBytes - maxTokens;
+ item->m_availableBytes = maxTokens;
+ unsaturatedObjects.removeRef(item);
+ }
+ }
+ }
+ }
+
+ if (m_objects[0].isEmpty() && m_objects[1].isEmpty()) {
+ killTimers();
+ m_timer = false;
+ }
+}
+
+SpeedLimiterItem::SpeedLimiterItem()
+ : m_availableBytes(-1)
+{
+}
+
+void SpeedLimiterItem::updateUsage(int bytes)
+{
+ // Ignore if there are no limits
+ if (m_availableBytes == -1)
+ return;
+
+ if (bytes > m_availableBytes)
+ m_availableBytes = 0;
+ else
+ m_availableBytes -= bytes;
+}
+
+}
+
+#include "speedlimiter.moc"
diff --git a/kftpgrabber/src/engine/speedlimiter.h b/kftpgrabber/src/engine/speedlimiter.h
new file mode 100644
index 0000000..789cb19
--- /dev/null
+++ b/kftpgrabber/src/engine/speedlimiter.h
@@ -0,0 +1,158 @@
+/*
+ * This file is part of the KFTPGrabber project
+ *
+ * Copyright (C) 2003-2007 by the KFTPGrabber developers
+ * Copyright (C) 2003-2007 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.
+ */
+#ifndef KFTPENGINESPEEDLIMITER_H
+#define KFTPENGINESPEEDLIMITER_H
+
+#include <qobject.h>
+#include <qptrlist.h>
+
+namespace KFTPEngine {
+
+class SpeedLimiterItem;
+
+/**
+ * This class is used by Socket implementations to enforce speed limits for
+ * uploads or downloads. It implements a variant of Token Bucket algorithm.
+ *
+ * @author Jernej Kos <kostko@unimatrix-one.org>
+ */
+class SpeedLimiter : public QObject {
+Q_OBJECT
+public:
+ /**
+ * Possible limit types.
+ */
+ enum Type {
+ Download = 0,
+ Upload = 1
+ };
+
+ /**
+ * Returns the global speed limiter instance.
+ */
+ static SpeedLimiter *self();
+
+ /**
+ * Class destructor.
+ */
+ ~SpeedLimiter();
+
+ /**
+ * Set a limit rate.
+ *
+ * @param type Limit type
+ * @param limit Rate
+ */
+ void setLimit(Type type, int limit);
+
+ /**
+ * Appends an item to be managed by the speed limiter.
+ *
+ * @param item Item instance
+ * @param type Limit type
+ */
+ void append(SpeedLimiterItem *item, Type type);
+
+ /**
+ * Removes an item from the speed limiter.
+ *
+ * @param item Item instance
+ */
+ void remove(SpeedLimiterItem *item);
+
+ /**
+ * Removes an item from the speed limiter.
+ *
+ * @param item Item instance
+ * @param type Limit type
+ */
+ void remove(SpeedLimiterItem *item, Type type);
+protected:
+ /**
+ * Static class instance.
+ */
+ static SpeedLimiter *m_self;
+
+ /**
+ * Class constructor.
+ */
+ SpeedLimiter();
+
+ /**
+ * Timer event.
+ */
+ void timerEvent(QTimerEvent*);
+private:
+ bool m_timer;
+ int m_limits[2];
+
+ QPtrList<SpeedLimiterItem> m_objects[2];
+
+ int m_tokenDebt[2];
+private slots:
+ void updateLimits();
+};
+
+/**
+ * This class represents an item managed by the speed limiter. This is
+ * usually a socket.
+ *
+ * @author Jernej Kos <kostko@unimatrix-one.org>
+ */
+class SpeedLimiterItem {
+friend class SpeedLimiter;
+public:
+ /**
+ * Class constructor.
+ */
+ SpeedLimiterItem();
+
+ /**
+ * Returns the number of bytes allowed for consumption.
+ */
+ int allowedBytes() const { return m_availableBytes; }
+protected:
+ /**
+ * Updates object's byte usage.
+ */
+ void updateUsage(int bytes);
+private:
+ int m_availableBytes;
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/ssl.cpp b/kftpgrabber/src/engine/ssl.cpp
new file mode 100644
index 0000000..92418bb
--- /dev/null
+++ b/kftpgrabber/src/engine/ssl.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 "ssl.h"
+
+#include <ksocketdevice.h>
+#include <kmdcodec.h>
+#include <ksslx509v3.h>
+
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+
+#include <unistd.h>
+
+namespace KFTPEngine {
+
+class Ssl::Private {
+public:
+ Private()
+ : ssl(0), sslCtx(0), bio(0)
+ {
+ }
+
+ bool initialized;
+
+ SSL *ssl;
+ SSL_CTX *sslCtx;
+ BIO *bio;
+ X509 *certificate;
+};
+
+Ssl::Ssl(KNetwork::KStreamSocket *socket)
+ : d(new Ssl::Private()),
+ m_socket(socket)
+{
+ d->ssl = 0;
+ d->sslCtx = 0;
+ d->bio = 0;
+ d->certificate = 0;
+ d->initialized = false;
+
+ initialize();
+}
+
+Ssl::~Ssl()
+{
+ close();
+ delete d;
+}
+
+void Ssl::initialize()
+{
+ if (!d->ssl) {
+ SSL_library_init();
+
+ d->sslCtx = SSL_CTX_new(SSLv23_client_method());
+ d->ssl = SSL_new(d->sslCtx);
+
+ SSL_CTX_set_options(d->sslCtx, SSL_OP_ALL);
+
+ // Initialize the socket BIO
+ d->bio = BIO_new_socket(m_socket->socketDevice()->socket(), BIO_NOCLOSE);
+ SSL_set_bio(d->ssl, d->bio, d->bio);
+ }
+
+ d->initialized = true;
+}
+
+bool Ssl::connect()
+{
+ if (!d->initialized)
+ return false;
+
+retry_connect:
+ int ret = SSL_connect(d->ssl);
+ if (ret == 1) {
+ // Connection established
+ setConnectionInfo();
+ return true;
+ } else {
+ int err = SSL_get_error(d->ssl, ret);
+
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+retry_poll:
+ bool input;
+ m_socket->socketDevice()->poll(&input, 0, 0, 0);
+
+ if (input)
+ goto retry_connect;
+ else {
+ ::usleep(20000);
+ goto retry_poll;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Ssl::setClientCertificate(KSSLPKCS12 *pkcs)
+{
+ if (!pkcs || !pkcs->getCertificate())
+ return false;
+
+ int ret;
+ X509 *x;
+ EVP_PKEY *k = pkcs->getPrivateKey();
+ QCString cert = QCString(pkcs->getCertificate()->toString().ascii());
+
+ QByteArray qba, qbb = cert.copy();
+ KCodecs::base64Decode(qbb, qba);
+#if OPENSSL_VERSION_NUMBER > 0x009070afL
+ const unsigned char *qbap = reinterpret_cast<unsigned char *>(qba.data());
+#else
+ unsigned char *qbap = reinterpret_cast<unsigned char *>(qba.data());
+#endif
+ x = d2i_X509(NULL, &qbap, qba.size());
+
+ if (!x || !k)
+ return false;
+
+ if (!pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())
+ return false;
+
+ ret = SSL_CTX_use_certificate(d->sslCtx, x);
+ if (ret <= 0)
+ return false;
+
+ ret = SSL_CTX_use_PrivateKey(d->sslCtx, k);
+ if (ret <= 0)
+ return false;
+
+ return true;
+}
+
+void Ssl::setConnectionInfo()
+{
+ SSL_CIPHER *cipher;
+ char buffer[1024];
+
+ buffer[0] = 0;
+ cipher = SSL_get_current_cipher(d->ssl);
+
+ if (!cipher)
+ return;
+
+ m_connectionInfo.m_cipherUsedBits = SSL_CIPHER_get_bits(cipher, &(m_connectionInfo.m_cipherBits));
+ m_connectionInfo.m_cipherVersion = SSL_CIPHER_get_version(cipher);
+ m_connectionInfo.m_cipherName = SSL_CIPHER_get_name(cipher);
+ m_connectionInfo.m_cipherDescription = SSL_CIPHER_description(cipher, buffer, 1023);
+}
+
+SslConnectionInfo &Ssl::connectionInfo()
+{
+ return m_connectionInfo;
+}
+
+void Ssl::close()
+{
+ if (!d->initialized)
+ return;
+
+ if (d->certificate) {
+ X509_free(d->certificate);
+ d->certificate = 0;
+ }
+
+ if (d->ssl) {
+ SSL_shutdown(d->ssl);
+ SSL_free(d->ssl);
+ SSL_CTX_free(d->sslCtx);
+
+ d->ssl = 0;
+ d->sslCtx = 0;
+ d->bio = 0;
+ }
+}
+
+int Ssl::read(void *buffer, int bytes)
+{
+ if (!d->initialized)
+ return -1;
+
+ int ret = SSL_read(d->ssl, buffer, bytes);
+
+ if (ret <= 0) {
+ int err = SSL_get_error(d->ssl, ret);
+
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+ return 0;
+ else
+ return -1;
+ }
+
+ return ret;
+}
+
+int Ssl::write(void *buffer, int bytes)
+{
+ if (!d->initialized)
+ return -1;
+
+retry_write:
+ int ret = SSL_write(d->ssl, buffer, bytes);
+
+ if (ret <= 0) {
+ int err = SSL_get_error(d->ssl, ret);
+
+ if (err == SSL_ERROR_WANT_READ) {
+retry_poll:
+ bool input;
+ m_socket->socketDevice()->poll(&input, 0, 0, 0);
+
+ if (input)
+ goto retry_write;
+ else {
+ ::usleep(20000);
+ goto retry_poll;
+ }
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+}
diff --git a/kftpgrabber/src/engine/ssl.h b/kftpgrabber/src/engine/ssl.h
new file mode 100644
index 0000000..e0933ed
--- /dev/null
+++ b/kftpgrabber/src/engine/ssl.h
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+#ifndef KFTPENGINESSL_H
+#define KFTPENGINESSL_H
+
+#include <kstreamsocket.h>
+#include <ksslcertificate.h>
+#include <ksslpkcs12.h>
+
+namespace KFTPEngine {
+
+/**
+ * This class contains information about the currently established SSL
+ * connection.
+ *
+ * @author Jernej Kos
+ */
+class SslConnectionInfo {
+friend class Ssl;
+public:
+ /**
+ * Get the cipher in use.
+ */
+ const QString &getCipher() const { return m_cipherName; }
+
+ /**
+ * Describe the cipher in use.
+ */
+ const QString &getCipherDescription() const { return m_cipherDescription; }
+
+ /**
+ * Get the version of the cipher in use.
+ */
+ const QString &getCipherVersion() const { return m_cipherVersion; }
+
+ /**
+ * Get the number of bits of the cipher that are actually used.
+ */
+ int getCipherUsedBits() const { return m_cipherUsedBits; }
+
+ /**
+ * Get bit-size of the cipher.
+ */
+ int getCipherBits() const { return m_cipherBits; }
+protected:
+ /**
+ * Class constructor.
+ */
+ SslConnectionInfo() {}
+
+ int m_cipherUsedBits;
+ int m_cipherBits;
+
+ QString m_cipherName;
+ QString m_cipherDescription;
+ QString m_cipherVersion;
+};
+
+/**
+ * A class that properly handles asynchronious SSL connections.
+ *
+ * @author Jernej Kos
+ */
+class Ssl {
+public:
+ /**
+ * Class constructor.
+ *
+ * @param socket The socket to use as transport
+ */
+ Ssl(KNetwork::KStreamSocket *socket);
+
+ /**
+ * Class destructor.
+ */
+ ~Ssl();
+
+ /**
+ * Start the SSL handshake. This method will block until the
+ * handshake is completed.
+ *
+ * @return True if the handshake was successful, false otherwise
+ */
+ bool connect();
+
+ /**
+ * Close the SSL connection and deallocate resources.
+ */
+ void close();
+
+ /**
+ * Read from the underlying socket.
+ *
+ * @param buffer The tarrget buffer
+ * @param bytes Maximum number of bytes to read
+ * @return Number of bytes actually read or -1 in case of an error
+ */
+ int read(void *buffer, int bytes);
+
+ /**
+ * Write to the underlying socket.
+ *
+ * @param buffer The source buffer
+ * @param bytes Number of bytes to write
+ * @return Number of bytes actually written or -1 in case of an error
+ */
+ int write(void *buffer, int bytes);
+
+ /**
+ * Obtain a reference to the connection information.
+ *
+ * @return A reference ot the connection information, valid after connected
+ */
+ SslConnectionInfo &connectionInfo();
+
+ /**
+ * Set the client certificate to use.
+ *
+ * @return True if the certificate was successfuly set
+ */
+ bool setClientCertificate(KSSLPKCS12 *pkcs);
+private:
+ class Private;
+ Private *d;
+
+ KNetwork::KStreamSocket *m_socket;
+ SslConnectionInfo m_connectionInfo;
+protected:
+ /**
+ * Initialize the SSL session for operation.
+ */
+ void initialize();
+
+ /**
+ * Populate the connection info object with data retrieved from the SSL
+ * socket. Note that the socket has to be connected!
+ */
+ void setConnectionInfo();
+};
+
+}
+
+#endif
diff --git a/kftpgrabber/src/engine/thread.cpp b/kftpgrabber/src/engine/thread.cpp
new file mode 100644
index 0000000..3e151b5
--- /dev/null
+++ b/kftpgrabber/src/engine/thread.cpp
@@ -0,0 +1,346 @@
+/*
+ * 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 "thread.h"
+#include "ftpsocket.h"
+#include "sftpsocket.h"
+
+#include <qapplication.h>
+
+namespace KFTPEngine {
+
+Thread::Thread()
+ : QThread(),
+ m_eventHandler(new EventHandler(this)),
+ m_socket(0),
+ m_wakeupEvent(0),
+ m_abortLoop(false),
+ m_wakeUpPending(false)
+{
+ m_protocolMap.insert("ftp", new FtpSocket(this));
+ m_protocolMap.insert("sftp", new SftpSocket(this));
+
+ // FTP is the default protocol
+ m_socket = m_protocolMap["ftp"];
+
+ // Auto start the thread
+ start();
+}
+
+Thread::~Thread()
+{
+ m_abortLoop = true;
+
+ if (!wait(1000))
+ terminate();
+
+ // Destroy all protocol sockets
+ delete static_cast<FtpSocket*>(m_protocolMap["ftp"]);
+ delete static_cast<SftpSocket*>(m_protocolMap["sftp"]);
+
+ m_protocolMap.clear();
+}
+
+void Thread::run()
+{
+ while (!m_abortLoop) {
+ QThread::usleep(100);
+
+ // "Poll" the socket
+ m_socket->poll();
+
+ // Transmit wakeup events if any
+ if (m_wakeUpPending && m_socket->isBusy()) {
+ m_wakeupMutex.lock();
+ m_socket->wakeup(m_wakeupEvent);
+
+ delete m_wakeupEvent;
+ m_wakeupEvent = 0;
+ m_wakeUpPending = false;
+ m_wakeupMutex.unlock();
+ }
+
+ // Execute any pending commands if the socket isn't busy
+ if (!m_commandQueue.empty()) {
+ m_queueMutex.lock();
+
+ QValueList<Commands::Type>::iterator queueEnd = m_commandQueue.end();
+ for (QValueList<Commands::Type>::iterator i = m_commandQueue.begin(); i != queueEnd; ++i) {
+ Commands::Type cmdType = *i;
+
+ // Execute the command
+ if (cmdType == Commands::CmdNext) {
+ m_commandQueue.remove(i--);
+ m_socket->nextCommand();
+ } else if (!m_socket->isBusy()) {
+ m_commandQueue.remove(i--);
+ m_socket->setCurrentCommand(cmdType);
+
+ switch (cmdType) {
+ case Commands::CmdConnect: {
+ m_socket->protoConnect(nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdDisconnect: {
+ m_socket->protoDisconnect();
+ break;
+ }
+ case Commands::CmdList: {
+ m_socket->protoList(nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdScan: {
+ m_socket->protoScan(nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdGet: {
+ m_socket->protoGet(nextCommandParameter().asUrl(),
+ nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdPut: {
+ m_socket->protoPut(nextCommandParameter().asUrl(),
+ nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdDelete: {
+ m_socket->protoDelete(nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdRename: {
+ m_socket->protoRename(nextCommandParameter().asUrl(),
+ nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdChmod: {
+ m_socket->protoChmod(nextCommandParameter().asUrl(),
+ nextCommandParameter().asFileSize(),
+ nextCommandParameter().asBoolean());
+ break;
+ }
+ case Commands::CmdMkdir: {
+ m_socket->protoMkdir(nextCommandParameter().asUrl());
+ break;
+ }
+ case Commands::CmdRaw: {
+ m_socket->protoRaw(nextCommandParameter().asString());
+ break;
+ }
+ case Commands::CmdFxp: {
+ m_socket->protoSiteToSite(static_cast<Socket*>(nextCommandParameter().asData()),
+ nextCommandParameter().asUrl(),
+ nextCommandParameter().asUrl());
+ break;
+ }
+ default: {
+ // Just ignore unknown commands for now
+ break;
+ }
+ }
+ }
+ }
+
+ m_queueMutex.unlock();
+ }
+ }
+}
+
+void Thread::wakeup(WakeupEvent *event)
+{
+ QMutexLocker locker(&m_wakeupMutex);
+
+ m_wakeupEvent = event;
+ m_wakeUpPending = true;
+}
+
+void Thread::abort()
+{
+ // Clear any pending wakeup events
+ if (m_wakeUpPending) {
+ QMutexLocker locker(&m_wakeupMutex);
+
+ m_wakeupEvent = 0;
+ m_wakeUpPending = false;
+ }
+
+ m_socket->protoAbort();
+}
+
+void Thread::event(Event::Type type, QValueList<EventParameter> params)
+{
+ if (m_eventHandler) {
+ Event *e = new Event(type, params);
+ qApp->postEvent(m_eventHandler, e);
+ }
+}
+
+void Thread::selectSocketForProtocol(const KURL &url)
+{
+ if (url.protocol() == m_socket->protocolName())
+ return;
+
+ // Change the socket if one exists
+ Socket *socket = m_protocolMap.find(url.protocol());
+ if (socket)
+ m_socket = socket;
+}
+
+EventParameter Thread::nextCommandParameter()
+{
+ QMutexLocker locker(&m_paramsMutex);
+ EventParameter param = m_commandParams.front();
+ m_commandParams.pop_front();
+
+ return param;
+}
+
+void Thread::connect(const KURL &url)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ // Setup the correct socket to use for connection
+ selectSocketForProtocol(url);
+
+ m_commandQueue.append(Commands::CmdConnect);
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::disconnect()
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdDisconnect);
+}
+
+void Thread::list(const KURL &url)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdList);
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::scan(const KURL &url)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdScan);
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::get(const KURL &source, const KURL &destination)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdGet);
+ m_commandParams.append(EventParameter(destination));
+ m_commandParams.append(EventParameter(source));
+}
+
+void Thread::put(const KURL &source, const KURL &destination)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdPut);
+ m_commandParams.append(EventParameter(destination));
+ m_commandParams.append(EventParameter(source));
+}
+
+void Thread::remove(const KURL &url)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdDelete);
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::rename(const KURL &source, const KURL &destination)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdRename);
+ m_commandParams.append(EventParameter(destination));
+ m_commandParams.append(EventParameter(source));
+}
+
+void Thread::chmod(const KURL &url, int mode, bool recursive)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdChmod);
+ m_commandParams.append(EventParameter(recursive));
+ m_commandParams.append(EventParameter(mode));
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::mkdir(const KURL &url)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdMkdir);
+ m_commandParams.append(EventParameter(url));
+}
+
+void Thread::raw(const QString &raw)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdRaw);
+ m_commandParams.append(EventParameter(raw));
+}
+
+void Thread::siteToSite(Thread *thread, const KURL &source, const KURL &destination)
+{
+ QMutexLocker locker(&m_paramsMutex);
+ QMutexLocker lockerq(&m_queueMutex);
+
+ m_commandQueue.append(Commands::CmdFxp);
+ m_commandParams.append(EventParameter(destination));
+ m_commandParams.append(EventParameter(source));
+ m_commandParams.append(EventParameter(thread->socket()));
+}
+
+}
diff --git a/kftpgrabber/src/engine/thread.h b/kftpgrabber/src/engine/thread.h
new file mode 100644
index 0000000..62a36c4
--- /dev/null
+++ b/kftpgrabber/src/engine/thread.h
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+#ifndef KFTPENGINETHREAD_H
+#define KFTPENGINETHREAD_H
+
+#include <qthread.h>
+#include <qmutex.h>
+#include <qvaluelist.h>
+#include <qdict.h>
+
+#include "event.h"
+#include "directorylisting.h"
+#include "commands.h"
+#include "socket.h"
+
+namespace KFTPEngine {
+
+/**
+ * This class represents a socket thread. It serves as a command queue to
+ * the underlying socket implementation and also as an abstraction layer
+ * to support multiple protocols.
+ *
+ * @author Jernej Kos <kostko@jweb-network.net>
+ */
+class Thread : public QThread
+{
+friend class EventHandler;
+friend class Socket;
+public:
+ Thread();
+ ~Thread();
+
+ /**
+ * Returns the event handler for this thread. Should be used to connect
+ * to any signals this thread may emit.
+ *
+ * @return A pointer to the EventHandler object
+ */
+ EventHandler *eventHandler() { return m_eventHandler; }
+
+ /**
+ * Returns the underlying socket object.
+ *
+ * @return A pointer to the Socket object
+ */
+ Socket *socket() { return m_socket; }
+
+ /**
+ * Prepare the apropriate socket for use.
+ *
+ * @param url The url that should be used to identify the protocol
+ */
+ void selectSocketForProtocol(const KURL &url);
+
+ /**
+ * Schedules a wakeup event to be passed on to the underlying socket.
+ *
+ * @param event The wakeup event to pass on
+ */
+ void wakeup(WakeupEvent *event);
+
+ void abort();
+ void connect(const KURL &url);
+ void disconnect();
+ void list(const KURL &url);
+ void scan(const KURL &url);
+ void get(const KURL &source, const KURL &destination);
+ void put(const KURL &source, const KURL &destination);
+ void remove(const KURL &url);
+ void rename(const KURL &source, const KURL &destination);
+ void chmod(const KURL &url, int mode, bool recursive = false);
+ void mkdir(const KURL &url);
+ void raw(const QString &raw);
+ void siteToSite(Thread *thread, const KURL &source, const KURL &destination);
+protected:
+ void run();
+ void event(Event::Type type, QValueList<EventParameter> params);
+
+ EventParameter nextCommandParameter();
+protected:
+ EventHandler *m_eventHandler;
+ Socket *m_socket;
+
+ QMutex m_eventMutex;
+ QMutex m_wakeupMutex;
+ QMutex m_paramsMutex;
+ QMutex m_queueMutex;
+
+ QDict<Socket> m_protocolMap;
+ QValueList<Commands::Type> m_commandQueue;
+ QValueList<EventParameter> m_commandParams;
+ WakeupEvent *m_wakeupEvent;
+
+ bool m_abortLoop;
+ bool m_wakeUpPending;
+};
+
+}
+
+#endif