webengine/osswebengine/WebKit/qt/Api/qwebnetworkinterface.cpp
changeset 0 dd21522fd290
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebKit/qt/Api/qwebnetworkinterface.cpp	Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,1064 @@
+/*
+  Copyright (C) 2006 Enrico Ros <enrico.ros@m31engineering.it>
+  Copyright (C) 2007 Trolltech ASA
+  Copyright (C) 2007 Staikos Computing Services Inc.  <info@staikos.net>
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+
+  You should have received a copy of the GNU Library General Public License
+  along with this library; see the file COPYING.LIB.  If not, write to
+  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+  Boston, MA 02110-1301, USA.
+
+  This class provides all functionality needed for loading images, style sheets and html
+  pages from the web. It has a memory cache for these objects.
+*/
+#include <qglobal.h>
+#include "qwebframe.h"
+#include "qwebnetworkinterface.h"
+#include "qwebnetworkinterface_p.h"
+#include "qwebobjectpluginconnector.h"
+#include "qwebpage.h"
+#include <qdebug.h>
+#include <qfile.h>
+#include <qnetworkproxy.h>
+#include <qurl.h>
+#include <QAuthenticator>
+#include <QCoreApplication>
+#include <QSslError>
+
+#include "ResourceHandle.h"
+#include "ResourceHandleClient.h"
+#include "ResourceHandleInternal.h"
+#include "MIMETypeRegistry.h"
+#include "CookieJar.h"
+
+#define notImplemented() qDebug("FIXME: UNIMPLEMENTED: %s:%d (%s)", __FILE__, __LINE__, __FUNCTION__)
+
+#if 0
+#define DEBUG qDebug
+#else
+#define DEBUG if (1) {} else qDebug
+#endif
+
+static QWebNetworkInterface *default_interface = 0;
+static QWebNetworkManager *manager = 0;
+
+using namespace WebCore;
+
+uint qHash(const HostInfo &info)
+{
+    return qHash(info.host) + info.port;
+}
+
+static bool operator==(const HostInfo &i1, const HostInfo &i2)
+{
+    return i1.port == i2.port && i1.host == i2.host;
+}
+
+void QWebNetworkRequestPrivate::init(const WebCore::ResourceRequest &resourceRequest)
+{
+    KURL url = resourceRequest.url();
+    QUrl qurl = QString(url.url());
+    init(resourceRequest.httpMethod(), qurl, &resourceRequest);
+}
+
+void QWebNetworkRequestPrivate::init(const QString &method, const QUrl &url, const WebCore::ResourceRequest *resourceRequest)
+{
+    httpHeader = QHttpRequestHeader(method, url.toString(QUrl::RemoveScheme|QUrl::RemoveAuthority));
+    httpHeader.setValue(QLatin1String("User-Agent"),
+                         QLatin1String("Mozilla/5.0 (PC; U; Intel; Linux; en) AppleWebKit/420+ (KHTML, like Gecko)"));
+    httpHeader.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
+    setURL(url);
+
+    if (resourceRequest) {
+        const QString scheme = url.scheme().toLower();
+        if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
+            QString cookies = WebCore::cookies(resourceRequest->url());
+            if (!cookies.isEmpty())
+                httpHeader.setValue(QLatin1String("Cookie"), cookies);
+        }
+
+
+        const HTTPHeaderMap& loaderHeaders = resourceRequest->httpHeaderFields();
+        HTTPHeaderMap::const_iterator end = loaderHeaders.end();
+        for (HTTPHeaderMap::const_iterator it = loaderHeaders.begin(); it != end; ++it)
+            httpHeader.setValue(it->first, it->second);
+
+        // handle and perform a 'POST' request
+        if (method == "POST") {
+            DeprecatedString pd = resourceRequest->httpBody()->flattenToString().deprecatedString();
+            postData = QByteArray(pd.ascii(), pd.length());
+            httpHeader.setValue(QLatin1String("content-length"), QString::number(postData.size()));
+        }
+    }
+}
+
+void QWebNetworkRequestPrivate::setURL(const QUrl &u)
+{
+    url = u;
+    int port = url.port();
+    const QString scheme = u.scheme();
+    if (port > 0 && (port != 80 || scheme != "http") && (port != 443 || scheme != "https"))
+        httpHeader.setValue(QLatin1String("Host"), url.host() + QLatin1Char(':') + QString::number(port));
+    else
+        httpHeader.setValue(QLatin1String("Host"), url.host());
+}
+
+/*!
+  \class QWebNetworkRequest
+
+  The QWebNetworkRequest class represents a request for data from the network with all the
+  necessary information needed for retrieval. This includes the url, extra HTTP header fields
+  as well as data for a HTTP POST request.
+*/
+
+QWebNetworkRequest::QWebNetworkRequest()
+    : d(new QWebNetworkRequestPrivate)
+{
+}
+
+QWebNetworkRequest::QWebNetworkRequest(const QUrl &url, Method method, const QByteArray &postData)
+    : d(new QWebNetworkRequestPrivate)
+{
+    d->init(method == Get ? "GET" : "POST", url);
+    d->postData = postData;
+}
+
+QWebNetworkRequest::QWebNetworkRequest(const QWebNetworkRequest &other)
+    : d(new QWebNetworkRequestPrivate(*other.d))
+{
+}
+
+QWebNetworkRequest &QWebNetworkRequest::operator=(const QWebNetworkRequest &other)
+{
+    *d = *other.d;
+    return *this;
+}
+
+/*!
+  \internal
+*/
+QWebNetworkRequest::QWebNetworkRequest(const QWebNetworkRequestPrivate &priv)
+    : d(new QWebNetworkRequestPrivate(priv))
+{
+}
+
+/*!
+  \internal
+*/
+QWebNetworkRequest::QWebNetworkRequest(const WebCore::ResourceRequest &request)
+    : d(new QWebNetworkRequestPrivate)
+{
+    d->init(request);
+}
+
+QWebNetworkRequest::~QWebNetworkRequest()
+{
+    delete d;
+}
+
+/*!
+  The requested URL
+*/
+QUrl QWebNetworkRequest::url() const
+{
+    return d->url;
+}
+
+/*!
+   Sets the URL to request.
+
+   Note that setting the URL also sets the "Host" field in the HTTP header.
+*/
+void QWebNetworkRequest::setUrl(const QUrl &url)
+{
+    d->setURL(url);
+}
+
+/*!
+   The http request header information.
+*/
+QHttpRequestHeader QWebNetworkRequest::httpHeader() const
+{
+    return d->httpHeader;
+}
+
+void QWebNetworkRequest::setHttpHeader(const QHttpRequestHeader &header) const
+{
+    d->httpHeader = header;
+}
+
+QString QWebNetworkRequest::httpHeaderField(const QString &key) const
+{
+    return d->httpHeader.value(key);
+}
+
+void QWebNetworkRequest::setHttpHeaderField(const QString &key, const QString &value)
+{
+    d->httpHeader.setValue(key, value);
+}
+
+/*!
+    Post data sent with HTTP POST requests.
+*/
+QByteArray QWebNetworkRequest::postData() const
+{
+    return d->postData;
+}
+
+void QWebNetworkRequest::setPostData(const QByteArray &data)
+{
+    d->postData = data;
+}
+
+/*!
+  \class QWebNetworkJob
+
+  The QWebNetworkJob class represents a network job, that needs to be
+  processed by the QWebNetworkInterface.
+
+  This class is only required when implementing a new network layer (or
+  support for a special protocol) using QWebNetworkInterface.
+
+  QWebNetworkJob objects are created and owned by the QtWebKit library.
+  Most of it's properties are read-only.
+
+  The job is reference counted. This can be used to ensure that the job doesn't
+  get deleted while it's still stored in some data structure.
+*/
+
+/*!
+  \internal
+*/
+QWebNetworkJob::QWebNetworkJob()
+    : d(new QWebNetworkJobPrivate)
+{
+    d->ref = 1;
+    d->redirected = false;
+    d->interface = 0;
+}
+
+/*!
+  \internal
+*/
+QWebNetworkJob::~QWebNetworkJob()
+{
+    delete d;
+}
+
+/*!
+  The requested URL
+*/
+QUrl QWebNetworkJob::url() const
+{
+    return d->request.url;
+}
+
+/*!
+  Post data associated with the job
+*/
+QByteArray QWebNetworkJob::postData() const
+{
+    return d->request.postData;
+}
+
+/*!
+  The HTTP request header that should be used to download the job.
+*/
+QHttpRequestHeader QWebNetworkJob::httpHeader() const
+{
+    return d->request.httpHeader;
+}
+
+/*!
+  The complete network request that should be used to download the job.
+*/
+QWebNetworkRequest QWebNetworkJob::request() const
+{
+    return QWebNetworkRequest(d->request);
+}
+
+/*!
+  The HTTP response header received from the network.
+*/
+QHttpResponseHeader QWebNetworkJob::response() const
+{
+    return d->response;
+}
+
+/*!
+  Sets the HTTP reponse header. The response header has to be called before
+  emitting QWebNetworkInterface::started.
+*/
+void QWebNetworkJob::setResponse(const QHttpResponseHeader &response)
+{
+    d->response = response;
+}
+
+/*!
+  returns true if the job has been cancelled by the WebKit framework
+*/
+bool QWebNetworkJob::cancelled() const
+{
+    return !d->resourceHandle && !d->connector;
+}
+
+/*!
+  reference the job.
+*/
+void QWebNetworkJob::ref()
+{
+    ++d->ref;
+}
+
+/*!
+  derefence the job.
+
+  If the reference count drops to 0 this method also deletes the job.
+
+  Returns false if the reference count has dropped to 0.
+*/
+bool QWebNetworkJob::deref()
+{
+    if (!--d->ref) {
+        delete this;
+        return false;
+    }
+    return true;
+}
+
+/*!
+   Returns the network interface that is associated with this job.
+*/
+QWebNetworkInterface *QWebNetworkJob::networkInterface() const
+{
+    return d->interface;
+}
+
+/*!
+   Returns the network interface that is associated with this job.
+*/
+QWebFrame *QWebNetworkJob::frame() const
+{
+    if (d->resourceHandle) {
+        ResourceHandleInternal *rhi = d->resourceHandle->getInternal();
+        if (rhi) {
+            return rhi->m_frame;
+        }
+    }
+    return 0;
+}
+
+/*!
+  \class QWebNetworkManager
+  \internal
+*/
+QWebNetworkManager::QWebNetworkManager()
+    : QObject(0)
+{
+}
+
+QWebNetworkManager *QWebNetworkManager::self()
+{
+    // ensure everything's constructed and connected
+    QWebNetworkInterface::defaultInterface();
+
+    return manager;
+}
+
+bool QWebNetworkManager::add(ResourceHandle *handle, QWebNetworkInterface *interface)
+{
+    if (!interface)
+        interface = default_interface;
+
+    ASSERT(interface);
+
+    QWebNetworkJob *job = new QWebNetworkJob();
+    handle->getInternal()->m_job = job;
+    job->d->resourceHandle = handle;
+    job->d->interface = interface;
+    job->d->connector = 0;
+
+    job->d->request.init(handle->request());
+
+    const QString method = handle->getInternal()->m_request.httpMethod();
+    if (method != "POST" && method != "GET") {
+        // don't know what to do! (probably a request error!!)
+        // but treat it like a 'GET' request
+        qWarning("REQUEST: [%s]\n", qPrintable(job->d->request.httpHeader.toString()));
+    }
+
+    DEBUG() << "QWebNetworkManager::add:" <<  job->d->request.httpHeader.toString();
+
+    interface->addJob(job);
+
+    return true;
+}
+
+void QWebNetworkManager::cancel(ResourceHandle *handle)
+{
+    QWebNetworkJob *job = handle->getInternal()->m_job;
+    if (!job)
+        return;
+    DEBUG() << "QWebNetworkManager::cancel:" <<  job->d->request.httpHeader.toString();
+    job->d->resourceHandle = 0;
+    job->d->connector = 0;
+    job->d->interface->cancelJob(job);
+    handle->getInternal()->m_job = 0;
+}
+
+void QWebNetworkManager::started(QWebNetworkJob *job)
+{
+    ResourceHandleClient* client = 0;
+    if (job->d->resourceHandle) {
+        client = job->d->resourceHandle->client();
+        if (!client)
+            return;
+    } else if (!job->d->connector) {
+        return;
+    }
+
+    DEBUG() << "ResourceHandleManager::receivedResponse:";
+    DEBUG() << job->d->response.toString();
+
+    QStringList cookies = job->d->response.allValues("Set-Cookie");
+    KURL url(job->url().toString());
+    foreach (QString c, cookies) {
+        setCookies(url, url, c);
+    }
+    QString contentType = job->d->response.value("Content-Type");
+    QString encoding;
+    int idx = contentType.indexOf(QLatin1Char(';'));
+    if (idx > 0) {
+        QString remainder = contentType.mid(idx + 1).toLower();
+        contentType = contentType.left(idx).trimmed();
+
+        idx = remainder.indexOf("charset");
+        if (idx >= 0) {
+            idx = remainder.indexOf(QLatin1Char('='), idx);
+            if (idx >= 0)
+                encoding = remainder.mid(idx + 1).trimmed();
+        }
+    }
+    if (contentType.isEmpty()) {
+        // let's try to guess from the extension
+        QString extension = job->d->request.url.path();
+        int index = extension.lastIndexOf(QLatin1Char('.'));
+        if (index > 0) {
+            extension = extension.mid(index + 1);
+            contentType = MIMETypeRegistry::getMIMETypeForExtension(extension);
+        }
+    }
+//     qDebug() << "Content-Type=" << contentType;
+//     qDebug() << "Encoding=" << encoding;
+
+
+    ResourceResponse response(url, contentType,
+                              0 /* FIXME */,
+                              encoding,
+                              String() /* FIXME */);
+
+    int statusCode = job->d->response.statusCode();
+    response.setHTTPStatusCode(statusCode);
+    /* Fill in the other fields */
+
+    if (statusCode >= 300 && statusCode < 400) {
+        // we're on a redirect page! if the 'Location:' field is valid, we redirect
+        QString location = job->d->response.value("location");
+        DEBUG() << "Redirection";
+        if (!location.isEmpty()) {
+            QUrl newUrl = job->d->request.url.resolved(location);
+            if (job->d->resourceHandle) {
+                ResourceRequest newRequest = job->d->resourceHandle->request();
+                newRequest.setURL(KURL(newUrl.toString()));
+                if (client)
+                    client->willSendRequest(job->d->resourceHandle, newRequest, response);
+            }
+
+            job->d->request.httpHeader.setRequest(job->d->request.httpHeader.method(),
+                                                  newUrl.toString(QUrl::RemoveScheme|QUrl::RemoveAuthority));
+            job->d->request.setURL(newUrl);
+            job->d->redirected = true;
+            return;
+        }
+    }
+
+    if (client)
+        client->didReceiveResponse(job->d->resourceHandle, response);
+    if (job->d->connector)
+        emit job->d->connector->started(job);
+
+}
+
+void QWebNetworkManager::data(QWebNetworkJob *job, const QByteArray &data)
+{
+    ResourceHandleClient* client = 0;
+    if (job->d->resourceHandle) {
+        client = job->d->resourceHandle->client();
+        if (!client)
+            return;
+    } else if (!job->d->connector) {
+        return;
+    }
+
+    if (job->d->redirected)
+        return; // don't emit the "Document has moved here" type of HTML
+
+    DEBUG() << "receivedData" << job->d->request.url.path();
+    if (client)
+        client->didReceiveData(job->d->resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
+    if (job->d->connector)
+        emit job->d->connector->data(job, data);
+
+}
+
+void QWebNetworkManager::finished(QWebNetworkJob *job, int errorCode)
+{
+    ResourceHandleClient* client = 0;
+    if (job->d->resourceHandle) {
+        client = job->d->resourceHandle->client();
+        if (!client)
+            return;
+    } else if (!job->d->connector) {
+        job->deref();
+        return;
+    }
+
+    DEBUG() << "receivedFinished" << errorCode << job->url();
+
+    if (job->d->redirected) {
+        job->d->redirected = false;
+        job->d->interface->addJob(job);
+        return;
+    }
+
+    if (job->d->resourceHandle)
+        job->d->resourceHandle->getInternal()->m_job = 0;
+
+    if (client) {
+        if (errorCode) {
+            //FIXME: error setting error was removed from ResourceHandle
+            client->didFail(job->d->resourceHandle,
+                            ResourceError(job->d->request.url.host(), job->d->response.statusCode(),
+                                          job->d->request.url.toString(), String()));
+        } else {
+            client->didFinishLoading(job->d->resourceHandle);
+        }
+    }
+
+    if (job->d->connector)
+        emit job->d->connector->finished(job, errorCode);
+
+    DEBUG() << "receivedFinished done" << job->d->request.url;
+
+    job->deref();
+}
+
+void QWebNetworkManager::addHttpJob(QWebNetworkJob *job)
+{
+    HostInfo hostInfo(job->url());
+    WebCoreHttp *httpConnection = m_hostMapping.value(hostInfo);
+    if (!httpConnection) {
+        // #### fix custom ports
+        DEBUG() << "   new connection to" << hostInfo.host << hostInfo.port;
+        httpConnection = new WebCoreHttp(this, hostInfo);
+        QObject::connect(httpConnection, SIGNAL(connectionClosed(const WebCore::HostInfo&)),
+                         this, SLOT(httpConnectionClosed(const WebCore::HostInfo&)));
+
+        m_hostMapping[hostInfo] = httpConnection;
+    }
+    httpConnection->request(job);
+}
+
+void QWebNetworkManager::cancelHttpJob(QWebNetworkJob *job)
+{
+    WebCoreHttp *httpConnection = m_hostMapping.value(job->url());
+    if (httpConnection)
+        httpConnection->cancel(job);
+}
+
+void QWebNetworkManager::httpConnectionClosed(const WebCore::HostInfo &info)
+{
+    WebCoreHttp *connection = m_hostMapping.take(info);
+    connection->deleteLater();
+}
+
+void QWebNetworkInterfacePrivate::sendFileData(QWebNetworkJob* job, int statusCode, const QByteArray &data)
+{
+    int error = statusCode >= 400 ? 1 : 0;
+    if (!job->cancelled()) {
+        QHttpResponseHeader response;
+        response.setStatusLine(statusCode);
+        response.setContentLength(data.length());
+        job->setResponse(response);
+        emit q->started(job);
+        if (!data.isEmpty())
+            emit q->data(job, data);
+    }
+    emit q->finished(job, error);
+}
+
+void QWebNetworkInterfacePrivate::parseDataUrl(QWebNetworkJob* job)
+{
+    QByteArray data = job->url().toString().toLatin1();
+    //qDebug() << "handling data url:" << data;
+
+    ASSERT(data.startsWith("data:"));
+
+    // Here's the syntax of data URLs:
+    // dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
+    // mediatype  := [ type "/" subtype ] *( ";" parameter )
+    // data       := *urlchar
+    // parameter  := attribute "=" value
+    QByteArray header;
+    bool base64 = false;
+
+    int index = data.indexOf(',');
+    if (index != -1) {
+        header = data.mid(5, index - 5).toLower();
+        //qDebug() << "header=" << header;
+        data = data.mid(index+1);
+        //qDebug() << "data=" << data;
+
+        if (header.endsWith(";base64")) {
+            //qDebug() << "base64";
+            base64 = true;
+            header = header.left(header.length() - 7);
+            //qDebug() << "mime=" << header;
+        }
+    } else {
+        data = QByteArray();
+    }
+    data = QUrl::fromPercentEncoding(data).toLatin1();
+    if (base64) {
+        data = QByteArray::fromBase64(data);
+    }
+
+    if (header.isEmpty())
+        header = "text/plain;charset=US-ASCII";
+    int statusCode = 200;
+    QHttpResponseHeader response;
+    response.setContentType(header);
+    response.setContentLength(data.size());
+    job->setResponse(response);
+
+    int error = statusCode >= 400 ? 1 : 0;
+    emit q->started(job);
+    if (!data.isEmpty())
+        emit q->data(job, data);
+    emit q->finished(job, error);
+}
+
+/*!
+  \class QWebNetworkInterface
+
+  The QWebNetworkInterface class provides an abstraction layer for
+  WebKit's network interface.  It allows to completely replace or
+  extend the builtin network layer.
+
+  QWebNetworkInterface contains two virtual methods, addJob and
+  cancelJob that have to be reimplemented when implementing your own
+  networking layer.
+
+  QWebNetworkInterface can by default handle the http, https, file and
+  data URI protocols.
+
+*/
+
+static bool gRoutineAdded = false;
+
+static void gCleanupInterface()
+{
+    delete default_interface;
+    default_interface = 0;
+}
+
+/*!
+  Sets a new default interface that will be used by all of WebKit
+  for downloading data from the internet.
+*/
+void QWebNetworkInterface::setDefaultInterface(QWebNetworkInterface *defaultInterface)
+{
+    if (default_interface == defaultInterface)
+        return;
+    if (default_interface)
+        delete default_interface;
+    default_interface = defaultInterface;
+    if (!gRoutineAdded) {
+        qAddPostRoutine(gCleanupInterface);
+        gRoutineAdded = true;
+    }
+}
+
+/*!
+  Returns the default interface that will be used by WebKit. If no
+  default interface has been set, QtWebkit will create an instance of
+  QWebNetworkInterface to do the work.
+*/
+QWebNetworkInterface *QWebNetworkInterface::defaultInterface()
+{
+    if (!default_interface) {
+        setDefaultInterface(new QWebNetworkInterface);
+    }
+    return default_interface;
+}
+
+
+/*!
+  Constructs a QWebNetworkInterface object.
+*/
+QWebNetworkInterface::QWebNetworkInterface(QObject *parent)
+    : QObject(parent)
+{
+    d = new QWebNetworkInterfacePrivate;
+    d->q = this;
+
+    if (!manager)
+        manager = new QWebNetworkManager;
+
+    QObject::connect(this, SIGNAL(started(QWebNetworkJob*)),
+                     manager, SLOT(started(QWebNetworkJob*)), Qt::QueuedConnection);
+    QObject::connect(this, SIGNAL(data(QWebNetworkJob*, const QByteArray &)),
+                     manager, SLOT(data(QWebNetworkJob*, const QByteArray &)), Qt::QueuedConnection);
+    QObject::connect(this, SIGNAL(finished(QWebNetworkJob*, int)),
+                     manager, SLOT(finished(QWebNetworkJob*, int)), Qt::QueuedConnection);
+}
+
+/*!
+  Destructs the QWebNetworkInterface object.
+*/
+QWebNetworkInterface::~QWebNetworkInterface()
+{
+    delete d;
+}
+
+/*!
+  This virtual method gets called whenever QtWebkit needs to add a
+  new job to download.
+
+  The QWebNetworkInterface should process this job, by first emitting
+  the started signal, then emitting data repeatedly as new data for
+  the Job is available, and finally ending the job with emitting a
+  finished signal.
+
+  After the finished signal has been emitted, the QWebNetworkInterface
+  is not allowed to access the job anymore.
+*/
+void QWebNetworkInterface::addJob(QWebNetworkJob *job)
+{
+    QString protocol = job->url().scheme();
+    if (protocol == QLatin1String("http") || protocol == QLatin1String("https")) {
+        QWebNetworkManager::self()->addHttpJob(job);
+        return;
+    }
+
+    // "file", "data" and all unhandled stuff go through here
+    //DEBUG() << "fileRequest";
+    DEBUG() << "FileLoader::request" << job->url();
+
+    if (job->cancelled()) {
+        d->sendFileData(job, 400, QByteArray());
+        return;
+    }
+
+    QUrl url = job->url();
+    if (protocol == QLatin1String("data")) {
+        d->parseDataUrl(job);
+        return;
+    }
+
+    int statusCode = 200;
+    QByteArray data;
+    QString path = url.path();
+    if (protocol == QLatin1String("qrc")) {
+        protocol = "file";
+        path.prepend(QLatin1Char(':'));
+    }
+
+    if (!(protocol.isEmpty() || protocol == QLatin1String("file"))) {
+        statusCode = 404;
+    } else {
+        // we simply ignore post data here.
+        QFile f(path);
+        DEBUG() << "opening" << QString(url.path());
+
+        if (f.open(QIODevice::ReadOnly)) {
+            QHttpResponseHeader response;
+            response.setStatusLine(200);
+            job->setResponse(response);
+            data = f.readAll();
+        } else {
+            statusCode = 404;
+        }
+    }
+    d->sendFileData(job, statusCode, data);
+}
+
+/*!
+  This virtual method gets called whenever QtWebkit needs to cancel a
+  new job.
+
+  The QWebNetworkInterface acknowledge the canceling of the job, by
+  emitting the finished signal with an error code of 1. After emitting
+  the finished signal, the interface should not access the job
+  anymore.
+*/
+void QWebNetworkInterface::cancelJob(QWebNetworkJob *job)
+{
+    QString protocol = job->url().scheme();
+    if (protocol == QLatin1String("http") || protocol == QLatin1String("https"))
+        QWebNetworkManager::self()->cancelHttpJob(job);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+WebCoreHttp::WebCoreHttp(QObject* parent, const HostInfo &hi)
+    : QObject(parent), info(hi),
+      m_inCancel(false)
+{
+    for (int i = 0; i < 2; ++i) {
+        connection[i].http = new QHttp(info.host, (hi.protocol == QLatin1String("https")) ? QHttp::ConnectionModeHttps : QHttp::ConnectionModeHttp, info.port);
+        connect(connection[i].http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
+                this, SLOT(onResponseHeaderReceived(const QHttpResponseHeader&)));
+        connect(connection[i].http, SIGNAL(readyRead(const QHttpResponseHeader&)),
+                this, SLOT(onReadyRead()));
+        connect(connection[i].http, SIGNAL(requestFinished(int, bool)),
+                this, SLOT(onRequestFinished(int, bool)));
+        connect(connection[i].http, SIGNAL(done(bool)),
+                this, SLOT(onDone(bool)));
+        connect(connection[i].http, SIGNAL(stateChanged(int)),
+                this, SLOT(onStateChanged(int)));
+        connect(connection[i].http, SIGNAL(authenticationRequired(const QString&, quint16, QAuthenticator*)),
+                this, SLOT(onAuthenticationRequired(const QString&, quint16, QAuthenticator*)));
+        connect(connection[i].http, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
+                this, SLOT(onProxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)));
+        connect(connection[i].http, SIGNAL(sslErrors(const QList<QSslError>&)),
+                this, SLOT(onSslErrors(const QList<QSslError>&)));
+    }
+}
+
+WebCoreHttp::~WebCoreHttp()
+{
+    connection[0].http->deleteLater();
+    connection[1].http->deleteLater();
+}
+
+void WebCoreHttp::request(QWebNetworkJob *job)
+{
+    m_pendingRequests.append(job);
+    scheduleNextRequest();
+}
+
+void WebCoreHttp::scheduleNextRequest()
+{
+    int c = 0;
+    for (; c < 2; ++c) {
+        if (!connection[c].current)
+            break;
+    }
+    if (c >= 2)
+        return;
+
+    QWebNetworkJob *job = 0;
+    while (!job && !m_pendingRequests.isEmpty()) {
+        job = m_pendingRequests.takeFirst();
+        if (job->cancelled()) {
+            emit job->networkInterface()->finished(job, 1);
+            job = 0;
+        }
+    }
+    if (!job)
+        return;
+
+    QHttp *http = connection[c].http;
+
+    connection[c].current = job;
+    connection[c].id = -1;
+#ifndef QT_NO_NETWORKPROXY
+    int proxyId = http->setProxy(job->frame()->page()->networkProxy());
+#endif
+
+    QByteArray postData = job->postData();
+    if (!postData.isEmpty())
+        connection[c].id = http->request(job->httpHeader(), postData);
+    else
+        connection[c].id = http->request(job->httpHeader());
+
+    DEBUG() << "WebCoreHttp::scheduleNextRequest: using connection" << c;
+    DEBUG() << job->httpHeader().toString();
+//     DEBUG() << job->request.toString();
+}
+
+int WebCoreHttp::getConnection()
+{
+    QObject *o = sender();
+    int c;
+    if (o == connection[0].http) {
+        c = 0;
+    } else {
+        Q_ASSERT(o == connection[1].http);
+        c = 1;
+    }
+    Q_ASSERT(connection[c].current);
+    return c;
+}
+
+void WebCoreHttp::onResponseHeaderReceived(const QHttpResponseHeader &resp)
+{
+    QHttp *http = qobject_cast<QHttp*>(sender());
+    if (http->currentId() == 0) {
+        qDebug() << "ERROR!  Invalid job id.  Why?"; // foxnews.com triggers this
+        return;
+    }
+    int c = getConnection();
+    QWebNetworkJob *job = connection[c].current;
+    DEBUG() << "WebCoreHttp::slotResponseHeaderReceived connection=" << c;
+    DEBUG() << resp.toString();
+
+    job->setResponse(resp);
+
+    emit job->networkInterface()->started(job);
+}
+
+void WebCoreHttp::onReadyRead()
+{
+    QHttp *http = qobject_cast<QHttp*>(sender());
+    if (http->currentId() == 0) {
+        qDebug() << "ERROR!  Invalid job id.  Why?"; // foxnews.com triggers this
+        return;
+    }
+    int c = getConnection();
+    QWebNetworkJob *req = connection[c].current;
+    Q_ASSERT(http == connection[c].http);
+    //DEBUG() << "WebCoreHttp::slotReadyRead connection=" << c;
+
+    QByteArray data;
+    data.resize(http->bytesAvailable());
+    http->read(data.data(), data.length());
+    emit req->networkInterface()->data(req, data);
+}
+
+void WebCoreHttp::onRequestFinished(int id, bool error)
+{
+    int c = getConnection();
+    if (connection[c].id != id) {
+        return;
+    }
+
+    QWebNetworkJob *req = connection[c].current;
+    if (!req) {
+        scheduleNextRequest();
+        return;
+    }
+
+    QHttp *http = connection[c].http;
+    DEBUG() << "WebCoreHttp::slotFinished connection=" << c << error << req;
+    if (error)
+        DEBUG() << "   error: " << http->errorString();
+
+    if (!error && http->bytesAvailable()) {
+        QByteArray data;
+        data.resize(http->bytesAvailable());
+        http->read(data.data(), data.length());
+        emit req->networkInterface()->data(req, data);
+    }
+    emit req->networkInterface()->finished(req, error ? 1 : 0);
+    connection[c].current = 0;
+    connection[c].id = -1;
+    scheduleNextRequest();
+}
+
+void WebCoreHttp::onDone(bool error)
+{
+    DEBUG() << "WebCoreHttp::onDone" << error;
+}
+
+void WebCoreHttp::onStateChanged(int state)
+{
+    DEBUG() << "State changed to" << state << "and connections are" << connection[0].current << connection[1].current;
+    if (state == QHttp::Closing || state == QHttp::Unconnected) {
+        if (!m_inCancel && m_pendingRequests.isEmpty()
+            && !connection[0].current && !connection[1].current)
+            emit connectionClosed(info);
+    }
+}
+
+void WebCoreHttp::cancel(QWebNetworkJob* request)
+{
+    bool doEmit = true;
+    m_inCancel = true;
+    for (int i = 0; i < 2; ++i) {
+        if (request == connection[i].current) {
+            connection[i].http->abort();
+            doEmit = false;
+        }
+    }
+    if (!m_pendingRequests.removeAll(request))
+        doEmit = false;
+    m_inCancel = false;
+
+    if (doEmit)
+        emit request->networkInterface()->finished(request, 1);
+
+    if (m_pendingRequests.isEmpty()
+        && !connection[0].current && !connection[1].current)
+        emit connectionClosed(info);
+}
+
+void WebCoreHttp::onSslErrors(const QList<QSslError>& errors)
+{
+    int c = getConnection();
+    QWebNetworkJob *req = connection[c].current;
+    if (req) {
+        bool continueAnyway = false;
+        emit req->networkInterface()->sslErrors(req->frame(), req->url(), errors, &continueAnyway);
+#ifndef QT_NO_OPENSSL
+        if (continueAnyway)
+            connection[c].http->ignoreSslErrors();
+#endif
+    }
+}
+
+void WebCoreHttp::onAuthenticationRequired(const QString& hostname, quint16 port, QAuthenticator *auth)
+{
+    int c = getConnection();
+    QWebNetworkJob *req = connection[c].current;
+    if (req) {
+        emit req->networkInterface()->authenticate(req->frame(), req->url(), hostname, port, auth);
+        if (auth->isNull())
+            connection[c].http->abort();
+    }
+}
+
+void WebCoreHttp::onProxyAuthenticationRequired(const QNetworkProxy& proxy, QAuthenticator *auth)
+{
+    int c = getConnection();
+    QWebNetworkJob *req = connection[c].current;
+    if (req) {
+        emit req->networkInterface()->authenticateProxy(req->frame(), req->url(), proxy, auth);
+        if (auth->isNull())
+            connection[c].http->abort();
+    }
+}
+
+HostInfo::HostInfo(const QUrl& url)
+    : protocol(url.scheme())
+    , host(url.host())
+    , port(url.port())
+{
+    if (port < 0) {
+        if (protocol == QLatin1String("http"))
+            port = 80;
+        else if (protocol == QLatin1String("https"))
+            port = 443;
+    }
+}
+