| author | Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com> | 
| Fri, 16 Apr 2010 15:50:13 +0300 | |
| changeset 18 | 2f34d5167611 | 
| parent 0 | 1918ee327afb | 
| permissions | -rw-r--r-- | 
| 0 | 1 | /**************************************************************************** | 
| 2 | ** | |
| 18 
2f34d5167611
Revision: 201011
 Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com> parents: 
0diff
changeset | 3 | ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). | 
| 0 | 4 | ** All rights reserved. | 
| 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) | |
| 6 | ** | |
| 7 | ** This file is part of the QtNetwork module of the Qt Toolkit. | |
| 8 | ** | |
| 9 | ** $QT_BEGIN_LICENSE:LGPL$ | |
| 10 | ** No Commercial Usage | |
| 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 | ** 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 | ** If you have questions regarding the use of this file, please contact | |
| 29 | ** Nokia at qt-info@nokia.com. | |
| 30 | ** | |
| 31 | ** | |
| 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() == QLatin1String("ftp"))
 | |
| 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 | disconnectFromFtp(); | |
| 111 | } | |
| 112 | ||
| 113 | void 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 | ||
| 174 | void 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 | ||
| 185 | bool 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 | ||
| 202 | void QNetworkAccessFtpBackend::downstreamReadyWrite() | |
| 203 | {
 | |
| 204 | if (state == Transferring && ftp && ftp->bytesAvailable()) | |
| 205 | ftpReadyRead(); | |
| 206 | } | |
| 207 | ||
| 208 | void 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 | ||
| 222 | void 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 | ||
| 236 | void 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 | ||
| 356 | void 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 | ||
| 365 | void 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 | ||
| 391 | QT_END_NAMESPACE | |
| 392 | ||
| 393 | #endif // QT_NO_FTP |