src/network/access/qnetworkaccesshttpbackend.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/network/access/qnetworkaccesshttpbackend.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1079 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#define QNETWORKACCESSHTTPBACKEND_DEBUG
+
+#include "qnetworkaccesshttpbackend_p.h"
+#include "qnetworkaccessmanager_p.h"
+#include "qnetworkaccesscache_p.h"
+#include "qabstractnetworkcache.h"
+#include "qnetworkrequest.h"
+#include "qnetworkreply.h"
+#include "qnetworkrequest_p.h"
+#include "qnetworkcookie_p.h"
+#include "QtCore/qdatetime.h"
+#include "QtNetwork/qsslconfiguration.h"
+
+#ifndef QT_NO_HTTP
+
+#include <string.h>             // for strchr
+
+QT_BEGIN_NAMESPACE
+
+enum {
+    DefaultHttpPort = 80,
+    DefaultHttpsPort = 443
+};
+
+class QNetworkProxy;
+
+static QByteArray makeCacheKey(QNetworkAccessHttpBackend *backend, QNetworkProxy *proxy)
+{
+    QByteArray result;
+    QUrl copy = backend->url();
+    bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
+    copy.setPort(copy.port(isEncrypted ? DefaultHttpsPort : DefaultHttpPort));
+    result = copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath |
+                            QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+#ifndef QT_NO_NETWORKPROXY
+    if (proxy->type() != QNetworkProxy::NoProxy) {
+        QUrl key;
+
+        switch (proxy->type()) {
+        case QNetworkProxy::Socks5Proxy:
+            key.setScheme(QLatin1String("proxy-socks5"));
+            break;
+
+        case QNetworkProxy::HttpProxy:
+        case QNetworkProxy::HttpCachingProxy:
+            key.setScheme(QLatin1String("proxy-http"));
+            break;
+
+        default:
+            break;
+        }
+
+        if (!key.scheme().isEmpty()) {
+            key.setUserName(proxy->user());
+            key.setHost(proxy->hostName());
+            key.setPort(proxy->port());
+            key.setEncodedQuery(result);
+            result = key.toEncoded();
+        }
+    }
+#endif
+
+    return "http-connection:" + result;
+}
+
+static inline bool isSeparator(register char c)
+{
+    static const char separators[] = "()<>@,;:\\\"/[]?={}";
+    return isLWS(c) || strchr(separators, c) != 0;
+}
+
+// ### merge with nextField in cookiejar.cpp
+static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
+{
+    // The HTTP header is of the form:
+    // header          = #1(directives)
+    // directives      = token | value-directive
+    // value-directive = token "=" (token | quoted-string)
+    QHash<QByteArray, QByteArray> result;
+
+    int pos = 0;
+    while (true) {
+        // skip spaces
+        pos = nextNonWhitespace(header, pos);
+        if (pos == header.length())
+            return result;      // end of parsing
+
+        // pos points to a non-whitespace
+        int comma = header.indexOf(',', pos);
+        int equal = header.indexOf('=', pos);
+        if (comma == pos || equal == pos)
+            // huh? Broken header.
+            return result;
+
+        // The key name is delimited by either a comma, an equal sign or the end
+        // of the header, whichever comes first
+        int end = comma;
+        if (end == -1)
+            end = header.length();
+        if (equal != -1 && end > equal)
+            end = equal;        // equal sign comes before comma/end
+        QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
+        pos = end + 1;
+
+        if (equal != -1) {
+            // case: token "=" (token | quoted-string)
+            // skip spaces
+            pos = nextNonWhitespace(header, pos);
+            if (pos == header.length())
+                // huh? Broken header
+                return result;
+
+            QByteArray value;
+            value.reserve(header.length() - pos);
+            if (header.at(pos) == '"') {
+                // case: quoted-string
+                // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
+                // qdtext         = <any TEXT except <">>
+                // quoted-pair    = "\" CHAR
+                ++pos;
+                while (pos < header.length()) {
+                    register char c = header.at(pos);
+                    if (c == '"') {
+                        // end of quoted text
+                        break;
+                    } else if (c == '\\') {
+                        ++pos;
+                        if (pos >= header.length())
+                            // broken header
+                            return result;
+                        c = header.at(pos);
+                    }
+
+                    value += c;
+                    ++pos;
+                }
+            } else {
+                // case: token
+                while (pos < header.length()) {
+                    register char c = header.at(pos);
+                    if (isSeparator(c))
+                        break;
+                    value += c;
+                    ++pos;
+                }
+            }
+
+            result.insert(key, value);
+
+            // find the comma now:
+            comma = header.indexOf(',', pos);
+            if (comma == -1)
+                return result;  // end of parsing
+            pos = comma + 1;
+        } else {
+            // case: token
+            // key is already set
+            result.insert(key, QByteArray());
+        }
+    }
+}
+
+QNetworkAccessBackend *
+QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
+                                         const QNetworkRequest &request) const
+{
+    // check the operation
+    switch (op) {
+    case QNetworkAccessManager::GetOperation:
+    case QNetworkAccessManager::PostOperation:
+    case QNetworkAccessManager::HeadOperation:
+    case QNetworkAccessManager::PutOperation:
+    case QNetworkAccessManager::DeleteOperation:
+        break;
+
+    default:
+        // no, we can't handle this request
+        return 0;
+    }
+
+    QUrl url = request.url();
+    QString scheme = url.scheme().toLower();
+    if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
+        return new QNetworkAccessHttpBackend;
+
+    return 0;
+}
+
+static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
+{
+    QNetworkReply::NetworkError code;
+    // we've got an error
+    switch (httpStatusCode) {
+    case 401:               // Authorization required
+        code = QNetworkReply::AuthenticationRequiredError;
+        break;
+
+    case 403:               // Access denied
+        code = QNetworkReply::ContentOperationNotPermittedError;
+        break;
+
+    case 404:               // Not Found
+        code = QNetworkReply::ContentNotFoundError;
+        break;
+
+    case 405:               // Method Not Allowed
+        code = QNetworkReply::ContentOperationNotPermittedError;
+        break;
+
+    case 407:
+        code = QNetworkReply::ProxyAuthenticationRequiredError;
+        break;
+
+    default:
+        if (httpStatusCode > 500) {
+            // some kind of server error
+            code = QNetworkReply::ProtocolUnknownError;
+        } else if (httpStatusCode >= 400) {
+            // content error we did not handle above
+            code = QNetworkReply::UnknownContentError;
+        } else {
+            qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
+                     httpStatusCode, qPrintable(url.toString()));
+            code = QNetworkReply::ProtocolFailure;
+        }
+    }
+
+    return code;
+}
+
+class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
+                                      public QNetworkAccessCache::CacheableObject
+{
+    // Q_OBJECT
+public:
+    QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
+        : QHttpNetworkConnection(hostName, port, encrypt)
+    {
+        setExpires(true);
+        setShareable(true);
+    }
+
+    virtual void dispose()
+    {
+#if 0  // sample code; do this right with the API
+        Q_ASSERT(!isWorking());
+#endif
+        delete this;
+    }
+};
+
+QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
+    : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0)
+#ifndef QT_NO_OPENSSL
+    , pendingSslConfiguration(0), pendingIgnoreAllSslErrors(false)
+#endif
+{
+}
+
+QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
+{
+    if (http)
+        disconnectFromHttp();
+#ifndef QT_NO_OPENSSL
+    delete pendingSslConfiguration;
+#endif
+}
+
+void QNetworkAccessHttpBackend::disconnectFromHttp()
+{
+    if (http) {
+        // This is abut disconnecting signals, not about disconnecting TCP connections
+        disconnect(http, 0, this, 0);
+
+        // Get the object cache that stores our QHttpNetworkConnection objects
+        QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
+        cache->releaseEntry(cacheKey);
+    }
+
+    // This is abut disconnecting signals, not about disconnecting TCP connections
+    if (httpReply)
+        disconnect(httpReply, 0, this, 0);
+
+    http = 0;
+    httpReply = 0;
+    cacheKey.clear();
+}
+
+void QNetworkAccessHttpBackend::finished()
+{
+    if (http)
+        disconnectFromHttp();
+    // call parent
+    QNetworkAccessBackend::finished();
+}
+
+void QNetworkAccessHttpBackend::setupConnection()
+{
+#ifndef QT_NO_NETWORKPROXY
+    connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+            SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
+#endif
+    connect(http, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+            SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
+    connect(http, SIGNAL(error(QNetworkReply::NetworkError,QString)),
+            SLOT(httpError(QNetworkReply::NetworkError,QString)));
+#ifndef QT_NO_OPENSSL
+    connect(http, SIGNAL(sslErrors(QList<QSslError>)),
+            SLOT(sslErrors(QList<QSslError>)));
+#endif
+}
+
+/*
+    For a given httpRequest
+    1) If AlwaysNetwork, return
+    2) If we have a cache entry for this url populate headers so the server can return 304
+    3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
+ */
+void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache)
+{
+    QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
+        (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
+    if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
+        // forced reload from the network
+        // tell any caching proxy servers to reload too
+        httpRequest.setHeaderField("Cache-Control", "no-cache");
+        httpRequest.setHeaderField("Pragma", "no-cache");
+        return;
+    }
+
+    QAbstractNetworkCache *nc = networkCache();
+    if (!nc)
+        return;                 // no local cache
+
+    QNetworkCacheMetaData metaData = nc->metaData(url());
+    if (!metaData.isValid())
+        return;                 // not in cache
+
+    if (!metaData.saveToDisk())
+        return;
+
+    QNetworkHeadersPrivate cacheHeaders;
+    QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
+    cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
+
+    it = cacheHeaders.findRawHeader("etag");
+    if (it != cacheHeaders.rawHeaders.constEnd())
+        httpRequest.setHeaderField("If-None-Match", it->second);
+
+    QDateTime lastModified = metaData.lastModified();
+    if (lastModified.isValid())
+        httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
+
+    it = cacheHeaders.findRawHeader("Cache-Control");
+    if (it != cacheHeaders.rawHeaders.constEnd()) {
+        QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
+        if (cacheControl.contains("must-revalidate"))
+            return;
+    }
+
+    /*
+     * age_value
+     *      is the value of Age: header received by the cache with
+     *              this response.
+     * date_value
+     *      is the value of the origin server's Date: header
+     * request_time
+     *      is the (local) time when the cache made the request
+     *              that resulted in this cached response
+     * response_time
+     *      is the (local) time when the cache received the
+     *              response
+     * now
+     *      is the current (local) time
+     */
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    int age_value = 0;
+    it = cacheHeaders.findRawHeader("age");
+    if (it != cacheHeaders.rawHeaders.constEnd())
+        age_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
+
+    int date_value = 0;
+    it = cacheHeaders.findRawHeader("date");
+    if (it != cacheHeaders.rawHeaders.constEnd())
+        date_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
+
+    int now = currentDateTime.toUTC().toTime_t();
+    int request_time = now;
+    int response_time = now;
+
+    int apparent_age = qMax(0, response_time - date_value);
+    int corrected_received_age = qMax(apparent_age, age_value);
+    int response_delay = response_time - request_time;
+    int corrected_initial_age = corrected_received_age + response_delay;
+    int resident_time = now - response_time;
+    int current_age   = corrected_initial_age + resident_time;
+
+    // RFC 2616 13.2.4 Expiration Calculations
+    QDateTime expirationDate = metaData.expirationDate();
+    if (!expirationDate.isValid()) {
+        if (lastModified.isValid()) {
+            int diff = currentDateTime.secsTo(lastModified);
+            expirationDate = lastModified;
+            expirationDate.addSecs(diff / 10);
+            if (httpRequest.headerField("Warning").isEmpty()) {
+                QDateTime dt;
+                dt.setTime_t(current_age);
+                if (dt.daysTo(currentDateTime) > 1)
+                    httpRequest.setHeaderField("Warning", "113");
+            }
+        }
+    }
+
+    int freshness_lifetime = currentDateTime.secsTo(expirationDate);
+    bool response_is_fresh = (freshness_lifetime > current_age);
+
+    if (!response_is_fresh && CacheLoadControlAttribute == QNetworkRequest::PreferNetwork)
+        return;
+
+    loadedFromCache = true;
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+    qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
+#endif
+    if (!sendCacheContents(metaData))
+        loadedFromCache = false;
+}
+
+void QNetworkAccessHttpBackend::postRequest()
+{
+    bool loadedFromCache = false;
+    QHttpNetworkRequest httpRequest;
+    switch (operation()) {
+    case QNetworkAccessManager::GetOperation:
+        httpRequest.setOperation(QHttpNetworkRequest::Get);
+        validateCache(httpRequest, loadedFromCache);
+        break;
+
+    case QNetworkAccessManager::HeadOperation:
+        httpRequest.setOperation(QHttpNetworkRequest::Head);
+        validateCache(httpRequest, loadedFromCache);
+        break;
+
+    case QNetworkAccessManager::PostOperation:
+        invalidateCache();
+        httpRequest.setOperation(QHttpNetworkRequest::Post);
+        httpRequest.setUploadByteDevice(createUploadByteDevice());
+        break;
+
+    case QNetworkAccessManager::PutOperation:
+        invalidateCache();
+        httpRequest.setOperation(QHttpNetworkRequest::Put);
+        httpRequest.setUploadByteDevice(createUploadByteDevice());
+        break;
+
+    case QNetworkAccessManager::DeleteOperation:
+        invalidateCache();
+        httpRequest.setOperation(QHttpNetworkRequest::Delete);
+        break;
+
+    default:
+        break;                  // can't happen
+    }
+
+    httpRequest.setUrl(url());
+
+    QList<QByteArray> headers = request().rawHeaderList();
+    foreach (const QByteArray &header, headers)
+        httpRequest.setHeaderField(header, request().rawHeader(header));
+
+    if (loadedFromCache) {
+        // commented this out since it will be called later anyway
+        // by copyFinished()
+        //QNetworkAccessBackend::finished();
+        return;    // no need to send the request! :)
+    }
+
+    if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
+        httpRequest.setPipeliningAllowed(true);
+
+    httpReply = http->sendRequest(httpRequest);
+    httpReply->setParent(this);
+#ifndef QT_NO_OPENSSL
+    if (pendingSslConfiguration)
+        httpReply->setSslConfiguration(*pendingSslConfiguration);
+    if (pendingIgnoreAllSslErrors)
+        httpReply->ignoreSslErrors();
+    httpReply->ignoreSslErrors(pendingIgnoreSslErrorsList);
+#endif
+
+    connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
+    connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
+    connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
+            SLOT(httpError(QNetworkReply::NetworkError,QString)));
+    connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
+}
+
+void QNetworkAccessHttpBackend::invalidateCache()
+{
+    QAbstractNetworkCache *nc = networkCache();
+    if (nc)
+        nc->remove(url());
+}
+
+void QNetworkAccessHttpBackend::open()
+{
+    QUrl url = request().url();
+    bool encrypt = url.scheme().toLower() == QLatin1String("https");
+    setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
+
+    // set the port number in the reply if it wasn't set
+    url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
+
+    QNetworkProxy *theProxy = 0;
+#ifndef QT_NO_NETWORKPROXY
+    QNetworkProxy transparentProxy, cacheProxy;
+
+    foreach (const QNetworkProxy &p, proxyList()) {
+        // use the first proxy that works
+        // for non-encrypted connections, any transparent or HTTP proxy
+        // for encrypted, only transparent proxies
+        if (!encrypt
+            && (p.capabilities() & QNetworkProxy::CachingCapability)
+            && (p.type() == QNetworkProxy::HttpProxy ||
+                p.type() == QNetworkProxy::HttpCachingProxy)) {
+            cacheProxy = p;
+            transparentProxy = QNetworkProxy::NoProxy;
+            theProxy = &cacheProxy;
+            break;
+        }
+        if (p.isTransparentProxy()) {
+            transparentProxy = p;
+            cacheProxy = QNetworkProxy::NoProxy;
+            theProxy = &transparentProxy;
+            break;
+        }
+    }
+
+    // check if at least one of the proxies
+    if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
+        cacheProxy.type() == QNetworkProxy::DefaultProxy) {
+        // unsuitable proxies
+        error(QNetworkReply::ProxyNotFoundError,
+              tr("No suitable proxy found"));
+        finished();
+        return;
+    }
+#endif
+
+    // check if we have an open connection to this host
+    cacheKey = makeCacheKey(this, theProxy);
+    QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
+    // the http object is actually a QHttpNetworkConnection
+    http = static_cast<QNetworkAccessCachedHttpConnection *>(cache->requestEntryNow(cacheKey));
+    if (http == 0) {
+        // no entry in cache; create an object
+        // the http object is actually a QHttpNetworkConnection
+        http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt);
+
+#ifndef QT_NO_NETWORKPROXY
+        http->setTransparentProxy(transparentProxy);
+        http->setCacheProxy(cacheProxy);
+#endif
+
+        // cache the QHttpNetworkConnection corresponding to this cache key
+        cache->addEntry(cacheKey, http);
+    }
+
+    setupConnection();
+    postRequest();
+}
+
+void QNetworkAccessHttpBackend::closeDownstreamChannel()
+{
+    // this indicates that the user closed the stream while the reply isn't finished yet
+}
+
+bool QNetworkAccessHttpBackend::waitForDownstreamReadyRead(int msecs)
+{
+    Q_ASSERT(http);
+
+    if (httpReply->bytesAvailable()) {
+        readFromHttp();
+        return true;
+    }
+
+    if (msecs == 0) {
+        // no bytes available in the socket and no waiting
+        return false;
+    }
+
+    // ### FIXME
+    qCritical("QNetworkAccess: HTTP backend does not support waitForReadyRead()");
+    return false;
+}
+
+
+void QNetworkAccessHttpBackend::downstreamReadyWrite()
+{
+    readFromHttp();
+    if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
+        replyFinished();
+}
+
+void QNetworkAccessHttpBackend::replyReadyRead()
+{
+    readFromHttp();
+}
+
+void QNetworkAccessHttpBackend::readFromHttp()
+{
+    if (!httpReply)
+        return;
+
+    // We read possibly more than nextDownstreamBlockSize(), but
+    // this is not a critical thing since it is already in the
+    // memory anyway
+
+    QByteDataBuffer list;
+
+    while (httpReply->bytesAvailable() != 0 && nextDownstreamBlockSize() != 0 && nextDownstreamBlockSize() > list.byteAmount()) {
+        QByteArray data = httpReply->readAny();
+        list.append(data);
+    }
+
+    if (!list.isEmpty())
+      writeDownstreamData(list);
+}
+
+void QNetworkAccessHttpBackend::replyFinished()
+{
+    if (httpReply->bytesAvailable())
+        // we haven't read everything yet. Wait some more.
+        return;
+
+    int statusCode = httpReply->statusCode();
+    if (statusCode >= 400) {
+        // it's an error reply
+        QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
+                                                      "Error downloading %1 - server replied: %2"));
+        msg = msg.arg(url().toString(), httpReply->reasonPhrase());
+        error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg);
+    }
+
+#ifndef QT_NO_OPENSSL
+    // store the SSL configuration now
+    // once we call finished(), we won't have access to httpReply anymore
+    QSslConfiguration sslConfig = httpReply->sslConfiguration();
+    if (pendingSslConfiguration) {
+        *pendingSslConfiguration = sslConfig;
+    } else if (!sslConfig.isNull()) {
+        QT_TRY {
+            pendingSslConfiguration = new QSslConfiguration(sslConfig);
+        } QT_CATCH(...) {
+            qWarning("QNetworkAccess: could not allocate a QSslConfiguration object for a SSL connection.");
+        }
+    }
+#endif
+
+    finished();
+}
+
+void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode)
+{
+    switch (statusCode) {
+    case 301:                   // Moved Permanently
+    case 302:                   // Found
+    case 303:                   // See Other
+    case 307:                   // Temporary Redirect
+        // What do we do about the caching of the HTML note?
+        // The response to a 303 MUST NOT be cached, while the response to
+        // all of the others is cacheable if the headers indicate it to be
+        QByteArray header = rawHeader("location");
+        QUrl url = QUrl::fromEncoded(header);
+        if (!url.isValid())
+            url = QUrl(QLatin1String(header));
+        redirectionRequested(url);
+    }
+}
+
+void QNetworkAccessHttpBackend::replyHeaderChanged()
+{
+    setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, httpReply->isPipeliningUsed());
+
+    // reconstruct the HTTP header
+    QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header();
+    QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
+                                                        end = headerMap.constEnd();
+    QByteArray header;
+
+    for (; it != end; ++it) {
+        QByteArray value = rawHeader(it->first);
+        if (!value.isEmpty()) {
+            if (it->first.toLower() == "set-cookie")
+                value += "\n";
+            else
+                value += ", ";
+        }
+        value += it->second;
+        setRawHeader(it->first, value);
+    }
+
+    setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode());
+    setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
+
+    // is it a redirection?
+    const int statusCode = httpReply->statusCode();
+    checkForRedirect(statusCode);
+
+    if (statusCode >= 500 && statusCode < 600) {
+        QAbstractNetworkCache *nc = networkCache();
+        if (nc) {
+            QNetworkCacheMetaData metaData = nc->metaData(url());
+            QNetworkHeadersPrivate cacheHeaders;
+            cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
+            QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
+            it = cacheHeaders.findRawHeader("Cache-Control");
+            bool mustReValidate = false;
+            if (it != cacheHeaders.rawHeaders.constEnd()) {
+                QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
+                if (cacheControl.contains("must-revalidate"))
+                    mustReValidate = true;
+            }
+            if (!mustReValidate && sendCacheContents(metaData))
+                return;
+        }
+    }
+
+    if (statusCode == 304) {
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+        qDebug() << "Received a 304 from" << url();
+#endif
+        QAbstractNetworkCache *nc = networkCache();
+        if (nc) {
+            QNetworkCacheMetaData oldMetaData = nc->metaData(url());
+            QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
+            if (oldMetaData != metaData)
+                nc->updateMetaData(metaData);
+            if (sendCacheContents(metaData))
+                return;
+        }
+    }
+
+
+    if (statusCode != 304 && statusCode != 303) {
+        if (!isCachingEnabled())
+            setCachingEnabled(true);
+    }
+    metaDataChanged();
+}
+
+void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
+                                                           QAuthenticator *auth)
+{
+    authenticationRequired(auth);
+}
+
+void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode,
+                                          const QString &errorString)
+{
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+    qDebug() << "http error!" << errorCode << errorString;
+#endif
+#if 0
+    static const QNetworkReply::NetworkError conversionTable[] = {
+        QNetworkReply::ConnectionRefusedError,
+        QNetworkReply::RemoteHostClosedError,
+        QNetworkReply::HostNotFoundError,
+        QNetworkReply::UnknownNetworkError, // SocketAccessError
+        QNetworkReply::UnknownNetworkError, // SocketResourceError
+        QNetworkReply::TimeoutError,        // SocketTimeoutError
+        QNetworkReply::UnknownNetworkError, // DatagramTooLargeError
+        QNetworkReply::UnknownNetworkError, // NetworkError
+        QNetworkReply::UnknownNetworkError, // AddressInUseError
+        QNetworkReply::UnknownNetworkError, // SocketAddressNotAvailableError
+        QNetworkReply::UnknownNetworkError, // UnsupportedSocketOperationError
+        QNetworkReply::UnknownNetworkError, // UnfinishedSocketOperationError
+        QNetworkReply::ProxyAuthenticationRequiredError
+    };
+    QNetworkReply::NetworkError code;
+    if (int(errorCode) >= 0 &&
+        uint(errorCode) < (sizeof conversionTable / sizeof conversionTable[0]))
+        code = conversionTable[errorCode];
+    else
+        code = QNetworkReply::UnknownNetworkError;
+#endif
+    error(errorCode, errorString);
+    finished();
+}
+
+/*
+    A simple web page that can be used to test us: http://www.procata.com/cachetest/
+ */
+bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData)
+{
+    setCachingEnabled(false);
+    if (!metaData.isValid())
+        return false;
+
+    QAbstractNetworkCache *nc = networkCache();
+    Q_ASSERT(nc);
+    QIODevice *contents = nc->data(url());
+    if (!contents) {
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+        qDebug() << "Can not send cache, the contents are 0" << url();
+#endif
+        return false;
+    }
+    contents->setParent(this);
+
+    QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
+    int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+    if (status < 100)
+        status = 200;           // fake it
+
+    setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
+    setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
+    setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
+
+    QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
+    QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
+                                                       end = rawHeaders.constEnd();
+    for ( ; it != end; ++it)
+        setRawHeader(it->first, it->second);
+
+    checkForRedirect(status);
+
+    emit metaDataChanged();
+
+    // invoke this asynchronously, else Arora/QtDemoBrowser don't like cached downloads
+    // see task 250221 / 251801
+    qRegisterMetaType<QIODevice*>("QIODevice*");
+    QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents));
+
+
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+    qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
+#endif
+    if (httpReply)
+        disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished()));
+    return true;
+}
+
+void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
+{
+    delete dev;
+    finished();
+}
+
+#ifndef QT_NO_OPENSSL
+void QNetworkAccessHttpBackend::ignoreSslErrors()
+{
+    if (httpReply)
+        httpReply->ignoreSslErrors();
+    else
+        pendingIgnoreAllSslErrors = true;
+}
+
+void QNetworkAccessHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
+{
+    if (httpReply) {
+        httpReply->ignoreSslErrors(errors);
+    } else {
+        // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
+        // is called before QNetworkAccessManager::get() (or post(), etc.)
+        pendingIgnoreSslErrorsList = errors;
+    }
+}
+
+void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
+{
+    if (httpReply)
+        config = httpReply->sslConfiguration();
+    else if (pendingSslConfiguration)
+        config = *pendingSslConfiguration;
+}
+
+void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig)
+{
+    if (httpReply)
+        httpReply->setSslConfiguration(newconfig);
+    else if (pendingSslConfiguration)
+        *pendingSslConfiguration = newconfig;
+    else
+        pendingSslConfiguration = new QSslConfiguration(newconfig);
+}
+#endif
+
+QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
+{
+    QNetworkCacheMetaData metaData = oldMetaData;
+
+    QNetworkHeadersPrivate cacheHeaders;
+    cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
+    QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
+
+    QList<QByteArray> newHeaders = rawHeaderList();
+    foreach (QByteArray header, newHeaders) {
+        QByteArray originalHeader = header;
+        header = header.toLower();
+        bool hop_by_hop =
+            (header == "connection"
+             || header == "keep-alive"
+             || header == "proxy-authenticate"
+             || header == "proxy-authorization"
+             || header == "te"
+             || header == "trailers"
+             || header == "transfer-encoding"
+             || header ==  "upgrade");
+        if (hop_by_hop)
+            continue;
+
+        // for 4.6.0, we were planning to not store the date header in the
+        // cached resource; through that we planned to reduce the number
+        // of writes to disk when using a QNetworkDiskCache (i.e. don't
+        // write to disk when only the date changes).
+        // However, without the date we cannot calculate the age of the page
+        // anymore. Consider a proper fix of that problem for 4.6.1.
+        //if (header == "date")
+            //continue;
+
+        // Don't store Warning 1xx headers
+        if (header == "warning") {
+            QByteArray v = rawHeader(header);
+            if (v.length() == 3
+                && v[0] == '1'
+                && v[1] >= '0' && v[1] <= '9'
+                && v[2] >= '0' && v[2] <= '9')
+                continue;
+        }
+
+        it = cacheHeaders.findRawHeader(header);
+        if (it != cacheHeaders.rawHeaders.constEnd()) {
+            // Match the behavior of Firefox and assume Cache-Control: "no-transform"
+            if (header == "content-encoding"
+                || header == "content-range"
+                || header == "content-type")
+                continue;
+
+            // For MS servers that send "Content-Length: 0" on 304 responses
+            // ignore this too
+            if (header == "content-length")
+                continue;
+        }
+
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+        QByteArray n = rawHeader(header);
+        QByteArray o;
+        if (it != cacheHeaders.rawHeaders.constEnd())
+            o = (*it).second;
+        if (n != o && header != "date") {
+            qDebug() << "replacing" << header;
+            qDebug() << "new" << n;
+            qDebug() << "old" << o;
+        }
+#endif
+        cacheHeaders.setRawHeader(originalHeader, rawHeader(header));
+    }
+    metaData.setRawHeaders(cacheHeaders.rawHeaders);
+
+    bool checkExpired = true;
+
+    QHash<QByteArray, QByteArray> cacheControl;
+    it = cacheHeaders.findRawHeader("Cache-Control");
+    if (it != cacheHeaders.rawHeaders.constEnd()) {
+        cacheControl = parseHttpOptionHeader(it->second);
+        QByteArray maxAge = cacheControl.value("max-age");
+        if (!maxAge.isEmpty()) {
+            checkExpired = false;
+            QDateTime dt = QDateTime::currentDateTime();
+            dt = dt.addSecs(maxAge.toInt());
+            metaData.setExpirationDate(dt);
+        }
+    }
+    if (checkExpired) {
+        it = cacheHeaders.findRawHeader("expires");
+        if (it != cacheHeaders.rawHeaders.constEnd()) {
+            QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
+            metaData.setExpirationDate(expiredDateTime);
+        }
+    }
+
+    it = cacheHeaders.findRawHeader("last-modified");
+    if (it != cacheHeaders.rawHeaders.constEnd())
+        metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
+
+    bool canDiskCache;
+    // only cache GET replies by default, all other replies (POST, PUT, DELETE)
+    //  are not cacheable by default (according to RFC 2616 section 9)
+    if (httpReply->request().operation() == QHttpNetworkRequest::Get) {
+
+        canDiskCache = true;
+        // 14.32
+        // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client
+        // had sent "Cache-Control: no-cache".
+        it = cacheHeaders.findRawHeader("pragma");
+        if (it != cacheHeaders.rawHeaders.constEnd()
+            && it->second == "no-cache")
+            canDiskCache = false;
+
+        // HTTP/1.1. Check the Cache-Control header
+        if (cacheControl.contains("no-cache"))
+            canDiskCache = false;
+        else if (cacheControl.contains("no-store"))
+            canDiskCache = false;
+
+    // responses to POST might be cacheable
+    } else if (httpReply->request().operation() == QHttpNetworkRequest::Post) {
+
+        canDiskCache = false;
+        // some pages contain "expires:" and "cache-control: no-cache" field,
+        // so we only might cache POST requests if we get "cache-control: max-age ..."
+        if (cacheControl.contains("max-age"))
+            canDiskCache = true;
+
+    // responses to PUT and DELETE are not cacheable
+    } else {
+        canDiskCache = false;
+    }
+
+    metaData.setSaveToDisk(canDiskCache);
+    int statusCode = httpReply->statusCode();
+    QNetworkCacheMetaData::AttributesMap attributes;
+    if (statusCode != 304) {
+        // update the status code
+        attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
+        attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
+    } else {
+        // this is a redirection, keep the attributes intact
+        attributes = oldMetaData.attributes();
+    }
+    metaData.setAttributes(attributes);
+    return metaData;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP