diff options
Diffstat (limited to 'old/coreplugin/ssh/sftpchannel.cpp')
-rw-r--r-- | old/coreplugin/ssh/sftpchannel.cpp | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/old/coreplugin/ssh/sftpchannel.cpp b/old/coreplugin/ssh/sftpchannel.cpp new file mode 100644 index 0000000..4c94c4a --- /dev/null +++ b/old/coreplugin/ssh/sftpchannel.cpp @@ -0,0 +1,899 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpchannel.h" +#include "sftpchannel_p.h" + +#include "sshdelayedsignal_p.h" +#include "sshexception_p.h" +#include "sshsendfacility_p.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QWeakPointer> + +namespace Core { + +namespace Internal { +namespace { + const quint32 ProtocolVersion = 3; + + QString errorMessage(const QString &serverMessage, + const QString &alternativeMessage) + { + return serverMessage.isEmpty() ? alternativeMessage : serverMessage; + } + + QString errorMessage(const SftpStatusResponse &response, + const QString &alternativeMessage) + { + return response.status == SSH_FX_OK ? QString() + : errorMessage(response.errorString, alternativeMessage); + } +} // anonymous namespace +} // namespace Internal + +SftpChannel::SftpChannel(quint32 channelId, + Internal::SshSendFacility &sendFacility) + : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this)) +{ +} + +SftpChannel::State SftpChannel::state() const +{ + switch (d->channelState()) { + case Internal::AbstractSshChannel::Inactive: + return Uninitialized; + case Internal::AbstractSshChannel::SessionRequested: + return Initializing; + case Internal::AbstractSshChannel::CloseRequested: + return Closing; + case Internal::AbstractSshChannel::Closed: + return Closed; + case Internal::AbstractSshChannel::SessionEstablished: + return d->m_sftpState == Internal::SftpChannelPrivate::Initialized + ? Initialized : Initializing; + default: + Q_ASSERT(!"Oh no, we forgot to handle a channel state!"); + return Closed; // For the compiler. + } +} + +void SftpChannel::initialize() +{ + d->requestSessionStart(); + d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested; +} + +void SftpChannel::closeChannel() +{ + d->closeChannel(); +} + +SftpJobId SftpChannel::listDirectory(const QString &path) +{ + return d->createJob(Internal::SftpListDir::Ptr( + new Internal::SftpListDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::createDirectory(const QString &path) +{ + return d->createJob(Internal::SftpMakeDir::Ptr( + new Internal::SftpMakeDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::removeDirectory(const QString &path) +{ + return d->createJob(Internal::SftpRmDir::Ptr( + new Internal::SftpRmDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::removeFile(const QString &path) +{ + return d->createJob(Internal::SftpRm::Ptr( + new Internal::SftpRm(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath, + const QString &newPath) +{ + return d->createJob(Internal::SftpRename::Ptr( + new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath))); +} + +SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode) +{ + return d->createJob(Internal::SftpCreateFile::Ptr( + new Internal::SftpCreateFile(++d->m_nextJobId, path, mode))); +} + +SftpJobId SftpChannel::uploadFile(const QString &localFilePath, + const QString &remoteFilePath, SftpOverwriteMode mode) +{ + QSharedPointer<QFile> localFile(new QFile(localFilePath)); + if (!localFile->open(QIODevice::ReadOnly)) + return SftpInvalidJob; + return d->createJob(Internal::SftpUploadFile::Ptr( + new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode))); +} + +SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, + const QString &localFilePath, SftpOverwriteMode mode) +{ + QSharedPointer<QFile> localFile(new QFile(localFilePath)); + if (mode == SftpSkipExisting && localFile->exists()) + return SftpInvalidJob; + QIODevice::OpenMode openMode = QIODevice::WriteOnly; + if (mode == SftpOverwriteExisting) + openMode |= QIODevice::Truncate; + else if (mode == SftpAppendToExisting) + openMode |= QIODevice::Append; + if (!localFile->open(openMode)) + return SftpInvalidJob; + return d->createJob(Internal::SftpDownload::Ptr( + new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile))); +} + +SftpJobId SftpChannel::uploadDir(const QString &localDirPath, + const QString &remoteParentDirPath) +{ + if (state() != Initialized) + return SftpInvalidJob; + const QDir localDir(localDirPath); + if (!localDir.exists() || !localDir.isReadable()) + return SftpInvalidJob; + const Internal::SftpUploadDir::Ptr uploadDirOp( + new Internal::SftpUploadDir(++d->m_nextJobId)); + const QString remoteDirPath + = remoteParentDirPath + QLatin1Char('/') + localDir.dirName(); + const Internal::SftpMakeDir::Ptr mkdirOp( + new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp)); + uploadDirOp->mkdirsInProgress.insert(mkdirOp, + Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath)); + d->createJob(mkdirOp); + return uploadDirOp->jobId; +} + +SftpChannel::~SftpChannel() +{ + delete d; +} + + +namespace Internal { + +SftpChannelPrivate::SftpChannelPrivate(quint32 channelId, + SshSendFacility &sendFacility, SftpChannel *sftp) + : AbstractSshChannel(channelId, sendFacility), + m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp) +{ +} + +SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job) +{ + if (m_sftp->state() != SftpChannel::Initialized) + return SftpInvalidJob; + m_jobs.insert(job->jobId, job); + sendData(job->initialPacket(m_outgoingPacket).rawData()); + return job->jobId; +} + +void SftpChannelPrivate::handleChannelSuccess() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("sftp subsystem initialized"); +#endif + sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData()); + m_sftpState = InitSent; +} + +void SftpChannelPrivate::handleChannelFailure() +{ + if (m_sftpState != SubsystemRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_FAILURE packet."); + } + createDelayedInitFailedSignal(SSH_TR("Server could not start sftp subsystem.")); + closeChannel(); +} + +void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data) +{ + m_incomingData += data; + m_incomingPacket.consumeData(m_incomingData); + while (m_incomingPacket.isComplete()) { + handleCurrentPacket(); + m_incomingPacket.clear(); + m_incomingPacket.consumeData(m_incomingData); + } +} + +void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data) +{ + qWarning("Unexpected extended data '%s' of type %d on SFTP channel.", + data.data(), type); +} + +void SftpChannelPrivate::handleCurrentPacket() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("Handling SFTP packet of type %d", m_incomingPacket.type()); +#endif + switch (m_incomingPacket.type()) { + case SSH_FXP_VERSION: + handleServerVersion(); + break; + case SSH_FXP_HANDLE: + handleHandle(); + break; + case SSH_FXP_NAME: + handleName(); + break; + case SSH_FXP_STATUS: + handleStatus(); + break; + case SSH_FXP_DATA: + handleReadData(); + break; + case SSH_FXP_ATTRS: + handleAttrs(); + break; + default: + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected packet.", + SSH_TR("Unexpected packet of type %d.").arg(m_incomingPacket.type())); + } +} + +void SftpChannelPrivate::handleServerVersion() +{ + checkChannelActive(); + if (m_sftpState != InitSent) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_VERSION packet."); + } + +#ifdef CREATOR_SSH_DEBUG + qDebug("sftp init received"); +#endif + const quint32 serverVersion = m_incomingPacket.extractServerVersion(); + if (serverVersion != ProtocolVersion) { + createDelayedInitFailedSignal(SSH_TR("Protocol version mismatch: Expected %1, got %2") + .arg(serverVersion).arg(ProtocolVersion)); + closeChannel(); + } else { + m_sftpState = Initialized; + createDelayedInitializedSignal(); + } +} + +void SftpChannelPrivate::handleHandle() +{ + const SftpHandleResponse &response = m_incomingPacket.asHandleResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + const QSharedPointer<AbstractSftpOperationWithHandle> job + = it.value().dynamicCast<AbstractSftpOperationWithHandle>(); + if (job.isNull()) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_HANDLE packet."); + } + if (job->state != AbstractSftpOperationWithHandle::OpenRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_HANDLE packet."); + } + job->remoteHandle = response.handle; + job->state = AbstractSftpOperationWithHandle::Open; + + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: + handleLsHandle(it); + break; + case AbstractSftpOperation::CreateFile: + handleCreateFileHandle(it); + break; + case AbstractSftpOperation::Download: + handleGetHandle(it); + break; + case AbstractSftpOperation::UploadFile: + handlePutHandle(it); + break; + default: + Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!"); + } +} + +void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it) +{ + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + sendData(m_outgoingPacket.generateReadDir(op->remoteHandle, + op->jobId).rawData()); +} + +void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it) +{ + SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>(); + sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle, + op->jobId).rawData()); +} + +void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it) +{ + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + sendData(m_outgoingPacket.generateFstat(op->remoteHandle, + op->jobId).rawData()); + op->statRequested = true; +} + +void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it) +{ + SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>(); + if (op->parentJob && op->parentJob->hasError) + sendTransferCloseHandle(op, it.key()); + + // OpenSSH does not implement the RFC's append functionality, so we + // have to emulate it. + if (op->mode == SftpAppendToExisting) { + sendData(m_outgoingPacket.generateFstat(op->remoteHandle, + op->jobId).rawData()); + op->statRequested = true; + } else { + spawnWriteRequests(it); + } +} + +void SftpChannelPrivate::handleStatus() +{ + const SftpStatusResponse &response = m_incomingPacket.asStatusResponse(); +#ifdef CREATOR_SSH_DEBUG + qDebug("%s: status = %d", Q_FUNC_INFO, response.status); +#endif + JobMap::Iterator it = lookupJob(response.requestId); + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: + handleLsStatus(it, response); + break; + case AbstractSftpOperation::Download: + handleGetStatus(it, response); + break; + case AbstractSftpOperation::UploadFile: + handlePutStatus(it, response); + break; + case AbstractSftpOperation::MakeDir: + handleMkdirStatus(it, response); + break; + case AbstractSftpOperation::RmDir: + case AbstractSftpOperation::Rm: + case AbstractSftpOperation::Rename: + case AbstractSftpOperation::CreateFile: + handleStatusGeneric(it, response); + break; + } +} + +void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + AbstractSftpOperation::Ptr op = it.value(); + const QString error = errorMessage(response, SSH_TR("Unknown error.")); + createDelayedJobFinishedSignal(op->jobId, error); + m_jobs.erase(it); +} + +void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>(); + if (op->parentJob == SftpUploadDir::Ptr()) { + handleStatusGeneric(it, response); + return; + } + if (op->parentJob->hasError) { + m_jobs.erase(it); + return; + } + + typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt; + DirIt dirIt = op->parentJob->mkdirsInProgress.find(op); + Q_ASSERT(dirIt != op->parentJob->mkdirsInProgress.end()); + const QString &remoteDir = dirIt.value().remoteDir; + if (response.status == SSH_FX_OK) { + createDelayedDataAvailableSignal(op->parentJob->jobId, + SSH_TR("Created remote directory '%1'.").arg(remoteDir)); + } else if (response.status == SSH_FX_FAILURE) { + createDelayedDataAvailableSignal(op->parentJob->jobId, + SSH_TR("Remote directory '%1' already exists.").arg(remoteDir)); + } else { + op->parentJob->setError(); + createDelayedJobFinishedSignal(op->parentJob->jobId, + SSH_TR("Error creating directory '%1': %2") + .arg(remoteDir, response.errorString)); + m_jobs.erase(it); + return; + } + + QDir localDir(dirIt.value().localDir); + const QFileInfoList &dirInfos + = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QFileInfo &dirInfo, dirInfos) { + const QString remoteSubDir = remoteDir + '/' + dirInfo.fileName(); + const SftpMakeDir::Ptr mkdirOp( + new SftpMakeDir(++m_nextJobId, remoteSubDir, op->parentJob)); + op->parentJob->mkdirsInProgress.insert(mkdirOp, + SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir)); + createJob(mkdirOp); + } + + const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files); + foreach (const QFileInfo &fileInfo, fileInfos) { + QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath())); + if (!localFile->open(QIODevice::ReadOnly)) { + op->parentJob->setError(); + createDelayedJobFinishedSignal(op->parentJob->jobId, + SSH_TR("Could not open local file '%1': %2") + .arg(fileInfo.absoluteFilePath(), localFile->error())); + m_jobs.erase(it); + return; + } + + const QString remoteFilePath = remoteDir + '/' + fileInfo.fileName(); + SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId, + remoteFilePath, localFile, SftpOverwriteExisting, op->parentJob)); + createJob(uploadFileOp); + op->parentJob->uploadsInProgress.append(uploadFileOp); + } + + op->parentJob->mkdirsInProgress.erase(dirIt); + if (op->parentJob->mkdirsInProgress.isEmpty() + && op->parentJob->uploadsInProgress.isEmpty()) + createDelayedJobFinishedSignal(op->parentJob->jobId); + m_jobs.erase(it); +} + +void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + switch (op->state) { + case SftpListDir::OpenRequested: + createDelayedJobFinishedSignal(op->jobId, errorMessage(response.errorString, + SSH_TR("Remote directory could not be opened for reading."))); + m_jobs.erase(it); + break; + case SftpListDir::Open: + if (response.status != SSH_FX_EOF) + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to list remote directory contents."))); + op->state = SftpListDir::CloseRequested; + sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle, + op->jobId).rawData()); + break; + case SftpListDir::CloseRequested: + if (!op->hasError) { + const QString error = errorMessage(response, + SSH_TR("Failed to close remote directory.")); + createDelayedJobFinishedSignal(op->jobId, error); + } + m_jobs.erase(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + switch (op->state) { + case SftpDownload::OpenRequested: + createDelayedJobFinishedSignal(op->jobId, + errorMessage(response.errorString, + SSH_TR("Failed to open remote file for reading."))); + m_jobs.erase(it); + break; + case SftpDownload::Open: + if (op->statRequested) { + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to stat remote file."))); + sendTransferCloseHandle(op, response.requestId); + } else { + if ((response.status != SSH_FX_EOF || response.requestId != op->eofId) + && !op->hasError) + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to read remote file."))); + finishTransferRequest(it); + } + break; + case SftpDownload::CloseRequested: + Q_ASSERT(op->inFlightCount == 1); + if (!op->hasError) { + if (response.status == SSH_FX_OK) + createDelayedJobFinishedSignal(op->jobId); + else + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to close remote file."))); + } + removeTransferRequest(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>(); + switch (job->state) { + case SftpUploadFile::OpenRequested: { + bool emitError = false; + if (job->parentJob) { + if (!job->parentJob->hasError) { + job->parentJob->setError(); + emitError = true; + } + } else { + emitError = true; + } + + if (emitError) { + createDelayedJobFinishedSignal(job->jobId, + errorMessage(response.errorString, + SSH_TR("Failed to open remote file for writing."))); + } + m_jobs.erase(it); + break; + } + case SftpUploadFile::Open: + if (job->hasError || (job->parentJob && job->parentJob->hasError)) { + job->hasError = true; + finishTransferRequest(it); + return; + } + + if (response.status == SSH_FX_OK) { + sendWriteRequest(it); + } else { + if (job->parentJob) + job->parentJob->setError(); + reportRequestError(job, errorMessage(response.errorString, + SSH_TR("Failed to write remote file."))); + finishTransferRequest(it); + } + break; + case SftpUploadFile::CloseRequested: + Q_ASSERT(job->inFlightCount == 1); + if (job->hasError || (job->parentJob && job->parentJob->hasError)) { + m_jobs.erase(it); + return; + } + + if (response.status == SSH_FX_OK) { + if (job->parentJob) { + job->parentJob->uploadsInProgress.removeOne(job); + if (job->parentJob->mkdirsInProgress.isEmpty() + && job->parentJob->uploadsInProgress.isEmpty()) + createDelayedJobFinishedSignal(job->parentJob->jobId); + } else { + createDelayedJobFinishedSignal(job->jobId); + } + } else { + const QString error = errorMessage(response.errorString, + SSH_TR("Failed to close remote file.")); + if (job->parentJob) { + job->parentJob->setError(); + createDelayedJobFinishedSignal(job->parentJob->jobId, error); + } else { + createDelayedJobFinishedSignal(job->jobId, error); + } + } + m_jobs.erase(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handleName() +{ + const SftpNameResponse &response = m_incomingPacket.asNameResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: { + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + if (op->state != SftpListDir::Open) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_NAME packet."); + } + + for (int i = 0; i < response.files.count(); ++i) { + const SftpFile &file = response.files.at(i); + createDelayedDataAvailableSignal(op->jobId, file.fileName); + } + sendData(m_outgoingPacket.generateReadDir(op->remoteHandle, + op->jobId).rawData()); + break; + } + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_NAME packet."); + } +} + +void SftpChannelPrivate::handleReadData() +{ + const SftpDataResponse &response = m_incomingPacket.asDataResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + if (it.value()->type() != AbstractSftpOperation::Download) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_DATA packet."); + } + + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + if (op->hasError) { + finishTransferRequest(it); + return; + } + + if (!op->localFile->seek(op->offsets[response.requestId])) { + reportRequestError(op, op->localFile->errorString()); + finishTransferRequest(it); + return; + } + + if (op->localFile->write(response.data) != response.data.size()) { + reportRequestError(op, op->localFile->errorString()); + finishTransferRequest(it); + return; + } + + if (op->offset >= op->fileSize && op->fileSize != 0) + finishTransferRequest(it); + else + sendReadRequest(op, response.requestId); +} + +void SftpChannelPrivate::handleAttrs() +{ + const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + AbstractSftpTransfer::Ptr transfer + = it.value().dynamicCast<AbstractSftpTransfer>(); + if (!transfer || transfer->state != AbstractSftpTransfer::Open + || !transfer->statRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_ATTRS packet."); + } + Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile + || transfer->type() == AbstractSftpOperation::Download); + + if (transfer->type() == AbstractSftpOperation::Download) { + SftpDownload::Ptr op = transfer.staticCast<SftpDownload>(); + if (response.attrs.sizePresent) { + op->fileSize = response.attrs.size; + } else { + op->fileSize = 0; + op->eofId = op->jobId; + } + op->statRequested = false; + spawnReadRequests(op); + } else { + SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>(); + if (op->parentJob && op->parentJob->hasError) { + op->hasError = true; + sendTransferCloseHandle(op, op->jobId); + return; + } + + if (response.attrs.sizePresent) { + op->offset = response.attrs.size; + spawnWriteRequests(it); + } else { + if (op->parentJob) + op->parentJob->setError(); + reportRequestError(op, SSH_TR("Cannot append to remote file: " + "Server does not support file size attribute.")); + sendTransferCloseHandle(op, op->jobId); + } + } +} + +SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id) +{ + JobMap::Iterator it = m_jobs.find(id); + if (it == m_jobs.end()) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid request id in SFTP packet."); + } + return it; +} + +void SftpChannelPrivate::closeHook() +{ + createClosedSignal(); +} + +void SftpChannelPrivate::handleOpenSuccessInternal() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("SFTP session started"); +#endif + m_sendFacility.sendSftpPacket(remoteChannel()); + m_sftpState = SubsystemRequested; +} + +void SftpChannelPrivate::handleOpenFailureInternal() +{ + if (channelState() != SessionRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet."); + } + createDelayedInitFailedSignal(SSH_TR("Server could not start session.")); +} + +void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job, + quint32 requestId) +{ + Q_ASSERT(job->eofId == SftpInvalidJob); + sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset, + AbstractSftpPacket::MaxDataSize, requestId).rawData()); + job->offsets[requestId] = job->offset; + job->offset += AbstractSftpPacket::MaxDataSize; + if (job->offset >= job->fileSize) + job->eofId = requestId; +} + +void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job, + const QString &error) +{ + createDelayedJobFinishedSignal(job->jobId, error); + job->hasError = true; +} + +void SftpChannelPrivate::finishTransferRequest(const JobMap::Iterator &it) +{ + AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>(); + if (job->inFlightCount == 1) + sendTransferCloseHandle(job, it.key()); + else + removeTransferRequest(it); +} + +void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job, + quint32 requestId) +{ + sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle, + requestId).rawData()); + job->state = SftpDownload::CloseRequested; +} + +void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it) +{ + --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount; + m_jobs.erase(it); +} + +void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it) +{ + SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>(); + QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize); + if (job->localFile->error() != QFile::NoError) { + if (job->parentJob) + job->parentJob->setError(); + reportRequestError(job, SSH_TR("Error reading local file: %1") + .arg(job->localFile->errorString())); + finishTransferRequest(it); + } else if (data.isEmpty()) { + finishTransferRequest(it); + } else { + sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle, + job->offset, data, it.key()).rawData()); + job->offset += AbstractSftpPacket::MaxDataSize; + } +} + +void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it) +{ + SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>(); + op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize); + sendWriteRequest(it); + for (int i = 1; !op->hasError && i < op->inFlightCount; ++i) + sendWriteRequest(m_jobs.insert(++m_nextJobId, op)); +} + +void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job) +{ + job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize); + sendReadRequest(job, job->jobId); + for (int i = 1; i < job->inFlightCount; ++i) { + const quint32 requestId = ++m_nextJobId; + m_jobs.insert(requestId, job); + sendReadRequest(job, requestId); + } +} + +void SftpChannelPrivate::createDelayedInitFailedSignal(const QString &reason) +{ + new SftpInitializationFailedSignal(this, QWeakPointer<SftpChannel>(m_sftp), + reason); +} + +void SftpChannelPrivate::emitInitializationFailedSignal(const QString &reason) +{ + emit m_sftp->initializationFailed(reason); +} + +void SftpChannelPrivate::createDelayedInitializedSignal() +{ + new SftpInitializedSignal(this, QWeakPointer<SftpChannel>(m_sftp)); +} + +void SftpChannelPrivate::emitInitialized() +{ + emit m_sftp->initialized(); +} + +void SftpChannelPrivate::createDelayedJobFinishedSignal(SftpJobId jobId, + const QString &error) +{ + new SftpJobFinishedSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, error); +} + +void SftpChannelPrivate::emitJobFinished(SftpJobId jobId, const QString &error) +{ + emit m_sftp->finished(jobId, error); +} + +void SftpChannelPrivate::createDelayedDataAvailableSignal(SftpJobId jobId, + const QString &data) +{ + new SftpDataAvailableSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, data); +} + +void SftpChannelPrivate::emitDataAvailable(SftpJobId jobId, const QString &data) +{ + emit m_sftp->dataAvailable(jobId, data); +} + +void SftpChannelPrivate::createClosedSignal() +{ + new SftpClosedSignal(this, QWeakPointer<SftpChannel>(m_sftp)); +} + +void SftpChannelPrivate::emitClosed() +{ + emit m_sftp->closed(); +} + +} // namespace Internal +} // namespace Core |