choke uploadProgress signals
[profile/ivi/qtbase.git] / src / network / access / qnetworkaccessftpbackend.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qnetworkaccessftpbackend_p.h"
43 #include "qnetworkaccessmanager_p.h"
44 #include "QtNetwork/qauthenticator.h"
45 #include "private/qnoncontiguousbytedevice_p.h"
46
47 #ifndef QT_NO_FTP
48
49 QT_BEGIN_NAMESPACE
50
51 enum {
52     DefaultFtpPort = 21
53 };
54
55 static QByteArray makeCacheKey(const QUrl &url)
56 {
57     QUrl copy = url;
58     copy.setPort(url.port(DefaultFtpPort));
59     return "ftp-connection:" +
60         copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath | QUrl::RemoveQuery |
61                        QUrl::RemoveFragment);
62 }
63
64 QNetworkAccessBackend *
65 QNetworkAccessFtpBackendFactory::create(QNetworkAccessManager::Operation op,
66                                         const QNetworkRequest &request) const
67 {
68     // is it an operation we know of?
69     switch (op) {
70     case QNetworkAccessManager::GetOperation:
71     case QNetworkAccessManager::PutOperation:
72         break;
73
74     default:
75         // no, we can't handle this operation
76         return 0;
77     }
78
79     QUrl url = request.url();
80     if (url.scheme().compare(QLatin1String("ftp"), Qt::CaseInsensitive) == 0)
81         return new QNetworkAccessFtpBackend;
82     return 0;
83 }
84
85 class QNetworkAccessCachedFtpConnection: public QFtp, public QNetworkAccessCache::CacheableObject
86 {
87     // Q_OBJECT
88 public:
89     QNetworkAccessCachedFtpConnection()
90     {
91         setExpires(true);
92         setShareable(false);
93     }
94
95     void dispose()
96     {
97         connect(this, SIGNAL(done(bool)), this, SLOT(deleteLater()));
98         close();
99     }
100 };
101
102 QNetworkAccessFtpBackend::QNetworkAccessFtpBackend()
103     : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1),
104     supportsSize(false), supportsMdtm(false), state(Idle)
105 {
106 }
107
108 QNetworkAccessFtpBackend::~QNetworkAccessFtpBackend()
109 {
110     //if backend destroyed while in use, then abort (this is the code path from QNetworkReply::abort)
111     if (ftp && state != Disconnecting)
112         ftp->abort();
113     disconnectFromFtp();
114 }
115
116 void QNetworkAccessFtpBackend::open()
117 {
118 #ifndef QT_NO_NETWORKPROXY
119     QNetworkProxy proxy;
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) {
125             proxy = p;
126             break;
127         }
128     }
129
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"));
135         finished();
136         return;
137     }
138
139 #endif
140
141     QUrl url = this->url();
142     if (url.path().isEmpty()) {
143         url.setPath(QLatin1String("/"));
144         setUrl(url);
145     }
146     if (url.path().endsWith(QLatin1Char('/'))) {
147         error(QNetworkReply::ContentOperationNotPermittedError,
148               tr("Cannot open %1: is a directory").arg(url.toString()));
149         finished();
150         return;
151     }
152     state = LoggingIn;
153
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"));
162 #endif
163 #ifndef QT_NO_NETWORKPROXY
164         if (proxy.type() == QNetworkProxy::FtpCachingProxy)
165             ftp->setProxy(proxy.hostName(), proxy.port());
166 #endif
167         ftp->connectToHost(url.host(), url.port(DefaultFtpPort));
168         ftp->login(url.userName(), url.password());
169
170         objectCache->addEntry(cacheKey, ftp);
171         ftpConnectionReady(ftp);
172     }
173
174     // Put operation
175     if (operation() == QNetworkAccessManager::PutOperation) {
176         uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
177         uploadDevice->setParent(this);
178     }
179 }
180
181 void QNetworkAccessFtpBackend::closeDownstreamChannel()
182 {
183     state = Disconnecting;
184     if (operation() == QNetworkAccessManager::GetOperation)
185         ftp->abort();
186 }
187
188 void QNetworkAccessFtpBackend::downstreamReadyWrite()
189 {
190     if (state == Transferring && ftp && ftp->bytesAvailable())
191         ftpReadyRead();
192 }
193
194 void QNetworkAccessFtpBackend::ftpConnectionReady(QNetworkAccessCache::CacheableObject *o)
195 {
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()));
200
201     // is the login process done already?
202     if (ftp->state() == QFtp::LoggedIn)
203         ftpDone();
204
205     // no, defer the actual operation until after we've logged in
206 }
207
208 void QNetworkAccessFtpBackend::disconnectFromFtp()
209 {
210     state = Disconnecting;
211
212     if (ftp) {
213         disconnect(ftp, 0, this, 0);
214
215         QByteArray key = makeCacheKey(url());
216         QNetworkAccessManagerPrivate::getObjectCache(this)->releaseEntry(key);
217
218         ftp = 0;
219     }
220 }
221
222 void QNetworkAccessFtpBackend::ftpDone()
223 {
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
228             QUrl newUrl = url();
229             QString userInfo = newUrl.userInfo();
230             newUrl.setUserInfo(QString());
231             setUrl(newUrl);
232
233             QAuthenticator auth;
234             authenticationRequired(&auth);
235
236             if (!auth.isNull()) {
237                 // try again:
238                 newUrl.setUserName(auth.user());
239                 ftp->login(auth.user(), auth.password());
240                 return;
241             }
242
243             // Re insert the user info so that we can remove the cache entry.
244             newUrl.setUserInfo(userInfo);
245             setUrl(newUrl);
246
247             error(QNetworkReply::AuthenticationRequiredError,
248                   tr("Logging in to %1 failed: authentication required")
249                   .arg(url().host()));
250         } else {
251             // we did not connect
252             QNetworkReply::NetworkError code;
253             switch (ftp->error()) {
254             case QFtp::HostNotFound:
255                 code = QNetworkReply::HostNotFoundError;
256                 break;
257
258             case QFtp::ConnectionRefused:
259                 code = QNetworkReply::ConnectionRefusedError;
260                 break;
261
262             default:
263                 code = QNetworkReply::ProtocolFailure;
264                 break;
265             }
266
267             error(code, ftp->errorString());
268         }
269
270         // we're not connected, so remove the cache entry:
271         QByteArray key = makeCacheKey(url());
272         QNetworkAccessManagerPrivate::getObjectCache(this)->removeEntry(key);
273
274         disconnect(ftp, 0, this, 0);
275         ftp->dispose();
276         ftp = 0;
277
278         state = Disconnecting;
279         finished();
280         return;
281     }
282
283     // check for errors:
284     if (ftp->error() != QFtp::NoError) {
285         QString msg;
286         if (operation() == QNetworkAccessManager::GetOperation)
287             msg = tr("Error while downloading %1: %2");
288         else
289             msg = tr("Error while uploading %1: %2");
290         msg = msg.arg(url().toString(), ftp->errorString());
291
292         if (state == Statting)
293             // file probably doesn't exist
294             error(QNetworkReply::ContentNotFoundError,  msg);
295         else
296             error(QNetworkReply::ContentAccessDenied, msg);
297
298         disconnectFromFtp();
299         finished();
300     }
301
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
309         } else {
310             ftpDone();
311         }
312     } else if (state == CheckingFeatures) {
313         state = Statting;
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 "));
318             if (supportsSize) {
319                 ftp->rawCommand(QLatin1String("TYPE I"));
320                 sizeId = ftp->rawCommand(command.arg(QLatin1String("SIZE"))); // get size
321             }
322             if (supportsMdtm)
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
326         } else {
327             ftpDone();
328         }
329     } else if (state == Statting) {
330         // statted successfully, send the actual request
331         emit metaDataChanged();
332         state = Transferring;
333
334         QFtp::TransferType type = QFtp::Binary;
335         if (operation() == QNetworkAccessManager::GetOperation) {
336             setCachingEnabled(true);
337             ftp->get(url().path(), 0, type);
338         } else {
339             ftp->put(uploadDevice, url().path(), type);
340         }
341
342     } else if (state == Transferring) {
343         // upload or download finished
344         disconnectFromFtp();
345         finished();
346     }
347 }
348
349 void QNetworkAccessFtpBackend::ftpReadyRead()
350 {
351     QByteArray data = ftp->readAll();
352     QByteDataBuffer list;
353     list.append(data);
354     data.clear(); // important because of implicit sharing!
355     writeDownstreamData(list);
356 }
357
358 void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text)
359 {
360     //qDebug() << "FTP reply:" << code << text;
361     int id = ftp->currentId();
362
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
366         // in RFC 3659)
367         if (text.contains(QLatin1String("SIZE"), Qt::CaseSensitive))
368             supportsSize = true;
369         if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive))
370             supportsMdtm = true;
371     } else if (code == 213) {          // file status
372         if (id == sizeId) {
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);
379 #endif
380         }
381     }
382 }
383
384 QT_END_NAMESPACE
385
386 #endif // QT_NO_FTP