util/src/network/access/qnetworkaccesshttpbackend.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 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 //#define QNETWORKACCESSHTTPBACKEND_DEBUG
       
    43 
       
    44 #include "qnetworkaccesshttpbackend_p.h"
       
    45 #include "qnetworkaccessmanager_p.h"
       
    46 #include "qnetworkaccesscache_p.h"
       
    47 #include "qabstractnetworkcache.h"
       
    48 #include "qnetworkrequest.h"
       
    49 #include "qnetworkreply.h"
       
    50 #include "qnetworkrequest_p.h"
       
    51 #include "qnetworkcookie_p.h"
       
    52 #include "QtCore/qdatetime.h"
       
    53 #include "QtNetwork/qsslconfiguration.h"
       
    54 
       
    55 #ifndef QT_NO_HTTP
       
    56 
       
    57 #include <string.h>             // for strchr
       
    58 
       
    59 QT_BEGIN_NAMESPACE
       
    60 
       
    61 enum {
       
    62     DefaultHttpPort = 80,
       
    63     DefaultHttpsPort = 443
       
    64 };
       
    65 
       
    66 class QNetworkProxy;
       
    67 
       
    68 static QByteArray makeCacheKey(QNetworkAccessHttpBackend *backend, QNetworkProxy *proxy)
       
    69 {
       
    70     QByteArray result;
       
    71     QUrl copy = backend->url();
       
    72     bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
       
    73     copy.setPort(copy.port(isEncrypted ? DefaultHttpsPort : DefaultHttpPort));
       
    74     result = copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath |
       
    75                             QUrl::RemoveQuery | QUrl::RemoveFragment);
       
    76 
       
    77 #ifndef QT_NO_NETWORKPROXY
       
    78     if (proxy->type() != QNetworkProxy::NoProxy) {
       
    79         QUrl key;
       
    80 
       
    81         switch (proxy->type()) {
       
    82         case QNetworkProxy::Socks5Proxy:
       
    83             key.setScheme(QLatin1String("proxy-socks5"));
       
    84             break;
       
    85 
       
    86         case QNetworkProxy::HttpProxy:
       
    87         case QNetworkProxy::HttpCachingProxy:
       
    88             key.setScheme(QLatin1String("proxy-http"));
       
    89             break;
       
    90 
       
    91         default:
       
    92             break;
       
    93         }
       
    94 
       
    95         if (!key.scheme().isEmpty()) {
       
    96             key.setUserName(proxy->user());
       
    97             key.setHost(proxy->hostName());
       
    98             key.setPort(proxy->port());
       
    99             key.setEncodedQuery(result);
       
   100             result = key.toEncoded();
       
   101         }
       
   102     }
       
   103 #endif
       
   104 
       
   105     return "http-connection:" + result;
       
   106 }
       
   107 
       
   108 static inline bool isSeparator(register char c)
       
   109 {
       
   110     static const char separators[] = "()<>@,;:\\\"/[]?={}";
       
   111     return isLWS(c) || strchr(separators, c) != 0;
       
   112 }
       
   113 
       
   114 // ### merge with nextField in cookiejar.cpp
       
   115 static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
       
   116 {
       
   117     // The HTTP header is of the form:
       
   118     // header          = #1(directives)
       
   119     // directives      = token | value-directive
       
   120     // value-directive = token "=" (token | quoted-string)
       
   121     QHash<QByteArray, QByteArray> result;
       
   122 
       
   123     int pos = 0;
       
   124     while (true) {
       
   125         // skip spaces
       
   126         pos = nextNonWhitespace(header, pos);
       
   127         if (pos == header.length())
       
   128             return result;      // end of parsing
       
   129 
       
   130         // pos points to a non-whitespace
       
   131         int comma = header.indexOf(',', pos);
       
   132         int equal = header.indexOf('=', pos);
       
   133         if (comma == pos || equal == pos)
       
   134             // huh? Broken header.
       
   135             return result;
       
   136 
       
   137         // The key name is delimited by either a comma, an equal sign or the end
       
   138         // of the header, whichever comes first
       
   139         int end = comma;
       
   140         if (end == -1)
       
   141             end = header.length();
       
   142         if (equal != -1 && end > equal)
       
   143             end = equal;        // equal sign comes before comma/end
       
   144         QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
       
   145         pos = end + 1;
       
   146 
       
   147         if (uint(equal) < uint(comma)) {
       
   148             // case: token "=" (token | quoted-string)
       
   149             // skip spaces
       
   150             pos = nextNonWhitespace(header, pos);
       
   151             if (pos == header.length())
       
   152                 // huh? Broken header
       
   153                 return result;
       
   154 
       
   155             QByteArray value;
       
   156             value.reserve(header.length() - pos);
       
   157             if (header.at(pos) == '"') {
       
   158                 // case: quoted-string
       
   159                 // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
       
   160                 // qdtext         = <any TEXT except <">>
       
   161                 // quoted-pair    = "\" CHAR
       
   162                 ++pos;
       
   163                 while (pos < header.length()) {
       
   164                     register char c = header.at(pos);
       
   165                     if (c == '"') {
       
   166                         // end of quoted text
       
   167                         break;
       
   168                     } else if (c == '\\') {
       
   169                         ++pos;
       
   170                         if (pos >= header.length())
       
   171                             // broken header
       
   172                             return result;
       
   173                         c = header.at(pos);
       
   174                     }
       
   175 
       
   176                     value += c;
       
   177                     ++pos;
       
   178                 }
       
   179             } else {
       
   180                 // case: token
       
   181                 while (pos < header.length()) {
       
   182                     register char c = header.at(pos);
       
   183                     if (isSeparator(c))
       
   184                         break;
       
   185                     value += c;
       
   186                     ++pos;
       
   187                 }
       
   188             }
       
   189 
       
   190             result.insert(key, value);
       
   191 
       
   192             // find the comma now:
       
   193             comma = header.indexOf(',', pos);
       
   194             if (comma == -1)
       
   195                 return result;  // end of parsing
       
   196             pos = comma + 1;
       
   197         } else {
       
   198             // case: token
       
   199             // key is already set
       
   200             result.insert(key, QByteArray());
       
   201         }
       
   202     }
       
   203 }
       
   204 
       
   205 QNetworkAccessBackend *
       
   206 QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
       
   207                                          const QNetworkRequest &request) const
       
   208 {
       
   209     // check the operation
       
   210     switch (op) {
       
   211     case QNetworkAccessManager::GetOperation:
       
   212     case QNetworkAccessManager::PostOperation:
       
   213     case QNetworkAccessManager::HeadOperation:
       
   214     case QNetworkAccessManager::PutOperation:
       
   215     case QNetworkAccessManager::DeleteOperation:
       
   216         break;
       
   217 
       
   218     default:
       
   219         // no, we can't handle this request
       
   220         return 0;
       
   221     }
       
   222 
       
   223     QUrl url = request.url();
       
   224     QString scheme = url.scheme().toLower();
       
   225     if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
       
   226         return new QNetworkAccessHttpBackend;
       
   227 
       
   228     return 0;
       
   229 }
       
   230 
       
   231 static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
       
   232 {
       
   233     QNetworkReply::NetworkError code;
       
   234     // we've got an error
       
   235     switch (httpStatusCode) {
       
   236     case 401:               // Authorization required
       
   237         code = QNetworkReply::AuthenticationRequiredError;
       
   238         break;
       
   239 
       
   240     case 403:               // Access denied
       
   241         code = QNetworkReply::ContentOperationNotPermittedError;
       
   242         break;
       
   243 
       
   244     case 404:               // Not Found
       
   245         code = QNetworkReply::ContentNotFoundError;
       
   246         break;
       
   247 
       
   248     case 405:               // Method Not Allowed
       
   249         code = QNetworkReply::ContentOperationNotPermittedError;
       
   250         break;
       
   251 
       
   252     case 407:
       
   253         code = QNetworkReply::ProxyAuthenticationRequiredError;
       
   254         break;
       
   255 
       
   256     default:
       
   257         if (httpStatusCode > 500) {
       
   258             // some kind of server error
       
   259             code = QNetworkReply::ProtocolUnknownError;
       
   260         } else if (httpStatusCode >= 400) {
       
   261             // content error we did not handle above
       
   262             code = QNetworkReply::UnknownContentError;
       
   263         } else {
       
   264             qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
       
   265                      httpStatusCode, qPrintable(url.toString()));
       
   266             code = QNetworkReply::ProtocolFailure;
       
   267         }
       
   268     }
       
   269 
       
   270     return code;
       
   271 }
       
   272 
       
   273 class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
       
   274                                       public QNetworkAccessCache::CacheableObject
       
   275 {
       
   276     // Q_OBJECT
       
   277 public:
       
   278     QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
       
   279         : QHttpNetworkConnection(hostName, port, encrypt)
       
   280     {
       
   281         setExpires(true);
       
   282         setShareable(true);
       
   283     }
       
   284 
       
   285     virtual void dispose()
       
   286     {
       
   287 #if 0  // sample code; do this right with the API
       
   288         Q_ASSERT(!isWorking());
       
   289 #endif
       
   290         delete this;
       
   291     }
       
   292 };
       
   293 
       
   294 QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
       
   295     : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0)
       
   296 #ifndef QT_NO_OPENSSL
       
   297     , pendingSslConfiguration(0), pendingIgnoreAllSslErrors(false)
       
   298 #endif
       
   299 {
       
   300 }
       
   301 
       
   302 QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
       
   303 {
       
   304     if (http)
       
   305         disconnectFromHttp();
       
   306 #ifndef QT_NO_OPENSSL
       
   307     delete pendingSslConfiguration;
       
   308 #endif
       
   309 }
       
   310 
       
   311 void QNetworkAccessHttpBackend::disconnectFromHttp()
       
   312 {
       
   313     if (http) {
       
   314         // This is abut disconnecting signals, not about disconnecting TCP connections
       
   315         disconnect(http, 0, this, 0);
       
   316 
       
   317         // Get the object cache that stores our QHttpNetworkConnection objects
       
   318         QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
       
   319         cache->releaseEntry(cacheKey);
       
   320     }
       
   321 
       
   322     // This is abut disconnecting signals, not about disconnecting TCP connections
       
   323     if (httpReply)
       
   324         disconnect(httpReply, 0, this, 0);
       
   325 
       
   326     http = 0;
       
   327     httpReply = 0;
       
   328     cacheKey.clear();
       
   329 }
       
   330 
       
   331 void QNetworkAccessHttpBackend::finished()
       
   332 {
       
   333     if (http)
       
   334         disconnectFromHttp();
       
   335     // call parent
       
   336     QNetworkAccessBackend::finished();
       
   337 }
       
   338 
       
   339 void QNetworkAccessHttpBackend::setupConnection()
       
   340 {
       
   341 #ifndef QT_NO_NETWORKPROXY
       
   342     connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
       
   343             SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
       
   344 #endif
       
   345     connect(http, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
       
   346             SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
       
   347     connect(http, SIGNAL(error(QNetworkReply::NetworkError,QString)),
       
   348             SLOT(httpError(QNetworkReply::NetworkError,QString)));
       
   349 #ifndef QT_NO_OPENSSL
       
   350     connect(http, SIGNAL(sslErrors(QList<QSslError>)),
       
   351             SLOT(sslErrors(QList<QSslError>)));
       
   352 #endif
       
   353 }
       
   354 
       
   355 /*
       
   356     For a given httpRequest
       
   357     1) If AlwaysNetwork, return
       
   358     2) If we have a cache entry for this url populate headers so the server can return 304
       
   359     3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
       
   360  */
       
   361 void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache)
       
   362 {
       
   363     QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
       
   364         (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
       
   365     if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
       
   366         // forced reload from the network
       
   367         // tell any caching proxy servers to reload too
       
   368         httpRequest.setHeaderField("Cache-Control", "no-cache");
       
   369         httpRequest.setHeaderField("Pragma", "no-cache");
       
   370         return;
       
   371     }
       
   372 
       
   373     QAbstractNetworkCache *nc = networkCache();
       
   374     if (!nc)
       
   375         return;                 // no local cache
       
   376 
       
   377     QNetworkCacheMetaData metaData = nc->metaData(url());
       
   378     if (!metaData.isValid())
       
   379         return;                 // not in cache
       
   380 
       
   381     if (!metaData.saveToDisk())
       
   382         return;
       
   383 
       
   384     QNetworkHeadersPrivate cacheHeaders;
       
   385     QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
       
   386     cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
       
   387 
       
   388     it = cacheHeaders.findRawHeader("etag");
       
   389     if (it != cacheHeaders.rawHeaders.constEnd())
       
   390         httpRequest.setHeaderField("If-None-Match", it->second);
       
   391 
       
   392     QDateTime lastModified = metaData.lastModified();
       
   393     if (lastModified.isValid())
       
   394         httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
       
   395 
       
   396     if (CacheLoadControlAttribute == QNetworkRequest::PreferNetwork) {
       
   397         it = cacheHeaders.findRawHeader("Cache-Control");
       
   398         if (it != cacheHeaders.rawHeaders.constEnd()) {
       
   399             QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
       
   400             if (cacheControl.contains("must-revalidate"))
       
   401                 return;
       
   402         }
       
   403     }
       
   404 
       
   405     QDateTime currentDateTime = QDateTime::currentDateTime();
       
   406     QDateTime expirationDate = metaData.expirationDate();
       
   407 
       
   408 #if 0
       
   409     /*
       
   410      * age_value
       
   411      *      is the value of Age: header received by the cache with
       
   412      *              this response.
       
   413      * date_value
       
   414      *      is the value of the origin server's Date: header
       
   415      * request_time
       
   416      *      is the (local) time when the cache made the request
       
   417      *              that resulted in this cached response
       
   418      * response_time
       
   419      *      is the (local) time when the cache received the
       
   420      *              response
       
   421      * now
       
   422      *      is the current (local) time
       
   423      */
       
   424     int age_value = 0;
       
   425     it = cacheHeaders.findRawHeader("age");
       
   426     if (it != cacheHeaders.rawHeaders.constEnd())
       
   427         age_value = it->second.toInt();
       
   428 
       
   429     QDateTime dateHeader;
       
   430     int date_value = 0;
       
   431     it = cacheHeaders.findRawHeader("date");
       
   432     if (it != cacheHeaders.rawHeaders.constEnd()) {
       
   433         dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
       
   434         date_value = dateHeader.toTime_t();
       
   435     }
       
   436 
       
   437     int now = currentDateTime.toUTC().toTime_t();
       
   438     int request_time = now;
       
   439     int response_time = now;
       
   440 
       
   441     // Algorithm from RFC 2616 section 13.2.3
       
   442     int apparent_age = qMax(0, response_time - date_value);
       
   443     int corrected_received_age = qMax(apparent_age, age_value);
       
   444     int response_delay = response_time - request_time;
       
   445     int corrected_initial_age = corrected_received_age + response_delay;
       
   446     int resident_time = now - response_time;
       
   447     int current_age   = corrected_initial_age + resident_time;
       
   448 
       
   449     // RFC 2616 13.2.4 Expiration Calculations
       
   450     if (!expirationDate.isValid()) {
       
   451         if (lastModified.isValid()) {
       
   452             int diff = currentDateTime.secsTo(lastModified);
       
   453             expirationDate = lastModified;
       
   454             expirationDate.addSecs(diff / 10);
       
   455             if (httpRequest.headerField("Warning").isEmpty()) {
       
   456                 QDateTime dt;
       
   457                 dt.setTime_t(current_age);
       
   458                 if (dt.daysTo(currentDateTime) > 1)
       
   459                     httpRequest.setHeaderField("Warning", "113");
       
   460             }
       
   461         }
       
   462     }
       
   463 
       
   464     // the cache-saving code below sets the expirationDate with date+max_age
       
   465     // if "max-age" is present, or to Expires otherwise
       
   466     int freshness_lifetime = dateHeader.secsTo(expirationDate);
       
   467     bool response_is_fresh = (freshness_lifetime > current_age);
       
   468 #else
       
   469     bool response_is_fresh = currentDateTime.secsTo(expirationDate) >= 0;
       
   470 #endif
       
   471 
       
   472     if (!response_is_fresh)
       
   473         return;
       
   474 
       
   475     loadedFromCache = true;
       
   476 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
   477     qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
       
   478 #endif
       
   479     if (!sendCacheContents(metaData))
       
   480         loadedFromCache = false;
       
   481 }
       
   482 
       
   483 void QNetworkAccessHttpBackend::postRequest()
       
   484 {
       
   485     bool loadedFromCache = false;
       
   486     QHttpNetworkRequest httpRequest;
       
   487     switch (operation()) {
       
   488     case QNetworkAccessManager::GetOperation:
       
   489         httpRequest.setOperation(QHttpNetworkRequest::Get);
       
   490         validateCache(httpRequest, loadedFromCache);
       
   491         break;
       
   492 
       
   493     case QNetworkAccessManager::HeadOperation:
       
   494         httpRequest.setOperation(QHttpNetworkRequest::Head);
       
   495         validateCache(httpRequest, loadedFromCache);
       
   496         break;
       
   497 
       
   498     case QNetworkAccessManager::PostOperation:
       
   499         invalidateCache();
       
   500         httpRequest.setOperation(QHttpNetworkRequest::Post);
       
   501         httpRequest.setUploadByteDevice(createUploadByteDevice());
       
   502         break;
       
   503 
       
   504     case QNetworkAccessManager::PutOperation:
       
   505         invalidateCache();
       
   506         httpRequest.setOperation(QHttpNetworkRequest::Put);
       
   507         httpRequest.setUploadByteDevice(createUploadByteDevice());
       
   508         break;
       
   509 
       
   510     case QNetworkAccessManager::DeleteOperation:
       
   511         invalidateCache();
       
   512         httpRequest.setOperation(QHttpNetworkRequest::Delete);
       
   513         break;
       
   514 
       
   515     default:
       
   516         break;                  // can't happen
       
   517     }
       
   518 
       
   519     httpRequest.setUrl(url());
       
   520 
       
   521     QList<QByteArray> headers = request().rawHeaderList();
       
   522     foreach (const QByteArray &header, headers)
       
   523         httpRequest.setHeaderField(header, request().rawHeader(header));
       
   524 
       
   525     if (loadedFromCache) {
       
   526         // commented this out since it will be called later anyway
       
   527         // by copyFinished()
       
   528         //QNetworkAccessBackend::finished();
       
   529         return;    // no need to send the request! :)
       
   530     }
       
   531 
       
   532     if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
       
   533         httpRequest.setPipeliningAllowed(true);
       
   534 
       
   535     httpReply = http->sendRequest(httpRequest);
       
   536     httpReply->setParent(this);
       
   537 #ifndef QT_NO_OPENSSL
       
   538     if (pendingSslConfiguration)
       
   539         httpReply->setSslConfiguration(*pendingSslConfiguration);
       
   540     if (pendingIgnoreAllSslErrors)
       
   541         httpReply->ignoreSslErrors();
       
   542     httpReply->ignoreSslErrors(pendingIgnoreSslErrorsList);
       
   543 #endif
       
   544 
       
   545     connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
       
   546     connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
       
   547     connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
       
   548             SLOT(httpError(QNetworkReply::NetworkError,QString)));
       
   549     connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
       
   550 }
       
   551 
       
   552 void QNetworkAccessHttpBackend::invalidateCache()
       
   553 {
       
   554     QAbstractNetworkCache *nc = networkCache();
       
   555     if (nc)
       
   556         nc->remove(url());
       
   557 }
       
   558 
       
   559 void QNetworkAccessHttpBackend::open()
       
   560 {
       
   561     QUrl url = request().url();
       
   562     bool encrypt = url.scheme().toLower() == QLatin1String("https");
       
   563     setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
       
   564 
       
   565     // set the port number in the reply if it wasn't set
       
   566     url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
       
   567 
       
   568     QNetworkProxy *theProxy = 0;
       
   569 #ifndef QT_NO_NETWORKPROXY
       
   570     QNetworkProxy transparentProxy, cacheProxy;
       
   571 
       
   572     foreach (const QNetworkProxy &p, proxyList()) {
       
   573         // use the first proxy that works
       
   574         // for non-encrypted connections, any transparent or HTTP proxy
       
   575         // for encrypted, only transparent proxies
       
   576         if (!encrypt
       
   577             && (p.capabilities() & QNetworkProxy::CachingCapability)
       
   578             && (p.type() == QNetworkProxy::HttpProxy ||
       
   579                 p.type() == QNetworkProxy::HttpCachingProxy)) {
       
   580             cacheProxy = p;
       
   581             transparentProxy = QNetworkProxy::NoProxy;
       
   582             theProxy = &cacheProxy;
       
   583             break;
       
   584         }
       
   585         if (p.isTransparentProxy()) {
       
   586             transparentProxy = p;
       
   587             cacheProxy = QNetworkProxy::NoProxy;
       
   588             theProxy = &transparentProxy;
       
   589             break;
       
   590         }
       
   591     }
       
   592 
       
   593     // check if at least one of the proxies
       
   594     if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
       
   595         cacheProxy.type() == QNetworkProxy::DefaultProxy) {
       
   596         // unsuitable proxies
       
   597         QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
       
   598                                   Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
       
   599                                   Q_ARG(QString, tr("No suitable proxy found")));
       
   600         QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
       
   601         return;
       
   602     }
       
   603 #endif
       
   604 
       
   605     // check if we have an open connection to this host
       
   606     cacheKey = makeCacheKey(this, theProxy);
       
   607     QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
       
   608     // the http object is actually a QHttpNetworkConnection
       
   609     http = static_cast<QNetworkAccessCachedHttpConnection *>(cache->requestEntryNow(cacheKey));
       
   610     if (http == 0) {
       
   611         // no entry in cache; create an object
       
   612         // the http object is actually a QHttpNetworkConnection
       
   613         http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt);
       
   614 
       
   615 #ifndef QT_NO_NETWORKPROXY
       
   616         http->setTransparentProxy(transparentProxy);
       
   617         http->setCacheProxy(cacheProxy);
       
   618 #endif
       
   619 
       
   620         // cache the QHttpNetworkConnection corresponding to this cache key
       
   621         cache->addEntry(cacheKey, http);
       
   622     }
       
   623 
       
   624     setupConnection();
       
   625     postRequest();
       
   626 }
       
   627 
       
   628 void QNetworkAccessHttpBackend::closeDownstreamChannel()
       
   629 {
       
   630     // this indicates that the user closed the stream while the reply isn't finished yet
       
   631 }
       
   632 
       
   633 bool QNetworkAccessHttpBackend::waitForDownstreamReadyRead(int msecs)
       
   634 {
       
   635     Q_ASSERT(http);
       
   636 
       
   637     if (httpReply->bytesAvailable()) {
       
   638         readFromHttp();
       
   639         return true;
       
   640     }
       
   641 
       
   642     if (msecs == 0) {
       
   643         // no bytes available in the socket and no waiting
       
   644         return false;
       
   645     }
       
   646 
       
   647     // ### FIXME
       
   648     qCritical("QNetworkAccess: HTTP backend does not support waitForReadyRead()");
       
   649     return false;
       
   650 }
       
   651 
       
   652 
       
   653 void QNetworkAccessHttpBackend::downstreamReadyWrite()
       
   654 {
       
   655     readFromHttp();
       
   656     if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
       
   657         replyFinished();
       
   658 }
       
   659 
       
   660 void QNetworkAccessHttpBackend::setDownstreamLimited(bool b)
       
   661 {
       
   662     if (httpReply)
       
   663         httpReply->setDownstreamLimited(b);
       
   664 }
       
   665 
       
   666 void QNetworkAccessHttpBackend::replyReadyRead()
       
   667 {
       
   668     readFromHttp();
       
   669 }
       
   670 
       
   671 void QNetworkAccessHttpBackend::readFromHttp()
       
   672 {
       
   673     if (!httpReply)
       
   674         return;
       
   675 
       
   676     // We read possibly more than nextDownstreamBlockSize(), but
       
   677     // this is not a critical thing since it is already in the
       
   678     // memory anyway
       
   679 
       
   680     QByteDataBuffer list;
       
   681 
       
   682     while (httpReply->bytesAvailable() != 0 && nextDownstreamBlockSize() != 0 && nextDownstreamBlockSize() > list.byteAmount()) {
       
   683         QByteArray data = httpReply->readAny();
       
   684         list.append(data);
       
   685     }
       
   686 
       
   687     if (!list.isEmpty())
       
   688       writeDownstreamData(list);
       
   689 }
       
   690 
       
   691 void QNetworkAccessHttpBackend::replyFinished()
       
   692 {
       
   693     if (httpReply->bytesAvailable())
       
   694         // we haven't read everything yet. Wait some more.
       
   695         return;
       
   696 
       
   697     int statusCode = httpReply->statusCode();
       
   698     if (statusCode >= 400) {
       
   699         // it's an error reply
       
   700         QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
       
   701                                                       "Error downloading %1 - server replied: %2"));
       
   702         msg = msg.arg(url().toString(), httpReply->reasonPhrase());
       
   703         error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg);
       
   704     }
       
   705 
       
   706 #ifndef QT_NO_OPENSSL
       
   707     // store the SSL configuration now
       
   708     // once we call finished(), we won't have access to httpReply anymore
       
   709     QSslConfiguration sslConfig = httpReply->sslConfiguration();
       
   710     if (pendingSslConfiguration) {
       
   711         *pendingSslConfiguration = sslConfig;
       
   712     } else if (!sslConfig.isNull()) {
       
   713         QT_TRY {
       
   714             pendingSslConfiguration = new QSslConfiguration(sslConfig);
       
   715         } QT_CATCH(...) {
       
   716             qWarning("QNetworkAccess: could not allocate a QSslConfiguration object for a SSL connection.");
       
   717         }
       
   718     }
       
   719 #endif
       
   720 
       
   721     finished();
       
   722 }
       
   723 
       
   724 void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode)
       
   725 {
       
   726     switch (statusCode) {
       
   727     case 301:                   // Moved Permanently
       
   728     case 302:                   // Found
       
   729     case 303:                   // See Other
       
   730     case 307:                   // Temporary Redirect
       
   731         // What do we do about the caching of the HTML note?
       
   732         // The response to a 303 MUST NOT be cached, while the response to
       
   733         // all of the others is cacheable if the headers indicate it to be
       
   734         QByteArray header = rawHeader("location");
       
   735         QUrl url = QUrl::fromEncoded(header);
       
   736         if (!url.isValid())
       
   737             url = QUrl(QLatin1String(header));
       
   738         redirectionRequested(url);
       
   739     }
       
   740 }
       
   741 
       
   742 void QNetworkAccessHttpBackend::replyHeaderChanged()
       
   743 {
       
   744     setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, httpReply->isPipeliningUsed());
       
   745 
       
   746     // reconstruct the HTTP header
       
   747     QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header();
       
   748     QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
       
   749                                                         end = headerMap.constEnd();
       
   750     QByteArray header;
       
   751 
       
   752     for (; it != end; ++it) {
       
   753         QByteArray value = rawHeader(it->first);
       
   754         if (!value.isEmpty()) {
       
   755             if (qstricmp(it->first.constData(), "set-cookie") == 0)
       
   756                 value += '\n';
       
   757             else
       
   758                 value += ", ";
       
   759         }
       
   760         value += it->second;
       
   761         setRawHeader(it->first, value);
       
   762     }
       
   763 
       
   764     setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode());
       
   765     setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
       
   766 
       
   767     // is it a redirection?
       
   768     const int statusCode = httpReply->statusCode();
       
   769     checkForRedirect(statusCode);
       
   770 
       
   771     if (statusCode >= 500 && statusCode < 600) {
       
   772         QAbstractNetworkCache *nc = networkCache();
       
   773         if (nc) {
       
   774             QNetworkCacheMetaData metaData = nc->metaData(url());
       
   775             QNetworkHeadersPrivate cacheHeaders;
       
   776             cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
       
   777             QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
       
   778             it = cacheHeaders.findRawHeader("Cache-Control");
       
   779             bool mustReValidate = false;
       
   780             if (it != cacheHeaders.rawHeaders.constEnd()) {
       
   781                 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
       
   782                 if (cacheControl.contains("must-revalidate"))
       
   783                     mustReValidate = true;
       
   784             }
       
   785             if (!mustReValidate && sendCacheContents(metaData))
       
   786                 return;
       
   787         }
       
   788     }
       
   789 
       
   790     if (statusCode == 304) {
       
   791 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
   792         qDebug() << "Received a 304 from" << url();
       
   793 #endif
       
   794         QAbstractNetworkCache *nc = networkCache();
       
   795         if (nc) {
       
   796             QNetworkCacheMetaData oldMetaData = nc->metaData(url());
       
   797             QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
       
   798             if (oldMetaData != metaData)
       
   799                 nc->updateMetaData(metaData);
       
   800             if (sendCacheContents(metaData))
       
   801                 return;
       
   802         }
       
   803     }
       
   804 
       
   805 
       
   806     if (statusCode != 304 && statusCode != 303) {
       
   807         if (!isCachingEnabled())
       
   808             setCachingEnabled(true);
       
   809     }
       
   810     metaDataChanged();
       
   811 }
       
   812 
       
   813 void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
       
   814                                                            QAuthenticator *auth)
       
   815 {
       
   816     authenticationRequired(auth);
       
   817 }
       
   818 
       
   819 void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode,
       
   820                                           const QString &errorString)
       
   821 {
       
   822 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
   823     qDebug() << "http error!" << errorCode << errorString;
       
   824 #endif
       
   825 #if 0
       
   826     static const QNetworkReply::NetworkError conversionTable[] = {
       
   827         QNetworkReply::ConnectionRefusedError,
       
   828         QNetworkReply::RemoteHostClosedError,
       
   829         QNetworkReply::HostNotFoundError,
       
   830         QNetworkReply::UnknownNetworkError, // SocketAccessError
       
   831         QNetworkReply::UnknownNetworkError, // SocketResourceError
       
   832         QNetworkReply::TimeoutError,        // SocketTimeoutError
       
   833         QNetworkReply::UnknownNetworkError, // DatagramTooLargeError
       
   834         QNetworkReply::UnknownNetworkError, // NetworkError
       
   835         QNetworkReply::UnknownNetworkError, // AddressInUseError
       
   836         QNetworkReply::UnknownNetworkError, // SocketAddressNotAvailableError
       
   837         QNetworkReply::UnknownNetworkError, // UnsupportedSocketOperationError
       
   838         QNetworkReply::UnknownNetworkError, // UnfinishedSocketOperationError
       
   839         QNetworkReply::ProxyAuthenticationRequiredError
       
   840     };
       
   841     QNetworkReply::NetworkError code;
       
   842     if (int(errorCode) >= 0 &&
       
   843         uint(errorCode) < (sizeof conversionTable / sizeof conversionTable[0]))
       
   844         code = conversionTable[errorCode];
       
   845     else
       
   846         code = QNetworkReply::UnknownNetworkError;
       
   847 #endif
       
   848     error(errorCode, errorString);
       
   849     finished();
       
   850 }
       
   851 
       
   852 /*
       
   853     A simple web page that can be used to test us: http://www.procata.com/cachetest/
       
   854  */
       
   855 bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData)
       
   856 {
       
   857     setCachingEnabled(false);
       
   858     if (!metaData.isValid())
       
   859         return false;
       
   860 
       
   861     QAbstractNetworkCache *nc = networkCache();
       
   862     Q_ASSERT(nc);
       
   863     QIODevice *contents = nc->data(url());
       
   864     if (!contents) {
       
   865 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
   866         qDebug() << "Can not send cache, the contents are 0" << url();
       
   867 #endif
       
   868         return false;
       
   869     }
       
   870     contents->setParent(this);
       
   871 
       
   872     QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
       
   873     int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
       
   874     if (status < 100)
       
   875         status = 200;           // fake it
       
   876 
       
   877     setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
       
   878     setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
       
   879     setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
       
   880 
       
   881     QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
       
   882     QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
       
   883                                                        end = rawHeaders.constEnd();
       
   884     for ( ; it != end; ++it)
       
   885         setRawHeader(it->first, it->second);
       
   886 
       
   887     checkForRedirect(status);
       
   888 
       
   889     emit metaDataChanged();
       
   890 
       
   891     // invoke this asynchronously, else Arora/QtDemoBrowser don't like cached downloads
       
   892     // see task 250221 / 251801
       
   893     qRegisterMetaType<QIODevice*>("QIODevice*");
       
   894     QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents));
       
   895 
       
   896 
       
   897 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
   898     qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
       
   899 #endif
       
   900     if (httpReply)
       
   901         disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished()));
       
   902     return true;
       
   903 }
       
   904 
       
   905 void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
       
   906 {
       
   907     delete dev;
       
   908     finished();
       
   909 }
       
   910 
       
   911 #ifndef QT_NO_OPENSSL
       
   912 void QNetworkAccessHttpBackend::ignoreSslErrors()
       
   913 {
       
   914     if (httpReply)
       
   915         httpReply->ignoreSslErrors();
       
   916     else
       
   917         pendingIgnoreAllSslErrors = true;
       
   918 }
       
   919 
       
   920 void QNetworkAccessHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
       
   921 {
       
   922     if (httpReply) {
       
   923         httpReply->ignoreSslErrors(errors);
       
   924     } else {
       
   925         // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
       
   926         // is called before QNetworkAccessManager::get() (or post(), etc.)
       
   927         pendingIgnoreSslErrorsList = errors;
       
   928     }
       
   929 }
       
   930 
       
   931 void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
       
   932 {
       
   933     if (httpReply)
       
   934         config = httpReply->sslConfiguration();
       
   935     else if (pendingSslConfiguration)
       
   936         config = *pendingSslConfiguration;
       
   937 }
       
   938 
       
   939 void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig)
       
   940 {
       
   941     if (httpReply)
       
   942         httpReply->setSslConfiguration(newconfig);
       
   943     else if (pendingSslConfiguration)
       
   944         *pendingSslConfiguration = newconfig;
       
   945     else
       
   946         pendingSslConfiguration = new QSslConfiguration(newconfig);
       
   947 }
       
   948 #endif
       
   949 
       
   950 QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
       
   951 {
       
   952     QNetworkCacheMetaData metaData = oldMetaData;
       
   953 
       
   954     QNetworkHeadersPrivate cacheHeaders;
       
   955     cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
       
   956     QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
       
   957 
       
   958     QList<QByteArray> newHeaders = rawHeaderList();
       
   959     foreach (QByteArray header, newHeaders) {
       
   960         QByteArray originalHeader = header;
       
   961         header = header.toLower();
       
   962         bool hop_by_hop =
       
   963             (header == "connection"
       
   964              || header == "keep-alive"
       
   965              || header == "proxy-authenticate"
       
   966              || header == "proxy-authorization"
       
   967              || header == "te"
       
   968              || header == "trailers"
       
   969              || header == "transfer-encoding"
       
   970              || header ==  "upgrade");
       
   971         if (hop_by_hop)
       
   972             continue;
       
   973 
       
   974         // for 4.6.0, we were planning to not store the date header in the
       
   975         // cached resource; through that we planned to reduce the number
       
   976         // of writes to disk when using a QNetworkDiskCache (i.e. don't
       
   977         // write to disk when only the date changes).
       
   978         // However, without the date we cannot calculate the age of the page
       
   979         // anymore.
       
   980         //if (header == "date")
       
   981             //continue;
       
   982 
       
   983         // Don't store Warning 1xx headers
       
   984         if (header == "warning") {
       
   985             QByteArray v = rawHeader(header);
       
   986             if (v.length() == 3
       
   987                 && v[0] == '1'
       
   988                 && v[1] >= '0' && v[1] <= '9'
       
   989                 && v[2] >= '0' && v[2] <= '9')
       
   990                 continue;
       
   991         }
       
   992 
       
   993         it = cacheHeaders.findRawHeader(header);
       
   994         if (it != cacheHeaders.rawHeaders.constEnd()) {
       
   995             // Match the behavior of Firefox and assume Cache-Control: "no-transform"
       
   996             if (header == "content-encoding"
       
   997                 || header == "content-range"
       
   998                 || header == "content-type")
       
   999                 continue;
       
  1000 
       
  1001             // For MS servers that send "Content-Length: 0" on 304 responses
       
  1002             // ignore this too
       
  1003             if (header == "content-length")
       
  1004                 continue;
       
  1005         }
       
  1006 
       
  1007 #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
       
  1008         QByteArray n = rawHeader(header);
       
  1009         QByteArray o;
       
  1010         if (it != cacheHeaders.rawHeaders.constEnd())
       
  1011             o = (*it).second;
       
  1012         if (n != o && header != "date") {
       
  1013             qDebug() << "replacing" << header;
       
  1014             qDebug() << "new" << n;
       
  1015             qDebug() << "old" << o;
       
  1016         }
       
  1017 #endif
       
  1018         cacheHeaders.setRawHeader(originalHeader, rawHeader(header));
       
  1019     }
       
  1020     metaData.setRawHeaders(cacheHeaders.rawHeaders);
       
  1021 
       
  1022     bool checkExpired = true;
       
  1023 
       
  1024     QHash<QByteArray, QByteArray> cacheControl;
       
  1025     it = cacheHeaders.findRawHeader("Cache-Control");
       
  1026     if (it != cacheHeaders.rawHeaders.constEnd()) {
       
  1027         cacheControl = parseHttpOptionHeader(it->second);
       
  1028         QByteArray maxAge = cacheControl.value("max-age");
       
  1029         if (!maxAge.isEmpty()) {
       
  1030             checkExpired = false;
       
  1031             QDateTime dt = QDateTime::currentDateTime();
       
  1032             dt = dt.addSecs(maxAge.toInt());
       
  1033             metaData.setExpirationDate(dt);
       
  1034         }
       
  1035     }
       
  1036     if (checkExpired) {
       
  1037         it = cacheHeaders.findRawHeader("expires");
       
  1038         if (it != cacheHeaders.rawHeaders.constEnd()) {
       
  1039             QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
       
  1040             metaData.setExpirationDate(expiredDateTime);
       
  1041         }
       
  1042     }
       
  1043 
       
  1044     it = cacheHeaders.findRawHeader("last-modified");
       
  1045     if (it != cacheHeaders.rawHeaders.constEnd())
       
  1046         metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
       
  1047 
       
  1048     bool canDiskCache;
       
  1049     // only cache GET replies by default, all other replies (POST, PUT, DELETE)
       
  1050     //  are not cacheable by default (according to RFC 2616 section 9)
       
  1051     if (httpReply->request().operation() == QHttpNetworkRequest::Get) {
       
  1052 
       
  1053         canDiskCache = true;
       
  1054         // 14.32
       
  1055         // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client
       
  1056         // had sent "Cache-Control: no-cache".
       
  1057         it = cacheHeaders.findRawHeader("pragma");
       
  1058         if (it != cacheHeaders.rawHeaders.constEnd()
       
  1059             && it->second == "no-cache")
       
  1060             canDiskCache = false;
       
  1061 
       
  1062         // HTTP/1.1. Check the Cache-Control header
       
  1063         if (cacheControl.contains("no-cache"))
       
  1064             canDiskCache = false;
       
  1065         else if (cacheControl.contains("no-store"))
       
  1066             canDiskCache = false;
       
  1067 
       
  1068     // responses to POST might be cacheable
       
  1069     } else if (httpReply->request().operation() == QHttpNetworkRequest::Post) {
       
  1070 
       
  1071         canDiskCache = false;
       
  1072         // some pages contain "expires:" and "cache-control: no-cache" field,
       
  1073         // so we only might cache POST requests if we get "cache-control: max-age ..."
       
  1074         if (cacheControl.contains("max-age"))
       
  1075             canDiskCache = true;
       
  1076 
       
  1077     // responses to PUT and DELETE are not cacheable
       
  1078     } else {
       
  1079         canDiskCache = false;
       
  1080     }
       
  1081 
       
  1082     metaData.setSaveToDisk(canDiskCache);
       
  1083     int statusCode = httpReply->statusCode();
       
  1084     QNetworkCacheMetaData::AttributesMap attributes;
       
  1085     if (statusCode != 304) {
       
  1086         // update the status code
       
  1087         attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
       
  1088         attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
       
  1089     } else {
       
  1090         // this is a redirection, keep the attributes intact
       
  1091         attributes = oldMetaData.attributes();
       
  1092     }
       
  1093     metaData.setAttributes(attributes);
       
  1094     return metaData;
       
  1095 }
       
  1096 
       
  1097 QT_END_NAMESPACE
       
  1098 
       
  1099 #endif // QT_NO_HTTP