1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "sftpchannel.h"
35 #include "sftpchannel_p.h"
37 #include "sshexception_p.h"
38 #include "sshincomingpacket_p.h"
39 #include "sshsendfacility_p.h"
41 #include <QtCore/QDir>
42 #include <QtCore/QFile>
48 const quint32 ProtocolVersion = 3;
50 QString errorMessage(const QString &serverMessage,
51 const QString &alternativeMessage)
53 return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
56 QString errorMessage(const SftpStatusResponse &response,
57 const QString &alternativeMessage)
59 return response.status == SSH_FX_OK ? QString()
60 : errorMessage(response.errorString, alternativeMessage);
62 } // anonymous namespace
63 } // namespace Internal
65 SftpChannel::SftpChannel(quint32 channelId,
66 Internal::SshSendFacility &sendFacility)
67 : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
69 connect(d, SIGNAL(initialized()), this, SIGNAL(initialized()),
70 Qt::QueuedConnection);
71 connect(d, SIGNAL(initializationFailed(QString)), this,
72 SIGNAL(initializationFailed(QString)), Qt::QueuedConnection);
73 connect(d, SIGNAL(dataAvailable(Core::SftpJobId, QString)), this,
74 SIGNAL(dataAvailable(Core::SftpJobId, QString)), Qt::QueuedConnection);
75 connect(d, SIGNAL(finished(Core::SftpJobId,QString)), this,
76 SIGNAL(finished(Core::SftpJobId,QString)), Qt::QueuedConnection);
77 connect(d, SIGNAL(closed()), this, SIGNAL(closed()), Qt::QueuedConnection);
80 SftpChannel::State SftpChannel::state() const
82 switch (d->channelState()) {
83 case Internal::AbstractSshChannel::Inactive:
85 case Internal::AbstractSshChannel::SessionRequested:
87 case Internal::AbstractSshChannel::CloseRequested:
89 case Internal::AbstractSshChannel::Closed:
91 case Internal::AbstractSshChannel::SessionEstablished:
92 return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
93 ? Initialized : Initializing;
95 Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
96 return Closed; // For the compiler.
100 void SftpChannel::initialize()
102 d->requestSessionStart();
103 d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
106 void SftpChannel::closeChannel()
111 SftpJobId SftpChannel::listDirectory(const QString &path)
113 return d->createJob(Internal::SftpListDir::Ptr(
114 new Internal::SftpListDir(++d->m_nextJobId, path)));
117 SftpJobId SftpChannel::createDirectory(const QString &path)
119 return d->createJob(Internal::SftpMakeDir::Ptr(
120 new Internal::SftpMakeDir(++d->m_nextJobId, path)));
123 SftpJobId SftpChannel::removeDirectory(const QString &path)
125 return d->createJob(Internal::SftpRmDir::Ptr(
126 new Internal::SftpRmDir(++d->m_nextJobId, path)));
129 SftpJobId SftpChannel::removeFile(const QString &path)
131 return d->createJob(Internal::SftpRm::Ptr(
132 new Internal::SftpRm(++d->m_nextJobId, path)));
135 SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
136 const QString &newPath)
138 return d->createJob(Internal::SftpRename::Ptr(
139 new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
142 SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
144 return d->createJob(Internal::SftpCreateFile::Ptr(
145 new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
148 SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
149 const QString &remoteFilePath, SftpOverwriteMode mode)
151 QSharedPointer<QFile> localFile(new QFile(localFilePath));
152 if (!localFile->open(QIODevice::ReadOnly))
153 return SftpInvalidJob;
154 return d->createJob(Internal::SftpUploadFile::Ptr(
155 new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
158 SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
159 const QString &localFilePath, SftpOverwriteMode mode)
161 QSharedPointer<QFile> localFile(new QFile(localFilePath));
162 if (mode == SftpSkipExisting && localFile->exists())
163 return SftpInvalidJob;
164 QIODevice::OpenMode openMode = QIODevice::WriteOnly;
165 if (mode == SftpOverwriteExisting)
166 openMode |= QIODevice::Truncate;
167 else if (mode == SftpAppendToExisting)
168 openMode |= QIODevice::Append;
169 if (!localFile->open(openMode))
170 return SftpInvalidJob;
171 return d->createJob(Internal::SftpDownload::Ptr(
172 new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile)));
175 SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
176 const QString &remoteParentDirPath)
178 if (state() != Initialized)
179 return SftpInvalidJob;
180 const QDir localDir(localDirPath);
181 if (!localDir.exists() || !localDir.isReadable())
182 return SftpInvalidJob;
183 const Internal::SftpUploadDir::Ptr uploadDirOp(
184 new Internal::SftpUploadDir(++d->m_nextJobId));
185 const QString remoteDirPath
186 = remoteParentDirPath + QLatin1Char('/') + localDir.dirName();
187 const Internal::SftpMakeDir::Ptr mkdirOp(
188 new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp));
189 uploadDirOp->mkdirsInProgress.insert(mkdirOp,
190 Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath));
191 d->createJob(mkdirOp);
192 return uploadDirOp->jobId;
195 SftpChannel::~SftpChannel()
203 SftpChannelPrivate::SftpChannelPrivate(quint32 channelId,
204 SshSendFacility &sendFacility, SftpChannel *sftp)
205 : AbstractSshChannel(channelId, sendFacility),
206 m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp)
210 SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job)
212 if (m_sftp->state() != SftpChannel::Initialized)
213 return SftpInvalidJob;
214 m_jobs.insert(job->jobId, job);
215 sendData(job->initialPacket(m_outgoingPacket).rawData());
219 void SftpChannelPrivate::handleChannelSuccess()
221 if (channelState() == CloseRequested)
223 #ifdef CREATOR_SSH_DEBUG
224 qDebug("sftp subsystem initialized");
226 sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
227 m_sftpState = InitSent;
230 void SftpChannelPrivate::handleChannelFailure()
232 if (channelState() == CloseRequested)
235 if (m_sftpState != SubsystemRequested) {
236 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
237 "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
239 emit initializationFailed(tr("Server could not start sftp subsystem."));
243 void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
245 if (channelState() == CloseRequested)
248 m_incomingData += data;
249 m_incomingPacket.consumeData(m_incomingData);
250 while (m_incomingPacket.isComplete()) {
251 handleCurrentPacket();
252 m_incomingPacket.clear();
253 m_incomingPacket.consumeData(m_incomingData);
257 void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
258 const QByteArray &data)
260 qWarning("Unexpected extended data '%s' of type %d on SFTP channel.",
264 void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
266 const char * const message = "Remote SFTP service exited with exit code %d";
267 #ifdef CREATOR_SSH_DEBUG
268 qDebug(message, exitStatus.exitStatus);
270 if (exitStatus.exitStatus != 0)
271 qWarning(message, exitStatus.exitStatus);
275 void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
277 qWarning("Remote SFTP service killed; signal was %s", signal.signal.data());
280 void SftpChannelPrivate::handleCurrentPacket()
282 #ifdef CREATOR_SSH_DEBUG
283 qDebug("Handling SFTP packet of type %d", m_incomingPacket.type());
285 switch (m_incomingPacket.type()) {
286 case SSH_FXP_VERSION:
287 handleServerVersion();
305 throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
306 "Unexpected packet.",
307 tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
311 void SftpChannelPrivate::handleServerVersion()
313 checkChannelActive();
314 if (m_sftpState != InitSent) {
315 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
316 "Unexpected SSH_FXP_VERSION packet.");
319 #ifdef CREATOR_SSH_DEBUG
320 qDebug("sftp init received");
322 const quint32 serverVersion = m_incomingPacket.extractServerVersion();
323 if (serverVersion != ProtocolVersion) {
324 emit initializationFailed(tr("Protocol version mismatch: Expected %1, got %2")
325 .arg(serverVersion).arg(ProtocolVersion));
328 m_sftpState = Initialized;
333 void SftpChannelPrivate::handleHandle()
335 const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
336 JobMap::Iterator it = lookupJob(response.requestId);
337 const QSharedPointer<AbstractSftpOperationWithHandle> job
338 = it.value().dynamicCast<AbstractSftpOperationWithHandle>();
340 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
341 "Unexpected SSH_FXP_HANDLE packet.");
343 if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
344 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
345 "Unexpected SSH_FXP_HANDLE packet.");
347 job->remoteHandle = response.handle;
348 job->state = AbstractSftpOperationWithHandle::Open;
350 switch (it.value()->type()) {
351 case AbstractSftpOperation::ListDir:
354 case AbstractSftpOperation::CreateFile:
355 handleCreateFileHandle(it);
357 case AbstractSftpOperation::Download:
360 case AbstractSftpOperation::UploadFile:
364 Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
368 void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it)
370 SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
371 sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
372 op->jobId).rawData());
375 void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it)
377 SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
378 sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
379 op->jobId).rawData());
382 void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it)
384 SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
385 sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
386 op->jobId).rawData());
387 op->statRequested = true;
390 void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it)
392 SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
393 if (op->parentJob && op->parentJob->hasError)
394 sendTransferCloseHandle(op, it.key());
396 // OpenSSH does not implement the RFC's append functionality, so we
397 // have to emulate it.
398 if (op->mode == SftpAppendToExisting) {
399 sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
400 op->jobId).rawData());
401 op->statRequested = true;
403 spawnWriteRequests(it);
407 void SftpChannelPrivate::handleStatus()
409 const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
410 #ifdef CREATOR_SSH_DEBUG
411 qDebug("%s: status = %d", Q_FUNC_INFO, response.status);
413 JobMap::Iterator it = lookupJob(response.requestId);
414 switch (it.value()->type()) {
415 case AbstractSftpOperation::ListDir:
416 handleLsStatus(it, response);
418 case AbstractSftpOperation::Download:
419 handleGetStatus(it, response);
421 case AbstractSftpOperation::UploadFile:
422 handlePutStatus(it, response);
424 case AbstractSftpOperation::MakeDir:
425 handleMkdirStatus(it, response);
427 case AbstractSftpOperation::RmDir:
428 case AbstractSftpOperation::Rm:
429 case AbstractSftpOperation::Rename:
430 case AbstractSftpOperation::CreateFile:
431 handleStatusGeneric(it, response);
436 void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
437 const SftpStatusResponse &response)
439 AbstractSftpOperation::Ptr op = it.value();
440 const QString error = errorMessage(response, tr("Unknown error."));
441 emit finished(op->jobId, error);
445 void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it,
446 const SftpStatusResponse &response)
448 SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
449 if (op->parentJob == SftpUploadDir::Ptr()) {
450 handleStatusGeneric(it, response);
453 if (op->parentJob->hasError) {
458 typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
459 DirIt dirIt = op->parentJob->mkdirsInProgress.find(op);
460 Q_ASSERT(dirIt != op->parentJob->mkdirsInProgress.end());
461 const QString &remoteDir = dirIt.value().remoteDir;
462 if (response.status == SSH_FX_OK) {
463 emit dataAvailable(op->parentJob->jobId,
464 tr("Created remote directory '%1'.").arg(remoteDir));
465 } else if (response.status == SSH_FX_FAILURE) {
466 emit dataAvailable(op->parentJob->jobId,
467 tr("Remote directory '%1' already exists.").arg(remoteDir));
469 op->parentJob->setError();
470 emit finished(op->parentJob->jobId,
471 tr("Error creating directory '%1': %2")
472 .arg(remoteDir, response.errorString));
477 QDir localDir(dirIt.value().localDir);
478 const QFileInfoList &dirInfos
479 = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
480 foreach (const QFileInfo &dirInfo, dirInfos) {
481 const QString remoteSubDir = remoteDir + '/' + dirInfo.fileName();
482 const SftpMakeDir::Ptr mkdirOp(
483 new SftpMakeDir(++m_nextJobId, remoteSubDir, op->parentJob));
484 op->parentJob->mkdirsInProgress.insert(mkdirOp,
485 SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
489 const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
490 foreach (const QFileInfo &fileInfo, fileInfos) {
491 QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
492 if (!localFile->open(QIODevice::ReadOnly)) {
493 op->parentJob->setError();
494 emit finished(op->parentJob->jobId,
495 tr("Could not open local file '%1': %2")
496 .arg(fileInfo.absoluteFilePath(), localFile->error()));
501 const QString remoteFilePath = remoteDir + '/' + fileInfo.fileName();
502 SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
503 remoteFilePath, localFile, SftpOverwriteExisting, op->parentJob));
504 createJob(uploadFileOp);
505 op->parentJob->uploadsInProgress.append(uploadFileOp);
508 op->parentJob->mkdirsInProgress.erase(dirIt);
509 if (op->parentJob->mkdirsInProgress.isEmpty()
510 && op->parentJob->uploadsInProgress.isEmpty())
511 emit finished(op->parentJob->jobId);
515 void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it,
516 const SftpStatusResponse &response)
518 SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
520 case SftpListDir::OpenRequested:
521 emit finished(op->jobId, errorMessage(response.errorString,
522 tr("Remote directory could not be opened for reading.")));
525 case SftpListDir::Open:
526 if (response.status != SSH_FX_EOF)
527 reportRequestError(op, errorMessage(response.errorString,
528 tr("Failed to list remote directory contents.")));
529 op->state = SftpListDir::CloseRequested;
530 sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
531 op->jobId).rawData());
533 case SftpListDir::CloseRequested:
535 const QString error = errorMessage(response,
536 tr("Failed to close remote directory."));
537 emit finished(op->jobId, error);
542 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
543 "Unexpected SSH_FXP_STATUS packet.");
547 void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it,
548 const SftpStatusResponse &response)
550 SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
552 case SftpDownload::OpenRequested:
553 emit finished(op->jobId,
554 errorMessage(response.errorString,
555 tr("Failed to open remote file for reading.")));
558 case SftpDownload::Open:
559 if (op->statRequested) {
560 reportRequestError(op, errorMessage(response.errorString,
561 tr("Failed retrieve information on the remote file ('stat' failed).")));
562 sendTransferCloseHandle(op, response.requestId);
564 if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
566 reportRequestError(op, errorMessage(response.errorString,
567 tr("Failed to read remote file.")));
568 finishTransferRequest(it);
571 case SftpDownload::CloseRequested:
572 Q_ASSERT(op->inFlightCount == 1);
574 if (response.status == SSH_FX_OK)
575 emit finished(op->jobId);
577 reportRequestError(op, errorMessage(response.errorString,
578 tr("Failed to close remote file.")));
580 removeTransferRequest(it);
583 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
584 "Unexpected SSH_FXP_STATUS packet.");
588 void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it,
589 const SftpStatusResponse &response)
591 SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
592 switch (job->state) {
593 case SftpUploadFile::OpenRequested: {
594 bool emitError = false;
595 if (job->parentJob) {
596 if (!job->parentJob->hasError) {
597 job->parentJob->setError();
605 emit finished(job->jobId,
606 errorMessage(response.errorString,
607 tr("Failed to open remote file for writing.")));
612 case SftpUploadFile::Open:
613 if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
614 job->hasError = true;
615 finishTransferRequest(it);
619 if (response.status == SSH_FX_OK) {
620 sendWriteRequest(it);
623 job->parentJob->setError();
624 reportRequestError(job, errorMessage(response.errorString,
625 tr("Failed to write remote file.")));
626 finishTransferRequest(it);
629 case SftpUploadFile::CloseRequested:
630 Q_ASSERT(job->inFlightCount == 1);
631 if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
636 if (response.status == SSH_FX_OK) {
637 if (job->parentJob) {
638 job->parentJob->uploadsInProgress.removeOne(job);
639 if (job->parentJob->mkdirsInProgress.isEmpty()
640 && job->parentJob->uploadsInProgress.isEmpty())
641 emit finished(job->parentJob->jobId);
643 emit finished(job->jobId);
646 const QString error = errorMessage(response.errorString,
647 tr("Failed to close remote file."));
648 if (job->parentJob) {
649 job->parentJob->setError();
650 emit finished(job->parentJob->jobId, error);
652 emit finished(job->jobId, error);
658 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
659 "Unexpected SSH_FXP_STATUS packet.");
663 void SftpChannelPrivate::handleName()
665 const SftpNameResponse &response = m_incomingPacket.asNameResponse();
666 JobMap::Iterator it = lookupJob(response.requestId);
667 switch (it.value()->type()) {
668 case AbstractSftpOperation::ListDir: {
669 SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
670 if (op->state != SftpListDir::Open) {
671 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
672 "Unexpected SSH_FXP_NAME packet.");
675 for (int i = 0; i < response.files.count(); ++i) {
676 const SftpFile &file = response.files.at(i);
677 emit dataAvailable(op->jobId, file.fileName);
679 sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
680 op->jobId).rawData());
684 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
685 "Unexpected SSH_FXP_NAME packet.");
689 void SftpChannelPrivate::handleReadData()
691 const SftpDataResponse &response = m_incomingPacket.asDataResponse();
692 JobMap::Iterator it = lookupJob(response.requestId);
693 if (it.value()->type() != AbstractSftpOperation::Download) {
694 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
695 "Unexpected SSH_FXP_DATA packet.");
698 SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
700 finishTransferRequest(it);
704 if (!op->localFile->seek(op->offsets[response.requestId])) {
705 reportRequestError(op, op->localFile->errorString());
706 finishTransferRequest(it);
710 if (op->localFile->write(response.data) != response.data.size()) {
711 reportRequestError(op, op->localFile->errorString());
712 finishTransferRequest(it);
716 if (op->offset >= op->fileSize && op->fileSize != 0)
717 finishTransferRequest(it);
719 sendReadRequest(op, response.requestId);
722 void SftpChannelPrivate::handleAttrs()
724 const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse();
725 JobMap::Iterator it = lookupJob(response.requestId);
726 AbstractSftpTransfer::Ptr transfer
727 = it.value().dynamicCast<AbstractSftpTransfer>();
728 if (!transfer || transfer->state != AbstractSftpTransfer::Open
729 || !transfer->statRequested) {
730 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
731 "Unexpected SSH_FXP_ATTRS packet.");
733 Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
734 || transfer->type() == AbstractSftpOperation::Download);
736 if (transfer->type() == AbstractSftpOperation::Download) {
737 SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
738 if (response.attrs.sizePresent) {
739 op->fileSize = response.attrs.size;
742 op->eofId = op->jobId;
744 op->statRequested = false;
745 spawnReadRequests(op);
747 SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
748 if (op->parentJob && op->parentJob->hasError) {
750 sendTransferCloseHandle(op, op->jobId);
754 if (response.attrs.sizePresent) {
755 op->offset = response.attrs.size;
756 spawnWriteRequests(it);
759 op->parentJob->setError();
760 reportRequestError(op, tr("Cannot append to remote file: "
761 "Server does not support the file size attribute."));
762 sendTransferCloseHandle(op, op->jobId);
767 SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id)
769 JobMap::Iterator it = m_jobs.find(id);
770 if (it == m_jobs.end()) {
771 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
772 "Invalid request id in SFTP packet.");
777 void SftpChannelPrivate::closeHook()
780 m_incomingData.clear();
781 m_incomingPacket.clear();
785 void SftpChannelPrivate::handleOpenSuccessInternal()
787 #ifdef CREATOR_SSH_DEBUG
788 qDebug("SFTP session started");
790 m_sendFacility.sendSftpPacket(remoteChannel());
791 m_sftpState = SubsystemRequested;
794 void SftpChannelPrivate::handleOpenFailureInternal()
796 if (channelState() != SessionRequested) {
797 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
798 "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
800 emit initializationFailed(tr("Server could not start session."));
803 void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
806 Q_ASSERT(job->eofId == SftpInvalidJob);
807 sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
808 AbstractSftpPacket::MaxDataSize, requestId).rawData());
809 job->offsets[requestId] = job->offset;
810 job->offset += AbstractSftpPacket::MaxDataSize;
811 if (job->offset >= job->fileSize)
812 job->eofId = requestId;
815 void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
816 const QString &error)
818 emit finished(job->jobId, error);
819 job->hasError = true;
822 void SftpChannelPrivate::finishTransferRequest(const JobMap::Iterator &it)
824 AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>();
825 if (job->inFlightCount == 1)
826 sendTransferCloseHandle(job, it.key());
828 removeTransferRequest(it);
831 void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
834 sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle,
835 requestId).rawData());
836 job->state = SftpDownload::CloseRequested;
839 void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it)
841 --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
845 void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
847 SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
848 QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
849 if (job->localFile->error() != QFile::NoError) {
851 job->parentJob->setError();
852 reportRequestError(job, tr("Error reading local file: %1")
853 .arg(job->localFile->errorString()));
854 finishTransferRequest(it);
855 } else if (data.isEmpty()) {
856 finishTransferRequest(it);
858 sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle,
859 job->offset, data, it.key()).rawData());
860 job->offset += AbstractSftpPacket::MaxDataSize;
864 void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it)
866 SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
867 op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
868 sendWriteRequest(it);
869 for (int i = 1; !op->hasError && i < op->inFlightCount; ++i)
870 sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
873 void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
875 job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
876 sendReadRequest(job, job->jobId);
877 for (int i = 1; i < job->inFlightCount; ++i) {
878 const quint32 requestId = ++m_nextJobId;
879 m_jobs.insert(requestId, job);
880 sendReadRequest(job, requestId);
884 } // namespace Internal