summaryrefslogtreecommitdiffstats
path: root/kftpgrabber/src/engine/sftpsocket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kftpgrabber/src/engine/sftpsocket.cpp')
-rw-r--r--kftpgrabber/src/engine/sftpsocket.cpp775
1 files changed, 775 insertions, 0 deletions
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;
+}
+
+}