12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175 |
- /**************************************************************************
- **
- ** This file is part of Qt Creator
- **
- ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
- **
- ** Contact: http://www.qt-project.org/
- **
- **
- ** GNU Lesser General Public License Usage
- **
- ** 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.
- **
- ** In addition, as a special exception, Nokia gives you certain additional
- ** rights. These rights are described in the Nokia Qt LGPL Exception
- ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
- **
- ** Other Usage
- **
- ** Alternatively, this file may be used in accordance with the terms and
- ** conditions contained in a signed written agreement between you and Nokia.
- **
- **
- **************************************************************************/
- #include "sftpchannel.h"
- #include "sftpchannel_p.h"
- #include "sftpdefs.h"
- #include "sshexception_p.h"
- #include "sshincomingpacket_p.h"
- #include "sshlogging_p.h"
- #include "sshsendfacility_p.h"
- #include <QDir>
- #include <QFile>
- namespace QSsh {
- 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);
- }
- bool openFile(QFile *localFile, SftpOverwriteMode mode)
- {
- if (mode == SftpSkipExisting && localFile->exists())
- return false;
- QIODevice::OpenMode openMode = QIODevice::WriteOnly;
- if (mode == SftpOverwriteExisting)
- openMode |= QIODevice::Truncate;
- else if (mode == SftpAppendToExisting)
- openMode |= QIODevice::Append;
- return localFile->open(openMode);
- }
- SftpError sftpStatusToError(const SftpStatusCode status)
- {
- switch (status) {
- case SSH_FX_OK:
- return SftpError::NoError;
- case SSH_FX_EOF:
- return SftpError::EndOfFile;
- case SSH_FX_NO_SUCH_FILE:
- return SftpError::FileNotFound;
- case SSH_FX_PERMISSION_DENIED:
- return SftpError::PermissionDenied;
- case SSH_FX_BAD_MESSAGE:
- return SftpError::BadMessage;
- case SSH_FX_NO_CONNECTION:
- return SftpError::NoConnection;
- case SSH_FX_CONNECTION_LOST:
- return SftpError::ConnectionLost;
- case SSH_FX_OP_UNSUPPORTED:
- return SftpError::UnsupportedOperation;
- case SSH_FX_FAILURE:
- default:
- return SftpError::GenericFailure;
- }
- }
- } // anonymous namespace
- } // namespace Internal
- //--------------------------------------------------------------------------------------------------
- // SftpChannel
- //--------------------------------------------------------------------------------------------------
- SftpChannel::SftpChannel(quint32 channelId,
- Internal::SshSendFacility &sendFacility)
- : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
- {
- connect(d, &Internal::SftpChannelPrivate::initialized,
- this, &SftpChannel::initialized, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::channelError,
- this, &SftpChannel::channelError, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::dataAvailable,
- this, &SftpChannel::dataAvailable, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::fileInfoAvailable,
- this, &SftpChannel::fileInfoAvailable, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::finished,
- this, &SftpChannel::finished, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::closed,
- this, &SftpChannel::closed, Qt::QueuedConnection);
- connect(d, &Internal::SftpChannelPrivate::transferProgress,
- this, &SftpChannel::transferProgress, Qt::QueuedConnection);
- }
- 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::statFile(const QString &path)
- {
- return d->createJob(Internal::SftpStatFile::Ptr(
- new Internal::SftpStatFile(++d->m_nextJobId, path)));
- }
- 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::createLink(const QString &filePath, const QString &target)
- {
- return d->createJob(Internal::SftpCreateLink::Ptr(
- new Internal::SftpCreateLink(++d->m_nextJobId, filePath, target)));
- }
- 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(QSharedPointer<QIODevice> device,
- const QString &remoteFilePath, SftpOverwriteMode mode)
- {
- if (!device->isOpen() && !device->open(QIODevice::ReadOnly))
- return SftpInvalidJob;
- return d->createJob(Internal::SftpUploadFile::Ptr(
- new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, device, 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));
- return d->createJob(Internal::SftpDownload::Ptr(
- new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile, mode)));
- }
- SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, QSharedPointer<QIODevice> device)
- {
- return d->createJob(Internal::SftpDownload::Ptr(
- new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, device, SftpOverwriteExisting)));
- }
- 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 + u'/' + 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;
- }
- SftpJobId SftpChannel::downloadDir(const QString &remoteDirPath,
- const QString &localDirPath, SftpOverwriteMode mode)
- {
- if (state() != Initialized)
- return SftpInvalidJob;
- if (!QDir().mkpath(localDirPath))
- return SftpInvalidJob;
- const Internal::SftpDownloadDir::Ptr downloadDirOp(
- new Internal::SftpDownloadDir(++d->m_nextJobId, mode));
- const Internal::SftpListDir::Ptr lsdirOp(
- new Internal::SftpListDir(++d->m_nextJobId, remoteDirPath, downloadDirOp));
- downloadDirOp->lsdirsInProgress.insert(lsdirOp,
- Internal::SftpDownloadDir::Dir(localDirPath, remoteDirPath));
- d->createJob(lsdirOp);
- return downloadDirOp->jobId;
- }
- SftpChannel::~SftpChannel()
- {
- delete d;
- }
- //--------------------------------------------------------------------------------------------------
- // SftpChannelPrivate
- //--------------------------------------------------------------------------------------------------
- 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()
- {
- if (channelState() == CloseRequested)
- return;
- qCDebug(sshLog, "sftp subsystem initialized");
- sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
- m_sftpState = InitSent;
- }
- void SftpChannelPrivate::handleChannelFailure()
- {
- if (channelState() == CloseRequested)
- return;
- if (m_sftpState != SubsystemRequested) {
- throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
- "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
- }
- emit channelError(tr("Server could not start SFTP subsystem."));
- closeChannel();
- }
- void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
- {
- if (channelState() == CloseRequested)
- return;
- 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)
- {
- qCWarning(sshLog, "Unexpected extended data '%s' of type %d on SFTP channel.",
- data.data(), type);
- }
- void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
- {
- qCDebug(sshLog, "Remote SFTP service exited with exit code %d", exitStatus.exitStatus);
- if (channelState() == CloseRequested || channelState() == Closed)
- return;
- emit channelError(tr("The SFTP server finished unexpectedly with exit code %1.")
- .arg(exitStatus.exitStatus));
- // Note: According to the specs, the server must close the channel after this happens,
- // but OpenSSH doesn't do that, so we need to initiate the closing procedure ourselves.
- closeChannel();
- }
- void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
- {
- emit channelError(tr("The SFTP server crashed: %1.").arg(signal.error));
- closeChannel(); // See above.
- }
- void SftpChannelPrivate::handleCurrentPacket()
- {
- qCDebug(sshLog, "Handling SFTP packet of type %d", m_incomingPacket.type());
- 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.",
- tr("Unexpected packet of type %1.").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.");
- }
- qCDebug(sshLog, "sftp init received");
- const quint32 serverVersion = m_incomingPacket.extractServerVersion();
- if (serverVersion != ProtocolVersion) {
- emit channelError(tr("Protocol version mismatch: Expected %1, got %2")
- .arg(serverVersion).arg(ProtocolVersion));
- closeChannel();
- } else {
- m_sftpState = Initialized;
- emit initialized();
- }
- }
- 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(JobMap::Iterator it)
- {
- SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
- sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
- op->jobId).rawData());
- }
- void SftpChannelPrivate::handleCreateFileHandle(JobMap::Iterator it)
- {
- SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
- sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
- op->jobId).rawData());
- }
- void SftpChannelPrivate::handleGetHandle(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(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();
- qCDebug(sshLog, "%s: status = %d", Q_FUNC_INFO, response.status);
- 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::StatFile:
- case AbstractSftpOperation::RmDir:
- case AbstractSftpOperation::Rm:
- case AbstractSftpOperation::Rename:
- case AbstractSftpOperation::CreateFile:
- case AbstractSftpOperation::CreateLink:
- handleStatusGeneric(it, response);
- break;
- }
- }
- void SftpChannelPrivate::handleStatusGeneric(JobMap::Iterator it,
- const SftpStatusResponse &response)
- {
- AbstractSftpOperation::Ptr op = it.value();
- const QString error = errorMessage(response, tr("Unknown error."));
- emit finished(op->jobId, sftpStatusToError(response.status), error);
- m_jobs.erase(it);
- }
- void SftpChannelPrivate::handleMkdirStatus(JobMap::Iterator it,
- const SftpStatusResponse &response)
- {
- SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
- QSharedPointer<SftpUploadDir> parentJob = op->parentJob;
- if (parentJob == SftpUploadDir::Ptr()) {
- handleStatusGeneric(it, response);
- return;
- }
- if (parentJob->hasError) {
- m_jobs.erase(it);
- return;
- }
- typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
- DirIt dirIt = parentJob->mkdirsInProgress.find(op);
- Q_ASSERT(dirIt != parentJob->mkdirsInProgress.end());
- const QString &remoteDir = dirIt.value().remoteDir;
- if (response.status == SSH_FX_OK) {
- emit dataAvailable(parentJob->jobId,
- tr("Created remote directory \"%1\".").arg(remoteDir));
- } else if (response.status == SSH_FX_FAILURE) {
- emit dataAvailable(parentJob->jobId,
- tr("Remote directory \"%1\" already exists.").arg(remoteDir));
- } else {
- parentJob->setError();
- emit finished(parentJob->jobId,
- sftpStatusToError(response.status),
- 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);
- for (const QFileInfo &dirInfo : dirInfos) {
- const QString remoteSubDir = remoteDir + u'/' + dirInfo.fileName();
- const SftpMakeDir::Ptr mkdirOp(
- new SftpMakeDir(++m_nextJobId, remoteSubDir, parentJob));
- parentJob->mkdirsInProgress.insert(mkdirOp,
- SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
- createJob(mkdirOp);
- }
- const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
- for (const QFileInfo &fileInfo : fileInfos) {
- QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
- if (!localFile->open(QIODevice::ReadOnly)) {
- parentJob->setError();
- emit finished(parentJob->jobId,
- sftpStatusToError(response.status),
- tr("Could not open local file \"%1\": %2")
- .arg(fileInfo.absoluteFilePath(), localFile->errorString()));
- m_jobs.erase(it);
- return;
- }
- const QString remoteFilePath = remoteDir + u'/' + fileInfo.fileName();
- SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
- remoteFilePath, localFile, SftpOverwriteExisting, parentJob));
- createJob(uploadFileOp);
- parentJob->uploadsInProgress.append(uploadFileOp);
- }
- parentJob->mkdirsInProgress.erase(dirIt);
- if (parentJob->mkdirsInProgress.isEmpty()
- && parentJob->uploadsInProgress.isEmpty())
- emit finished(parentJob->jobId);
- m_jobs.erase(it);
- }
- void SftpChannelPrivate::handleLsStatus(JobMap::Iterator it,
- const SftpStatusResponse &response)
- {
- SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
- if (op->parentJob && op->parentJob->hasError) {
- m_jobs.erase(it);
- return;
- }
- switch (op->state) {
- case SftpListDir::OpenRequested:
- reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
- 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,
- sftpStatusToError(response.status),
- errorMessage(response.errorString,
- 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 || (op->parentJob && op->parentJob->hasError)) {
- m_jobs.erase(it);
- return;
- }
- {
- const QString error = errorMessage(response,
- tr("Failed to close remote directory."));
- if (op->parentJob) {
- if (!error.isEmpty()) {
- op->parentJob->setError();
- }
- if (op->parentJob->hasError) {
- emit finished(op->parentJob->jobId, sftpStatusToError(response.status), error);
- } else {
- op->parentJob->lsdirsInProgress.remove(op);
- if (op->parentJob->lsdirsInProgress.isEmpty() &&
- op->parentJob->downloadsInProgress.isEmpty()) {
- emit finished(op->parentJob->jobId);
- }
- }
- } else {
- emit finished(op->jobId, sftpStatusToError(response.status), error);
- }
- }
- m_jobs.erase(it);
- break;
- default:
- throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
- "Unexpected SSH_FXP_STATUS packet.");
- }
- }
- void SftpChannelPrivate::handleGetStatus(JobMap::Iterator it,
- const SftpStatusResponse &response)
- {
- SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
- if (op->parentJob && op->parentJob->hasError) {
- m_jobs.erase(it);
- return;
- }
- switch (op->state) {
- case SftpDownload::OpenRequested:
- reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
- tr("Failed to open remote file for reading.")));
- m_jobs.erase(it);
- break;
- case SftpDownload::Open:
- if (op->statRequested) {
- reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
- tr("Failed to retrieve information on the remote file ('stat' failed).")));
- sendTransferCloseHandle(op, response.requestId);
- } else {
- if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
- && !op->hasError)
- reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
- 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) {
- if (op->parentJob) {
- op->parentJob->downloadsInProgress.removeOne(op);
- if (op->parentJob->lsdirsInProgress.isEmpty()
- && op->parentJob->downloadsInProgress.isEmpty())
- emit finished(op->parentJob->jobId);
- } else {
- emit finished(op->jobId);
- }
- } else {
- const QString error = errorMessage(response.errorString,
- tr("Failed to close remote file."));
- reportRequestError(op, sftpStatusToError(response.status), error);
- }
- }
- removeTransferRequest(it);
- break;
- default:
- throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
- "Unexpected SSH_FXP_STATUS packet.");
- }
- }
- void SftpChannelPrivate::handlePutStatus(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) {
- emit finished(job->jobId,
- sftpStatusToError(response.status),
- errorMessage(response.errorString,
- 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, sftpStatusToError(response.status), errorMessage(response.errorString,
- 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())
- emit finished(job->parentJob->jobId);
- } else {
- emit finished(job->jobId);
- }
- } else {
- const QString error = errorMessage(response.errorString,
- tr("Failed to close remote file."));
- if (job->parentJob) {
- job->parentJob->setError();
- emit finished(job->parentJob->jobId, sftpStatusToError(response.status), error);
- } else {
- emit finished(job->jobId, sftpStatusToError(response.status), 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.");
- }
- QList<SftpFileInfo> fileInfoList;
- for (int i = 0; i < response.files.count(); ++i) {
- const SftpFile &file = response.files.at(i);
- SftpFileInfo fileInfo;
- fileInfo.name = file.fileName;
- attributesToFileInfo(file.attributes, fileInfo);
- fileInfoList << fileInfo;
- }
- if (op->parentJob) {
- handleDownloadDir(op, fileInfoList);
- } else {
- emit fileInfoAvailable(op->jobId, fileInfoList);
- }
- 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->isOpen()) {
- QFile *fileDevice = qobject_cast<QFile*>(op->localFile.data());
- if (fileDevice){
- if (!Internal::openFile(fileDevice, op->mode)) {
- reportRequestError(op, SftpError::GenericFailure, tr("Cannot open file ") + fileDevice->fileName());
- finishTransferRequest(it);
- return;
- }
- } else {
- reportRequestError(op, SftpError::GenericFailure, tr("File to upload is not open"));
- finishTransferRequest(it);
- return;
- }
- }
- if (!op->localFile->seek(op->offsets[response.requestId])) {
- reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
- finishTransferRequest(it);
- return;
- }
- if (op->localFile->write(response.data) != response.data.size()) {
- reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
- finishTransferRequest(it);
- return;
- }
- emit transferProgress(op->jobId, op->localFile->pos(), op->fileSize);
- 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);
- SftpStatFile::Ptr statOp = it.value().dynamicCast<SftpStatFile>();
- if (statOp) {
- SftpFileInfo fileInfo;
- fileInfo.name = QFileInfo(statOp->path).fileName();
- attributesToFileInfo(response.attrs, fileInfo);
- emit fileInfoAvailable(it.key(), QList<SftpFileInfo>() << fileInfo);
- emit finished(it.key());
- m_jobs.erase(it);
- return;
- }
- 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;
- emit transferProgress(op->jobId, op->offset, op->fileSize);
- 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;
- emit transferProgress(op->jobId, op->offset, op->fileSize);
- spawnWriteRequests(it);
- } else {
- if (op->parentJob)
- op->parentJob->setError();
- reportRequestError(op, SftpError::UnsupportedOperation, tr("Cannot append to remote file: "
- "Server does not support the file size attribute."));
- sendTransferCloseHandle(op, op->jobId);
- }
- }
- }
- void SftpChannelPrivate::handleDownloadDir(SftpListDir::Ptr op,
- const QList<SftpFileInfo> &fileInfoList)
- {
- if (op->parentJob->hasError) {
- return;
- }
- for (SftpFileInfo fileInfo : fileInfoList) {
- Internal::SftpDownloadDir::Dir dir = op->parentJob->lsdirsInProgress[op];
- QString fullPathRemote = QDir(dir.remoteDir).path() + u'/' + fileInfo.name;
- QString fullPathLocal = QDir(dir.localDir).path() + u'/' + fileInfo.name;
- if (fileInfo.type == FileTypeRegular) {
- QSharedPointer<QFile> localFile(new QFile(fullPathLocal));
- Internal::SftpDownload::Ptr downloadJob = Internal::SftpDownload::Ptr(
- new Internal::SftpDownload(++m_nextJobId, fullPathRemote, localFile,
- op->parentJob->mode, op->parentJob));
- op->parentJob->downloadsInProgress.append(downloadJob);
- createJob(downloadJob);
- } else if (fileInfo.type == FileTypeDirectory) {
- if (fileInfo.name == u"." || fileInfo.name == u"..") {
- continue;
- }
- if (!QDir().mkpath(fullPathLocal)) {
- reportRequestError(op, SftpError::GenericFailure, tr("Cannot create directory ") + fullPathLocal);
- break;
- }
- Internal::SftpListDir::Ptr lsdir = Internal::SftpListDir::Ptr(
- new Internal::SftpListDir(++m_nextJobId, fullPathRemote, op->parentJob));
- op->parentJob->lsdirsInProgress.insert(lsdir,
- Internal::SftpDownloadDir::Dir(fullPathLocal, fullPathRemote));
- createJob(lsdir);
- } else {
- // andres.pagliano TODO handle?
- }
- }
- }
- 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()
- {
- for (JobMap::ConstIterator it = m_jobs.constBegin(); it != m_jobs.constEnd(); ++it)
- emit finished(it.key(), SftpError::EndOfFile, tr("SFTP channel closed unexpectedly."));
- m_jobs.clear();
- m_incomingData.clear();
- m_incomingPacket.clear();
- emit closed();
- }
- void SftpChannelPrivate::handleOpenSuccessInternal()
- {
- qCDebug(sshLog, "SFTP session started");
- m_sendFacility.sendSftpPacket(remoteChannel());
- m_sftpState = SubsystemRequested;
- }
- void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
- {
- if (channelState() != SessionRequested) {
- throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
- "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
- }
- emit channelError(tr("Server could not start session: %1").arg(reason));
- }
- 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 SftpError errorType,
- const QString &error)
- {
- // andres.pagliano TODO refactor
- // Report list error during download dir
- SftpListDir::Ptr lsjob = job.dynamicCast<SftpListDir>();
- if (!lsjob.isNull() && lsjob->parentJob) {
- if (!lsjob->parentJob->hasError) {
- emit finished(lsjob->parentJob->jobId, errorType, error);
- lsjob->parentJob->hasError = true;
- }
- } else {
- // Report download error during recursive download dir
- SftpDownload::Ptr djob = job.dynamicCast<SftpDownload>();
- if (!djob.isNull() && djob->parentJob) {
- if (!djob->parentJob->hasError) {
- emit finished(djob->parentJob->jobId, errorType, error);
- djob->parentJob->hasError = true;
- }
- } else {
- // Other error
- emit finished(job->jobId, errorType, error);
- }
- }
- job->hasError = true;
- }
- void SftpChannelPrivate::finishTransferRequest(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::attributesToFileInfo(const SftpFileAttributes &attributes,
- SftpFileInfo &fileInfo) const
- {
- if (attributes.sizePresent) {
- fileInfo.sizeValid = true;
- fileInfo.size = attributes.size;
- }
- if (attributes.permissionsPresent) {
- if (attributes.permissions & 0x8000) // S_IFREG
- fileInfo.type = FileTypeRegular;
- else if (attributes.permissions & 0x4000) // S_IFDIR
- fileInfo.type = FileTypeDirectory;
- else
- fileInfo.type = FileTypeOther;
- fileInfo.permissionsValid = true;
- fileInfo.permissions = {};
- if (attributes.timesPresent) {
- fileInfo.atime = attributes.atime;
- fileInfo.mtime = attributes.mtime;
- fileInfo.timestampsValid = true;
- }
- if (attributes.permissions & 00001) // S_IXOTH
- fileInfo.permissions |= QFile::ExeOther;
- if (attributes.permissions & 00002) // S_IWOTH
- fileInfo.permissions |= QFile::WriteOther;
- if (attributes.permissions & 00004) // S_IROTH
- fileInfo.permissions |= QFile::ReadOther;
- if (attributes.permissions & 00010) // S_IXGRP
- fileInfo.permissions |= QFile::ExeGroup;
- if (attributes.permissions & 00020) // S_IWGRP
- fileInfo.permissions |= QFile::WriteGroup;
- if (attributes.permissions & 00040) // S_IRGRP
- fileInfo.permissions |= QFile::ReadGroup;
- if (attributes.permissions & 00100) // S_IXUSR
- fileInfo.permissions |= QFile::ExeUser | QFile::ExeOwner;
- if (attributes.permissions & 00200) // S_IWUSR
- fileInfo.permissions |= QFile::WriteUser | QFile::WriteOwner;
- if (attributes.permissions & 00400) // S_IRUSR
- fileInfo.permissions |= QFile::ReadUser | QFile::ReadOwner;
- }
- }
- void SftpChannelPrivate::removeTransferRequest(JobMap::Iterator it)
- {
- --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
- m_jobs.erase(it);
- }
- void SftpChannelPrivate::sendWriteRequest(JobMap::Iterator it)
- {
- SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
- emit transferProgress(job->jobId, job->localFile->pos(), job->localFile->size());
- QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
- QFileDevice *fileDevice = qobject_cast<QFileDevice*>(job->localFile.data());
- if (fileDevice && fileDevice->error() != QFileDevice::NoError) {
- if (job->parentJob)
- job->parentJob->setError();
- reportRequestError(job, SftpError::GenericFailure, 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(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);
- }
- }
- } // namespace Internal
- } // namespace QSsh
|