1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qnetworkaccessftpbackend_p.h"
43 #include "qnetworkaccessmanager_p.h"
44 #include "QtNetwork/qauthenticator.h"
45 #include "private/qnoncontiguousbytedevice_p.h"
55 static QByteArray makeCacheKey(const QUrl &url)
58 copy.setPort(url.port(DefaultFtpPort));
59 return "ftp-connection:" +
60 copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath | QUrl::RemoveQuery |
61 QUrl::RemoveFragment);
64 QNetworkAccessBackend *
65 QNetworkAccessFtpBackendFactory::create(QNetworkAccessManager::Operation op,
66 const QNetworkRequest &request) const
68 // is it an operation we know of?
70 case QNetworkAccessManager::GetOperation:
71 case QNetworkAccessManager::PutOperation:
75 // no, we can't handle this operation
79 QUrl url = request.url();
80 if (url.scheme().compare(QLatin1String("ftp"), Qt::CaseInsensitive) == 0)
81 return new QNetworkAccessFtpBackend;
85 class QNetworkAccessCachedFtpConnection: public QFtp, public QNetworkAccessCache::CacheableObject
89 QNetworkAccessCachedFtpConnection()
97 connect(this, SIGNAL(done(bool)), this, SLOT(deleteLater()));
102 QNetworkAccessFtpBackend::QNetworkAccessFtpBackend()
103 : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1),
104 supportsSize(false), supportsMdtm(false), state(Idle)
108 QNetworkAccessFtpBackend::~QNetworkAccessFtpBackend()
110 //if backend destroyed while in use, then abort (this is the code path from QNetworkReply::abort)
111 if (ftp && state != Disconnecting)
116 void QNetworkAccessFtpBackend::open()
118 #ifndef QT_NO_NETWORKPROXY
120 foreach (const QNetworkProxy &p, proxyList()) {
121 // use the first FTP proxy
122 // or no proxy at all
123 if (p.type() == QNetworkProxy::FtpCachingProxy
124 || p.type() == QNetworkProxy::NoProxy) {
130 // did we find an FTP proxy or a NoProxy?
131 if (proxy.type() == QNetworkProxy::DefaultProxy) {
132 // unsuitable proxies
133 error(QNetworkReply::ProxyNotFoundError,
134 tr("No suitable proxy found"));
141 QUrl url = this->url();
142 if (url.path().isEmpty()) {
143 url.setPath(QLatin1String("/"));
146 if (url.path().endsWith(QLatin1Char('/'))) {
147 error(QNetworkReply::ContentOperationNotPermittedError,
148 tr("Cannot open %1: is a directory").arg(url.toString()));
154 QNetworkAccessCache* objectCache = QNetworkAccessManagerPrivate::getObjectCache(this);
155 QByteArray cacheKey = makeCacheKey(url);
156 if (!objectCache->requestEntry(cacheKey, this,
157 SLOT(ftpConnectionReady(QNetworkAccessCache::CacheableObject*)))) {
158 ftp = new QNetworkAccessCachedFtpConnection;
159 #ifndef QT_NO_BEARERMANAGEMENT
160 //copy network session down to the QFtp
161 ftp->setProperty("_q_networksession", property("_q_networksession"));
163 #ifndef QT_NO_NETWORKPROXY
164 if (proxy.type() == QNetworkProxy::FtpCachingProxy)
165 ftp->setProxy(proxy.hostName(), proxy.port());
167 ftp->connectToHost(url.host(), url.port(DefaultFtpPort));
168 ftp->login(url.userName(), url.password());
170 objectCache->addEntry(cacheKey, ftp);
171 ftpConnectionReady(ftp);
175 if (operation() == QNetworkAccessManager::PutOperation) {
176 uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
177 uploadDevice->setParent(this);
181 void QNetworkAccessFtpBackend::closeDownstreamChannel()
183 state = Disconnecting;
184 if (operation() == QNetworkAccessManager::GetOperation)
188 void QNetworkAccessFtpBackend::downstreamReadyWrite()
190 if (state == Transferring && ftp && ftp->bytesAvailable())
194 void QNetworkAccessFtpBackend::ftpConnectionReady(QNetworkAccessCache::CacheableObject *o)
196 ftp = static_cast<QNetworkAccessCachedFtpConnection *>(o);
197 connect(ftp, SIGNAL(done(bool)), SLOT(ftpDone()));
198 connect(ftp, SIGNAL(rawCommandReply(int,QString)), SLOT(ftpRawCommandReply(int,QString)));
199 connect(ftp, SIGNAL(readyRead()), SLOT(ftpReadyRead()));
201 // is the login process done already?
202 if (ftp->state() == QFtp::LoggedIn)
205 // no, defer the actual operation until after we've logged in
208 void QNetworkAccessFtpBackend::disconnectFromFtp()
210 state = Disconnecting;
213 disconnect(ftp, 0, this, 0);
215 QByteArray key = makeCacheKey(url());
216 QNetworkAccessManagerPrivate::getObjectCache(this)->releaseEntry(key);
222 void QNetworkAccessFtpBackend::ftpDone()
224 // the last command we sent is done
225 if (state == LoggingIn && ftp->state() != QFtp::LoggedIn) {
226 if (ftp->state() == QFtp::Connected) {
227 // the login did not succeed
229 QString userInfo = newUrl.userInfo();
230 newUrl.setUserInfo(QString());
234 authenticationRequired(&auth);
236 if (!auth.isNull()) {
238 newUrl.setUserName(auth.user());
239 ftp->login(auth.user(), auth.password());
243 // Re insert the user info so that we can remove the cache entry.
244 newUrl.setUserInfo(userInfo);
247 error(QNetworkReply::AuthenticationRequiredError,
248 tr("Logging in to %1 failed: authentication required")
251 // we did not connect
252 QNetworkReply::NetworkError code;
253 switch (ftp->error()) {
254 case QFtp::HostNotFound:
255 code = QNetworkReply::HostNotFoundError;
258 case QFtp::ConnectionRefused:
259 code = QNetworkReply::ConnectionRefusedError;
263 code = QNetworkReply::ProtocolFailure;
267 error(code, ftp->errorString());
270 // we're not connected, so remove the cache entry:
271 QByteArray key = makeCacheKey(url());
272 QNetworkAccessManagerPrivate::getObjectCache(this)->removeEntry(key);
274 disconnect(ftp, 0, this, 0);
278 state = Disconnecting;
284 if (ftp->error() != QFtp::NoError) {
286 if (operation() == QNetworkAccessManager::GetOperation)
287 msg = tr("Error while downloading %1: %2");
289 msg = tr("Error while uploading %1: %2");
290 msg = msg.arg(url().toString(), ftp->errorString());
292 if (state == Statting)
293 // file probably doesn't exist
294 error(QNetworkReply::ContentNotFoundError, msg);
296 error(QNetworkReply::ContentAccessDenied, msg);
302 if (state == LoggingIn) {
303 state = CheckingFeatures;
304 if (operation() == QNetworkAccessManager::GetOperation) {
305 // send help command to find out if server supports "SIZE" and "MDTM"
306 QString command = url().path();
307 command.prepend(QLatin1String("%1 "));
308 helpId = ftp->rawCommand(QLatin1String("HELP")); // get supported commands
312 } else if (state == CheckingFeatures) {
314 if (operation() == QNetworkAccessManager::GetOperation) {
315 // logged in successfully, send the stat requests (if supported)
316 QString command = url().path();
317 command.prepend(QLatin1String("%1 "));
319 ftp->rawCommand(QLatin1String("TYPE I"));
320 sizeId = ftp->rawCommand(command.arg(QLatin1String("SIZE"))); // get size
323 mdtmId = ftp->rawCommand(command.arg(QLatin1String("MDTM"))); // get modified time
324 if (!supportsSize && !supportsMdtm)
325 ftpDone(); // no commands sent, move to the next state
329 } else if (state == Statting) {
330 // statted successfully, send the actual request
331 emit metaDataChanged();
332 state = Transferring;
334 QFtp::TransferType type = QFtp::Binary;
335 if (operation() == QNetworkAccessManager::GetOperation) {
336 setCachingEnabled(true);
337 ftp->get(url().path(), 0, type);
339 ftp->put(uploadDevice, url().path(), type);
342 } else if (state == Transferring) {
343 // upload or download finished
349 void QNetworkAccessFtpBackend::ftpReadyRead()
351 QByteArray data = ftp->readAll();
352 QByteDataBuffer list;
354 data.clear(); // important because of implicit sharing!
355 writeDownstreamData(list);
358 void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text)
360 //qDebug() << "FTP reply:" << code << text;
361 int id = ftp->currentId();
363 if ((id == helpId) && ((code == 200) || (code == 214))) { // supported commands
364 // the "FEAT" ftp command would be nice here, but it is not part of the
365 // initial FTP RFC 959, neither ar "SIZE" nor "MDTM" (they are all specified
367 if (text.contains(QLatin1String("SIZE"), Qt::CaseSensitive))
369 if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive))
371 } else if (code == 213) { // file status
373 // reply to the size command
374 setHeader(QNetworkRequest::ContentLengthHeader, text.toLongLong());
375 #ifndef QT_NO_DATESTRING
376 } else if (id == mdtmId) {
377 QDateTime dt = QDateTime::fromString(text, QLatin1String("yyyyMMddHHmmss"));
378 setHeader(QNetworkRequest::LastModifiedHeader, dt);