OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / coreplugin / ssh / sftpchannel.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "sftpchannel.h"
35 #include "sftpchannel_p.h"
36
37 #include "sshexception_p.h"
38 #include "sshincomingpacket_p.h"
39 #include "sshsendfacility_p.h"
40
41 #include <QtCore/QDir>
42 #include <QtCore/QFile>
43
44 namespace Core {
45
46 namespace Internal {
47 namespace {
48     const quint32 ProtocolVersion = 3;
49
50     QString errorMessage(const QString &serverMessage,
51         const QString &alternativeMessage)
52     {
53         return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
54     }
55
56     QString errorMessage(const SftpStatusResponse &response,
57         const QString &alternativeMessage)
58     {
59         return response.status == SSH_FX_OK ? QString()
60             : errorMessage(response.errorString, alternativeMessage);
61     }
62 } // anonymous namespace
63 } // namespace Internal
64
65 SftpChannel::SftpChannel(quint32 channelId,
66     Internal::SshSendFacility &sendFacility)
67     : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
68 {
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);
78 }
79
80 SftpChannel::State SftpChannel::state() const
81 {
82     switch (d->channelState()) {
83     case Internal::AbstractSshChannel::Inactive:
84         return Uninitialized;
85     case Internal::AbstractSshChannel::SessionRequested:
86         return Initializing;
87     case Internal::AbstractSshChannel::CloseRequested:
88         return Closing;
89     case Internal::AbstractSshChannel::Closed:
90         return Closed;
91     case Internal::AbstractSshChannel::SessionEstablished:
92         return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
93             ? Initialized : Initializing;
94     default:
95         Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
96         return Closed; // For the compiler.
97     }
98 }
99
100 void SftpChannel::initialize()
101 {
102     d->requestSessionStart();
103     d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
104 }
105
106 void SftpChannel::closeChannel()
107 {
108     d->closeChannel();
109 }
110
111 SftpJobId SftpChannel::listDirectory(const QString &path)
112 {
113     return d->createJob(Internal::SftpListDir::Ptr(
114         new Internal::SftpListDir(++d->m_nextJobId, path)));
115 }
116
117 SftpJobId SftpChannel::createDirectory(const QString &path)
118 {
119     return d->createJob(Internal::SftpMakeDir::Ptr(
120         new Internal::SftpMakeDir(++d->m_nextJobId, path)));
121 }
122
123 SftpJobId SftpChannel::removeDirectory(const QString &path)
124 {
125     return d->createJob(Internal::SftpRmDir::Ptr(
126         new Internal::SftpRmDir(++d->m_nextJobId, path)));
127 }
128
129 SftpJobId SftpChannel::removeFile(const QString &path)
130 {
131     return d->createJob(Internal::SftpRm::Ptr(
132         new Internal::SftpRm(++d->m_nextJobId, path)));
133 }
134
135 SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
136     const QString &newPath)
137 {
138     return d->createJob(Internal::SftpRename::Ptr(
139         new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
140 }
141
142 SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
143 {
144     return d->createJob(Internal::SftpCreateFile::Ptr(
145         new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
146 }
147
148 SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
149     const QString &remoteFilePath, SftpOverwriteMode mode)
150 {
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)));
156 }
157
158 SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
159     const QString &localFilePath, SftpOverwriteMode mode)
160 {
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)));
173 }
174
175 SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
176     const QString &remoteParentDirPath)
177 {
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;
193 }
194
195 SftpChannel::~SftpChannel()
196 {
197     delete d;
198 }
199
200
201 namespace Internal {
202
203 SftpChannelPrivate::SftpChannelPrivate(quint32 channelId,
204     SshSendFacility &sendFacility, SftpChannel *sftp)
205     : AbstractSshChannel(channelId, sendFacility),
206       m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp)
207 {
208 }
209
210 SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job)
211 {
212    if (m_sftp->state() != SftpChannel::Initialized)
213        return SftpInvalidJob;
214    m_jobs.insert(job->jobId, job);
215    sendData(job->initialPacket(m_outgoingPacket).rawData());
216    return job->jobId;
217 }
218
219 void SftpChannelPrivate::handleChannelSuccess()
220 {
221     if (channelState() == CloseRequested)
222         return;
223 #ifdef CREATOR_SSH_DEBUG
224     qDebug("sftp subsystem initialized");
225 #endif
226     sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
227     m_sftpState = InitSent;
228 }
229
230 void SftpChannelPrivate::handleChannelFailure()
231 {
232     if (channelState() == CloseRequested)
233         return;
234
235     if (m_sftpState != SubsystemRequested) {
236         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
237             "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
238     }
239     emit initializationFailed(tr("Server could not start sftp subsystem."));
240     closeChannel();
241 }
242
243 void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
244 {
245     if (channelState() == CloseRequested)
246         return;
247
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);
254     }
255 }
256
257 void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
258     const QByteArray &data)
259 {
260     qWarning("Unexpected extended data '%s' of type %d on SFTP channel.",
261         data.data(), type);
262 }
263
264 void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
265 {
266     const char * const message = "Remote SFTP service exited with exit code %d";
267 #ifdef CREATOR_SSH_DEBUG
268     qDebug(message, exitStatus.exitStatus);
269 #else
270     if (exitStatus.exitStatus != 0)
271         qWarning(message, exitStatus.exitStatus);
272 #endif
273 }
274
275 void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
276 {
277     qWarning("Remote SFTP service killed; signal was %s", signal.signal.data());
278 }
279
280 void SftpChannelPrivate::handleCurrentPacket()
281 {
282 #ifdef CREATOR_SSH_DEBUG
283     qDebug("Handling SFTP packet of type %d", m_incomingPacket.type());
284 #endif
285     switch (m_incomingPacket.type()) {
286     case SSH_FXP_VERSION:
287         handleServerVersion();
288         break;
289     case SSH_FXP_HANDLE:
290         handleHandle();
291         break;
292     case SSH_FXP_NAME:
293         handleName();
294         break;
295     case SSH_FXP_STATUS:
296         handleStatus();
297         break;
298     case SSH_FXP_DATA:
299         handleReadData();
300         break;
301     case SSH_FXP_ATTRS:
302         handleAttrs();
303         break;
304     default:
305         throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
306             "Unexpected packet.",
307             tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
308     }
309 }
310
311 void SftpChannelPrivate::handleServerVersion()
312 {
313     checkChannelActive();
314     if (m_sftpState != InitSent) {
315         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
316             "Unexpected SSH_FXP_VERSION packet.");
317     }
318
319 #ifdef CREATOR_SSH_DEBUG
320     qDebug("sftp init received");
321 #endif
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));
326         closeChannel();
327     } else {
328         m_sftpState = Initialized;
329         emit initialized();
330     }
331 }
332
333 void SftpChannelPrivate::handleHandle()
334 {
335     const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
336     JobMap::Iterator it = lookupJob(response.requestId);
337     const QSharedPointer<AbstractSftpOperationWithHandle> job
338         = it.value().dynamicCast<AbstractSftpOperationWithHandle>();
339     if (job.isNull()) {
340         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
341             "Unexpected SSH_FXP_HANDLE packet.");
342     }
343     if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
344         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
345             "Unexpected SSH_FXP_HANDLE packet.");
346     }
347     job->remoteHandle = response.handle;
348     job->state = AbstractSftpOperationWithHandle::Open;
349
350     switch (it.value()->type()) {
351     case AbstractSftpOperation::ListDir:
352         handleLsHandle(it);
353         break;
354     case AbstractSftpOperation::CreateFile:
355         handleCreateFileHandle(it);
356         break;
357     case AbstractSftpOperation::Download:
358         handleGetHandle(it);
359         break;
360     case AbstractSftpOperation::UploadFile:
361         handlePutHandle(it);
362         break;
363     default:
364         Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
365     }
366 }
367
368 void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it)
369 {
370     SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
371     sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
372         op->jobId).rawData());
373 }
374
375 void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it)
376 {
377     SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
378     sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
379         op->jobId).rawData());
380 }
381
382 void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it)
383 {
384     SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
385     sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
386         op->jobId).rawData());
387     op->statRequested = true;
388 }
389
390 void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it)
391 {
392     SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
393     if (op->parentJob && op->parentJob->hasError)
394         sendTransferCloseHandle(op, it.key());
395
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;
402     } else {
403         spawnWriteRequests(it);
404     }
405 }
406
407 void SftpChannelPrivate::handleStatus()
408 {
409     const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
410 #ifdef CREATOR_SSH_DEBUG
411     qDebug("%s: status = %d", Q_FUNC_INFO, response.status);
412 #endif
413     JobMap::Iterator it = lookupJob(response.requestId);
414     switch (it.value()->type()) {
415     case AbstractSftpOperation::ListDir:
416         handleLsStatus(it, response);
417         break;
418     case AbstractSftpOperation::Download:
419         handleGetStatus(it, response);
420         break;
421     case AbstractSftpOperation::UploadFile:
422         handlePutStatus(it, response);
423         break;
424     case AbstractSftpOperation::MakeDir:
425         handleMkdirStatus(it, response);
426         break;
427     case AbstractSftpOperation::RmDir:
428     case AbstractSftpOperation::Rm:
429     case AbstractSftpOperation::Rename:
430     case AbstractSftpOperation::CreateFile:
431         handleStatusGeneric(it, response);
432         break;
433     }
434 }
435
436 void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
437     const SftpStatusResponse &response)
438 {
439     AbstractSftpOperation::Ptr op = it.value();
440     const QString error = errorMessage(response, tr("Unknown error."));
441     emit finished(op->jobId, error);
442     m_jobs.erase(it);
443 }
444
445 void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it,
446     const SftpStatusResponse &response)
447 {
448     SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
449     if (op->parentJob == SftpUploadDir::Ptr()) {
450         handleStatusGeneric(it, response);
451         return;
452     }
453     if (op->parentJob->hasError) {
454         m_jobs.erase(it);
455         return;
456     }
457
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));
468     } else {
469         op->parentJob->setError();
470         emit finished(op->parentJob->jobId,
471             tr("Error creating directory '%1': %2")
472             .arg(remoteDir, response.errorString));
473         m_jobs.erase(it);
474         return;
475     }
476
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));
486         createJob(mkdirOp);
487     }
488
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()));
497             m_jobs.erase(it);
498             return;
499         }
500
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);
506     }
507
508     op->parentJob->mkdirsInProgress.erase(dirIt);
509     if (op->parentJob->mkdirsInProgress.isEmpty()
510         && op->parentJob->uploadsInProgress.isEmpty())
511         emit finished(op->parentJob->jobId);
512     m_jobs.erase(it);
513 }
514
515 void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it,
516     const SftpStatusResponse &response)
517 {
518     SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
519     switch (op->state) {
520     case SftpListDir::OpenRequested:
521         emit finished(op->jobId, errorMessage(response.errorString,
522             tr("Remote directory could not be opened for reading.")));
523         m_jobs.erase(it);
524         break;
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());
532         break;
533     case SftpListDir::CloseRequested:
534         if (!op->hasError) {
535             const QString error = errorMessage(response,
536                 tr("Failed to close remote directory."));
537             emit finished(op->jobId, error);
538         }
539         m_jobs.erase(it);
540         break;
541     default:
542         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
543             "Unexpected SSH_FXP_STATUS packet.");
544     }
545 }
546
547 void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it,
548     const SftpStatusResponse &response)
549 {
550     SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
551     switch (op->state) {
552     case SftpDownload::OpenRequested:
553         emit finished(op->jobId,
554             errorMessage(response.errorString,
555                 tr("Failed to open remote file for reading.")));
556         m_jobs.erase(it);
557         break;
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);
563         } else {
564             if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
565                 && !op->hasError)
566                 reportRequestError(op, errorMessage(response.errorString,
567                     tr("Failed to read remote file.")));
568             finishTransferRequest(it);
569         }
570         break;
571     case SftpDownload::CloseRequested:
572         Q_ASSERT(op->inFlightCount == 1);
573         if (!op->hasError) {
574             if (response.status == SSH_FX_OK)
575                 emit finished(op->jobId);
576             else
577                 reportRequestError(op, errorMessage(response.errorString,
578                     tr("Failed to close remote file.")));
579         }
580         removeTransferRequest(it);
581         break;
582     default:
583         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
584             "Unexpected SSH_FXP_STATUS packet.");
585     }
586 }
587
588 void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it,
589     const SftpStatusResponse &response)
590 {
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();
598                 emitError = true;
599             }
600         } else {
601             emitError = true;
602         }
603
604         if (emitError) {
605             emit finished(job->jobId,
606                 errorMessage(response.errorString,
607                     tr("Failed to open remote file for writing.")));
608         }
609         m_jobs.erase(it);
610         break;
611     }
612     case SftpUploadFile::Open:
613         if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
614             job->hasError = true;
615             finishTransferRequest(it);
616             return;
617         }
618
619         if (response.status == SSH_FX_OK) {
620             sendWriteRequest(it);
621         } else {
622             if (job->parentJob)
623                 job->parentJob->setError();
624             reportRequestError(job, errorMessage(response.errorString,
625                 tr("Failed to write remote file.")));
626             finishTransferRequest(it);
627         }
628         break;
629     case SftpUploadFile::CloseRequested:
630         Q_ASSERT(job->inFlightCount == 1);
631         if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
632             m_jobs.erase(it);
633             return;
634         }
635
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);
642             } else {
643                 emit finished(job->jobId);
644             }
645         } else {
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);
651             } else {
652                 emit finished(job->jobId, error);
653             }
654         }
655         m_jobs.erase(it);
656         break;
657     default:
658         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
659             "Unexpected SSH_FXP_STATUS packet.");
660     }
661 }
662
663 void SftpChannelPrivate::handleName()
664 {
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.");
673         }
674
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);
678         }
679         sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
680             op->jobId).rawData());
681         break;
682     }
683     default:
684         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
685             "Unexpected SSH_FXP_NAME packet.");
686     }
687 }
688
689 void SftpChannelPrivate::handleReadData()
690 {
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.");
696     }
697
698     SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
699     if (op->hasError) {
700         finishTransferRequest(it);
701         return;
702     }
703
704     if (!op->localFile->seek(op->offsets[response.requestId])) {
705         reportRequestError(op, op->localFile->errorString());
706         finishTransferRequest(it);
707         return;
708     }
709
710     if (op->localFile->write(response.data) != response.data.size()) {
711         reportRequestError(op, op->localFile->errorString());
712         finishTransferRequest(it);
713         return;
714     }
715
716     if (op->offset >= op->fileSize && op->fileSize != 0)
717         finishTransferRequest(it);
718     else
719         sendReadRequest(op, response.requestId);
720 }
721
722 void SftpChannelPrivate::handleAttrs()
723 {
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.");
732     }
733     Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
734         || transfer->type() == AbstractSftpOperation::Download);
735
736     if (transfer->type() == AbstractSftpOperation::Download) {
737         SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
738         if (response.attrs.sizePresent) {
739             op->fileSize = response.attrs.size;
740         } else {
741             op->fileSize = 0;
742             op->eofId = op->jobId;
743         }
744         op->statRequested = false;
745         spawnReadRequests(op);
746     } else {
747         SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
748         if (op->parentJob && op->parentJob->hasError) {
749             op->hasError = true;
750             sendTransferCloseHandle(op, op->jobId);
751             return;
752         }
753
754         if (response.attrs.sizePresent) {
755             op->offset = response.attrs.size;
756             spawnWriteRequests(it);
757         } else {
758             if (op->parentJob)
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);
763         }
764     }
765 }
766
767 SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id)
768 {
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.");
773     }
774     return it;
775 }
776
777 void SftpChannelPrivate::closeHook()
778 {
779     m_jobs.clear();
780     m_incomingData.clear();
781     m_incomingPacket.clear();
782     emit closed();
783 }
784
785 void SftpChannelPrivate::handleOpenSuccessInternal()
786 {
787 #ifdef CREATOR_SSH_DEBUG
788     qDebug("SFTP session started");
789 #endif
790     m_sendFacility.sendSftpPacket(remoteChannel());
791     m_sftpState = SubsystemRequested;
792 }
793
794 void SftpChannelPrivate::handleOpenFailureInternal()
795 {
796     if (channelState() != SessionRequested) {
797         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
798             "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
799     }
800     emit initializationFailed(tr("Server could not start session."));
801 }
802
803 void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
804     quint32 requestId)
805 {
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;
813 }
814
815 void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
816     const QString &error)
817 {
818     emit finished(job->jobId, error);
819     job->hasError = true;
820 }
821
822 void SftpChannelPrivate::finishTransferRequest(const JobMap::Iterator &it)
823 {
824     AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>();
825     if (job->inFlightCount == 1)
826         sendTransferCloseHandle(job, it.key());
827     else
828         removeTransferRequest(it);
829 }
830
831 void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
832     quint32 requestId)
833 {
834     sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle,
835        requestId).rawData());
836     job->state = SftpDownload::CloseRequested;
837 }
838
839 void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it)
840 {
841     --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
842     m_jobs.erase(it);
843 }
844
845 void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
846 {
847     SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
848     QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
849     if (job->localFile->error() != QFile::NoError) {
850         if (job->parentJob)
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);
857     } else {
858         sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle,
859             job->offset, data, it.key()).rawData());
860         job->offset += AbstractSftpPacket::MaxDataSize;
861     }
862 }
863
864 void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it)
865 {
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));
871 }
872
873 void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
874 {
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);
881     }
882 }
883
884 } // namespace Internal
885 } // namespace Core