src/network/access/qnetworkaccessftpbackend.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     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