source: trunk/src/network/access/qnetworkaccessftpbackend.cpp@ 807

Last change on this file since 807 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 12.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
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
49QT_BEGIN_NAMESPACE
50
51enum {
52 DefaultFtpPort = 21
53};
54
55static 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
64QNetworkAccessBackend *
65QNetworkAccessFtpBackendFactory::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() == QLatin1String("ftp"))
81 return new QNetworkAccessFtpBackend;
82 return 0;
83}
84
85class QNetworkAccessCachedFtpConnection: public QFtp, public QNetworkAccessCache::CacheableObject
86{
87 // Q_OBJECT
88public:
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
102QNetworkAccessFtpBackend::QNetworkAccessFtpBackend()
103 : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1),
104 supportsSize(false), supportsMdtm(false), state(Idle)
105{
106}
107
108QNetworkAccessFtpBackend::~QNetworkAccessFtpBackend()
109{
110 disconnectFromFtp();
111}
112
113void QNetworkAccessFtpBackend::open()
114{
115#ifndef QT_NO_NETWORKPROXY
116 QNetworkProxy proxy;
117 foreach (const QNetworkProxy &p, proxyList()) {
118 // use the first FTP proxy
119 // or no proxy at all
120 if (p.type() == QNetworkProxy::FtpCachingProxy
121 || p.type() == QNetworkProxy::NoProxy) {
122 proxy = p;
123 break;
124 }
125 }
126
127 // did we find an FTP proxy or a NoProxy?
128 if (proxy.type() == QNetworkProxy::DefaultProxy) {
129 // unsuitable proxies
130 error(QNetworkReply::ProxyNotFoundError,
131 tr("No suitable proxy found"));
132 finished();
133 return;
134 }
135
136#endif
137
138 QUrl url = this->url();
139 if (url.path().isEmpty()) {
140 url.setPath(QLatin1String("/"));
141 setUrl(url);
142 }
143 if (url.path().endsWith(QLatin1Char('/'))) {
144 error(QNetworkReply::ContentOperationNotPermittedError,
145 tr("Cannot open %1: is a directory").arg(url.toString()));
146 finished();
147 return;
148 }
149 state = LoggingIn;
150
151 QNetworkAccessCache* objectCache = QNetworkAccessManagerPrivate::getObjectCache(this);
152 QByteArray cacheKey = makeCacheKey(url);
153 if (!objectCache->requestEntry(cacheKey, this,
154 SLOT(ftpConnectionReady(QNetworkAccessCache::CacheableObject*)))) {
155 ftp = new QNetworkAccessCachedFtpConnection;
156#ifndef QT_NO_NETWORKPROXY
157 if (proxy.type() == QNetworkProxy::FtpCachingProxy)
158 ftp->setProxy(proxy.hostName(), proxy.port());
159#endif
160 ftp->connectToHost(url.host(), url.port(DefaultFtpPort));
161 ftp->login(url.userName(), url.password());
162
163 objectCache->addEntry(cacheKey, ftp);
164 ftpConnectionReady(ftp);
165 }
166
167 // Put operation
168 if (operation() == QNetworkAccessManager::PutOperation) {
169 uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
170 uploadDevice->setParent(this);
171 }
172}
173
174void QNetworkAccessFtpBackend::closeDownstreamChannel()
175{
176 state = Disconnecting;
177 if (operation() == QNetworkAccessManager::GetOperation)
178#ifndef Q_OS_WINCE
179 abort();
180#else
181 exit(3);
182#endif
183}
184
185bool QNetworkAccessFtpBackend::waitForDownstreamReadyRead(int ms)
186{
187 if (!ftp)
188 return false;
189
190 if (ftp->bytesAvailable()) {
191 ftpReadyRead();
192 return true;
193 }
194
195 if (ms == 0)
196 return false;
197
198 qCritical("QNetworkAccess: FTP backend does not support waitForReadyRead()");
199 return false;
200}
201
202void QNetworkAccessFtpBackend::downstreamReadyWrite()
203{
204 if (state == Transferring && ftp && ftp->bytesAvailable())
205 ftpReadyRead();
206}
207
208void QNetworkAccessFtpBackend::ftpConnectionReady(QNetworkAccessCache::CacheableObject *o)
209{
210 ftp = static_cast<QNetworkAccessCachedFtpConnection *>(o);
211 connect(ftp, SIGNAL(done(bool)), SLOT(ftpDone()));
212 connect(ftp, SIGNAL(rawCommandReply(int,QString)), SLOT(ftpRawCommandReply(int,QString)));
213 connect(ftp, SIGNAL(readyRead()), SLOT(ftpReadyRead()));
214
215 // is the login process done already?
216 if (ftp->state() == QFtp::LoggedIn)
217 ftpDone();
218
219 // no, defer the actual operation until after we've logged in
220}
221
222void QNetworkAccessFtpBackend::disconnectFromFtp()
223{
224 state = Disconnecting;
225
226 if (ftp) {
227 disconnect(ftp, 0, this, 0);
228
229 QByteArray key = makeCacheKey(url());
230 QNetworkAccessManagerPrivate::getObjectCache(this)->releaseEntry(key);
231
232 ftp = 0;
233 }
234}
235
236void QNetworkAccessFtpBackend::ftpDone()
237{
238 // the last command we sent is done
239 if (state == LoggingIn && ftp->state() != QFtp::LoggedIn) {
240 if (ftp->state() == QFtp::Connected) {
241 // the login did not succeed
242 QUrl newUrl = url();
243 newUrl.setUserInfo(QString());
244 setUrl(newUrl);
245
246 QAuthenticator auth;
247 authenticationRequired(&auth);
248
249 if (!auth.isNull()) {
250 // try again:
251 newUrl.setUserName(auth.user());
252 ftp->login(auth.user(), auth.password());
253 return;
254 }
255
256 error(QNetworkReply::AuthenticationRequiredError,
257 tr("Logging in to %1 failed: authentication required")
258 .arg(url().host()));
259 } else {
260 // we did not connect
261 QNetworkReply::NetworkError code;
262 switch (ftp->error()) {
263 case QFtp::HostNotFound:
264 code = QNetworkReply::HostNotFoundError;
265 break;
266
267 case QFtp::ConnectionRefused:
268 code = QNetworkReply::ConnectionRefusedError;
269 break;
270
271 default:
272 code = QNetworkReply::ProtocolFailure;
273 break;
274 }
275
276 error(code, ftp->errorString());
277 }
278
279 // we're not connected, so remove the cache entry:
280 QByteArray key = makeCacheKey(url());
281 QNetworkAccessManagerPrivate::getObjectCache(this)->removeEntry(key);
282
283 disconnect(ftp, 0, this, 0);
284 ftp->dispose();
285 ftp = 0;
286
287 state = Disconnecting;
288 finished();
289 return;
290 }
291
292 // check for errors:
293 if (ftp->error() != QFtp::NoError) {
294 QString msg;
295 if (operation() == QNetworkAccessManager::GetOperation)
296 msg = tr("Error while downloading %1: %2");
297 else
298 msg = tr("Error while uploading %1: %2");
299 msg = msg.arg(url().toString(), ftp->errorString());
300
301 if (state == Statting)
302 // file probably doesn't exist
303 error(QNetworkReply::ContentNotFoundError, msg);
304 else
305 error(QNetworkReply::ContentAccessDenied, msg);
306
307 disconnectFromFtp();
308 finished();
309 }
310
311 if (state == LoggingIn) {
312 state = CheckingFeatures;
313 if (operation() == QNetworkAccessManager::GetOperation) {
314 // send help command to find out if server supports "SIZE" and "MDTM"
315 QString command = url().path();
316 command.prepend(QLatin1String("%1 "));
317 helpId = ftp->rawCommand(QLatin1String("HELP")); // get supported commands
318 } else {
319 ftpDone();
320 }
321 } else if (state == CheckingFeatures) {
322 state = Statting;
323 if (operation() == QNetworkAccessManager::GetOperation) {
324 // logged in successfully, send the stat requests (if supported)
325 QString command = url().path();
326 command.prepend(QLatin1String("%1 "));
327 if (supportsSize)
328 sizeId = ftp->rawCommand(command.arg(QLatin1String("SIZE"))); // get size
329 if (supportsMdtm)
330 mdtmId = ftp->rawCommand(command.arg(QLatin1String("MDTM"))); // get modified time
331 if (!supportsSize && !supportsMdtm)
332 ftpDone(); // no commands sent, move to the next state
333 } else {
334 ftpDone();
335 }
336 } else if (state == Statting) {
337 // statted successfully, send the actual request
338 emit metaDataChanged();
339 state = Transferring;
340
341 QFtp::TransferType type = QFtp::Binary;
342 if (operation() == QNetworkAccessManager::GetOperation) {
343 setCachingEnabled(true);
344 ftp->get(url().path(), 0, type);
345 } else {
346 ftp->put(uploadDevice, url().path(), type);
347 }
348
349 } else if (state == Transferring) {
350 // upload or download finished
351 disconnectFromFtp();
352 finished();
353 }
354}
355
356void QNetworkAccessFtpBackend::ftpReadyRead()
357{
358 QByteArray data = ftp->readAll();
359 QByteDataBuffer list;
360 list.append(data);
361 data.clear(); // important because of implicit sharing!
362 writeDownstreamData(list);
363}
364
365void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text)
366{
367 //qDebug() << "FTP reply:" << code << text;
368 int id = ftp->currentId();
369
370 if ((id == helpId) && ((code == 200) || (code == 214))) { // supported commands
371 // the "FEAT" ftp command would be nice here, but it is not part of the
372 // initial FTP RFC 959, neither ar "SIZE" nor "MDTM" (they are all specified
373 // in RFC 3659)
374 if (text.contains(QLatin1String("SIZE"), Qt::CaseSensitive))
375 supportsSize = true;
376 if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive))
377 supportsMdtm = true;
378 } else if (code == 213) { // file status
379 if (id == sizeId) {
380 // reply to the size command
381 setHeader(QNetworkRequest::ContentLengthHeader, text.toLongLong());
382#ifndef QT_NO_DATESTRING
383 } else if (id == mdtmId) {
384 QDateTime dt = QDateTime::fromString(text, QLatin1String("yyyyMMddHHmmss"));
385 setHeader(QNetworkRequest::LastModifiedHeader, dt);
386#endif
387 }
388 }
389}
390
391QT_END_NAMESPACE
392
393#endif // QT_NO_FTP
Note: See TracBrowser for help on using the repository browser.