qwebnetworkinterface.cpp   [plain text]


/*
    Copyright (C) 2006 Enrico Ros <enrico.ros@m31engineering.it>
    Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
    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.
*/

#include "config.h"
#include <qglobal.h>
#if QT_VERSION < 0x040400
#include "qwebframe.h"
#include "qwebnetworkinterface.h"
#include "qwebnetworkinterface_p.h"
#include "qwebpage.h"
#include "qcookiejar.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"

#if 0
#define DEBUG qDebug
#else
#define DEBUG if (1) {} else qDebug
#endif

static QWebNetworkInterface *s_default_interface = 0;
static QWebNetworkManager *s_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;
}

enum ParserState {
    State_Begin,
    State_FirstChar,
    State_SecondChar
};

/*
 * Decode URLs without doing any charset conversion.
 *
 * Most simple approach to do it without any lookahead.
 */
static QByteArray decodePercentEncoding(const QByteArray& input)
{
    int actualLength = 0;
    QByteArray tmpVal;
    QByteArray output;
    ParserState state = State_Begin;

    output.resize(input.length());
    tmpVal.resize(2);

    for (int i = 0; i < input.length(); ++i)
        if (state == State_Begin) {
            if (input.at(i) == '%') {
                state = State_FirstChar;
            } else {
                output[actualLength++] = input[i];
            }
        } else if (state == State_FirstChar) {
            state = State_SecondChar;
            tmpVal[0] = input[i];
        } else if (state == State_SecondChar) {
            state = State_Begin;
            tmpVal[1] = input[i];
            output[actualLength++] = tmpVal.toShort(0, 16);
        }

    output.resize(actualLength);
    return output;
}

void QWebNetworkRequestPrivate::init(const WebCore::ResourceRequest &resourceRequest)
{
    KURL url = resourceRequest.url();
    QUrl qurl = QString(url.string());
    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("Connection"), QLatin1String("Keep-Alive"));
    setURL(url);

    if (resourceRequest) {
        httpHeader.setValue(QLatin1String("User-Agent"), resourceRequest->httpUserAgent());
        const QString scheme = url.scheme().toLower();
        if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
            QString cookies = QCookieJar::cookieJar()->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") {
            Vector<char> data;
            resourceRequest->httpBody()->flatten(data);
            postData = QByteArray(data.data(), data.size());
            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
  \internal

  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;
}

/*!
  \internal
  The requested URL
*/
QUrl QWebNetworkRequest::url() const
{
    return d->url;
}

/*!
   \internal
   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);
}

/*!
   \internal
   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);
}

/*!
    \internal
    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
  \internal

  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)
{
}

/*!
  \internal
*/
QWebNetworkJob::~QWebNetworkJob()
{
    delete d;
    d = 0;
}

/*!
  \internal
  The requested URL
*/
QUrl QWebNetworkJob::url() const
{
    return d->request.url;
}

/*!
  \internal
  Post data associated with the job
*/
QByteArray QWebNetworkJob::postData() const
{
    return d->request.postData;
}

/*!
  \internal
  The HTTP request header that should be used to download the job.
*/
QHttpRequestHeader QWebNetworkJob::httpHeader() const
{
    return d->request.httpHeader;
}

/*!
  \internal
  The complete network request that should be used to download the job.
*/
QWebNetworkRequest QWebNetworkJob::request() const
{
    return QWebNetworkRequest(d->request);
}

/*!
  \internal
  The HTTP response header received from the network.
*/
QHttpResponseHeader QWebNetworkJob::response() const
{
    return d->response;
}

/*!
  \internal
  The last error of the Job.
*/
QString QWebNetworkJob::errorString() const
{
    return d->errorString;
}

/*!
  \internal
  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;
}

void QWebNetworkJob::setErrorString(const QString& errorString)
{
    d->errorString = errorString;
}

/*!
  \internal
  returns true if the job has been cancelled by the WebKit framework
*/
bool QWebNetworkJob::cancelled() const
{
    return !d->resourceHandle;
}

/*!
  \internal
  reference the job.
*/
void QWebNetworkJob::ref()
{
    ++d->ref;
}

/*!
  \internal
  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;
}

/*!
   \internal
   Returns the network interface that is associated with this job.
*/
QWebNetworkInterface *QWebNetworkJob::networkInterface() const
{
    return d->interface;
}

/*!
   \internal
   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;
}

QWebNetworkJob::JobStatus QWebNetworkJob::status() const
{
    return d->jobStatus;
}

void QWebNetworkJob::setStatus(const JobStatus& status)
{
    d->jobStatus = status;
}

/*!
  \class QWebNetworkManager
  \internal
*/
QWebNetworkManager::QWebNetworkManager()
    : QObject(0)
    , m_scheduledWork(false)
{
    connect(this, SIGNAL(scheduleWork()), SLOT(doWork()), Qt::QueuedConnection);
}

QWebNetworkManager *QWebNetworkManager::self()
{
    // ensure everything's constructed and connected
    QWebNetworkInterface::defaultInterface();

    return s_manager;
}

bool QWebNetworkManager::add(ResourceHandle *handle, QWebNetworkInterface *interface, JobMode jobMode)
{
    if (!interface)
        interface = s_default_interface;

    ASSERT(interface);

    QWebNetworkJob *job = new QWebNetworkJob();
    handle->getInternal()->m_job = job;
    job->d->resourceHandle = handle;
    job->d->interface = interface;

    job->d->request.init(handle->request());

    const QString method = handle->getInternal()->m_request.httpMethod();
    if (method != "POST" && method != "GET" && method != "HEAD") {
        qWarning("REQUEST: [%s]\n", qPrintable(job->d->request.httpHeader.toString()));
        return false;
    }

    DEBUG() << "QWebNetworkManager::add:" <<  job->d->request.httpHeader.toString();

    if (jobMode == SynchronousJob) {
        Q_ASSERT(!m_synchronousJobs.contains(job));
        m_synchronousJobs[job] = 1;
    }

    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->interface->cancelJob(job);
    handle->getInternal()->m_job = 0;
}

/*!
  \internal
*/
void QWebNetworkManager::started(QWebNetworkJob *job)
{
    Q_ASSERT(job->d);
    Q_ASSERT(job->status() == QWebNetworkJob::JobCreated ||
             job->status() == QWebNetworkJob::JobRecreated);

    job->setStatus(QWebNetworkJob::JobStarted);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else {
        return;
    }

    DEBUG() << "ResourceHandleManager::receivedResponse:";
    DEBUG() << job->d->response.toString();

    QStringList cookies = job->d->response.allValues("Set-Cookie");
    KURL url(job->url());
    foreach (QString c, cookies) {
        QCookieJar::cookieJar()->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();
    if (job->url().scheme() != QLatin1String("file"))
        response.setHTTPStatusCode(statusCode);
    else if (statusCode == 404)
        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));
                if (client)
                    client->willSendRequest(job->d->resourceHandle, newRequest, response);
            }

            QString method;
            if (statusCode == 302 || statusCode == 303) {
                // this is standard-correct for 303 and practically-correct (no standard-correct) for 302
                // also, it's required for Yahoo's login process for flickr.com which responds to a POST
                // with a 302 which must be GET'ed
                method = "GET";
                job->d->request.httpHeader.setContentLength(0);
            } else {
                method = job->d->request.httpHeader.method();
            }
            job->d->request.httpHeader.setRequest(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);

}

void QWebNetworkManager::data(QWebNetworkJob *job, const QByteArray &data)
{
    Q_ASSERT(job->status() == QWebNetworkJob::JobStarted ||
             job->status() == QWebNetworkJob::JobReceivingData);

    job->setStatus(QWebNetworkJob::JobReceivingData);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else {
        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*/);

}

void QWebNetworkManager::finished(QWebNetworkJob *job, int errorCode)
{
    Q_ASSERT(errorCode == 1 ||
             job->status() == QWebNetworkJob::JobStarted ||
             job->status() == QWebNetworkJob::JobReceivingData);

    if (m_synchronousJobs.contains(job))
        m_synchronousJobs.remove(job);

    job->setStatus(QWebNetworkJob::JobFinished);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else {
        job->deref();
        return;
    }

    DEBUG() << "receivedFinished" << errorCode << job->url();

    if (job->d->redirected) {
        job->d->redirected = false;
        job->setStatus(QWebNetworkJob::JobRecreated);
        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(), job->d->errorString));
        } else {
            client->didFinishLoading(job->d->resourceHandle);
        }
    }

    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);
        q->started(job);
        if (!data.isEmpty())
            q->data(job, data);
    }
    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();
    }

    if (base64) {
        data = QByteArray::fromBase64(data);
    } else {
        data = decodePercentEncoding(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;
    q->started(job);
    if (!data.isEmpty())
        q->data(job, data);
    q->finished(job, error);
}

void QWebNetworkManager::queueStart(QWebNetworkJob* job)
{
    Q_ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job));
    doScheduleWork();
}

void QWebNetworkManager::queueData(QWebNetworkJob* job, const QByteArray& data)
{
    ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job, data));
    doScheduleWork();
}

void QWebNetworkManager::queueFinished(QWebNetworkJob* job, int errorCode)
{
    Q_ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job, errorCode));
    doScheduleWork();
}

void QWebNetworkManager::doScheduleWork()
{
    if (!m_scheduledWork) {
        m_scheduledWork = true;
        emit scheduleWork();
    }
}


/*
 * We will work on a copy of m_pendingWork. While dispatching m_pendingWork
 * new work will be added that will be handled at a later doWork call. doWork
 * will be called we set m_scheduledWork to false early in this method.
 */
void QWebNetworkManager::doWork()
{
    m_queueMutex.lock();
    m_scheduledWork = false;
    bool hasSyncJobs = m_synchronousJobs.size();
    const QHash<QWebNetworkJob*, int> syncJobs = m_synchronousJobs;
    m_queueMutex.unlock();

    foreach (JobWork* work, m_pendingWork) {
        if (hasSyncJobs && !syncJobs.contains(work->job))
            continue;

        if (work->workType == JobWork::JobStarted)
            started(work->job);
        else if (work->workType == JobWork::JobData) {
            // This job was not yet started
            if (static_cast<int>(work->job->status()) < QWebNetworkJob::JobStarted)
                continue;

            data(work->job, work->data);
        } else if (work->workType == JobWork::JobFinished) {
            // This job was not yet started... we have no idea if data comes by...
            // and it won't start in case of errors
            if (static_cast<int>(work->job->status()) < QWebNetworkJob::JobStarted && work->errorCode != 1)
                continue;

            finished(work->job, work->errorCode);
        }

        m_queueMutex.lock();
        m_pendingWork.removeAll(work);
        m_queueMutex.unlock();

        work->job->deref();
        delete work;
    }

    m_queueMutex.lock();
    if (hasSyncJobs && m_synchronousJobs.size() == 0)
        doScheduleWork();
    m_queueMutex.unlock();
}

/*!
  \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 s_default_interface;
    s_default_interface = 0;
}

/*!
  \internal
  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 (s_default_interface == defaultInterface)
        return;
    if (s_default_interface)
        delete s_default_interface;
    s_default_interface = defaultInterface;
    if (!gRoutineAdded) {
        qAddPostRoutine(gCleanupInterface);
        gRoutineAdded = true;
    }
}

/*!
  \internal
  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 (!s_default_interface) {
        setDefaultInterface(new QWebNetworkInterface);
    }
    return s_default_interface;
}


/*!
  \internal
  Constructs a QWebNetworkInterface object.
*/
QWebNetworkInterface::QWebNetworkInterface(QObject *parent)
    : QObject(parent)
{
    d = new QWebNetworkInterfacePrivate;
    d->q = this;

    if (!s_manager)
        s_manager = new QWebNetworkManager;
}

/*!
  \internal
  Destructs the QWebNetworkInterface object.
*/
QWebNetworkInterface::~QWebNetworkInterface()
{
    delete d;
}

/*!
  \internal
  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;
        }
    }

    if (statusCode == 404) {
        QHttpResponseHeader response;
        response.setStatusLine(404);
        job->setResponse(response);
    }

    d->sendFileData(job, statusCode, data);
}

/*!
  \internal
  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);
}

/*!
  \internal
*/
void QWebNetworkInterface::started(QWebNetworkJob* job)
{
    Q_ASSERT(s_manager);
    s_manager->queueStart(job);
}

/*!
  \internal
*/
void QWebNetworkInterface::data(QWebNetworkJob* job, const QByteArray& data)
{
    Q_ASSERT(s_manager);
    s_manager->queueData(job, data);
}

/*!
  \internal
*/
void QWebNetworkInterface::finished(QWebNetworkJob* job, int errorCode)
{
    Q_ASSERT(s_manager);
    s_manager->queueFinished(job, errorCode);
}

/*!
  \fn void QWebNetworkInterface::sslErrors(QWebFrame *frame, const QUrl& url, const QList<QSslError>& errors, bool *continueAnyway);
  \internal

   Signal is emitted when an SSL error occurs.
*/

/*!
  \fn void QWebNetworkInterface::authenticate(QWebFrame *frame, const QUrl& url, const QString& hostname, quint16 port, QAuthenticator *auth);
  \internal

  Signal is emitted when network authentication is required.
*/

/*!
  \fn void QWebNetworkInterface::authenticateProxy(QWebFrame *frame, const QUrl& url, const QNetworkProxy& proxy, QAuthenticator *auth);
  \internal

  Signal is emitted when proxy authentication is required.
*/

/////////////////////////////////////////////////////////////////////////////
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()) {
            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);

    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 *job = 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());
    job->networkInterface()->data(job, data);
}

void WebCoreHttp::onRequestFinished(int id, bool error)
{
    int c = getConnection();
    if (connection[c].id != id) {
        return;
    }

    QWebNetworkJob *job = connection[c].current;
    if (!job) {
        scheduleNextRequest();
        return;
    }

    QHttp *http = connection[c].http;
    DEBUG() << "WebCoreHttp::slotFinished connection=" << c << error << job;
    if (error) {
        DEBUG() << "   error: " << http->errorString();
        job->setErrorString(http->errorString());
    }

    if (!error && http->bytesAvailable()) {
        QByteArray data;
        data.resize(http->bytesAvailable());
        http->read(data.data(), data.length());
        job->networkInterface()->data(job, data);
    }

    job->networkInterface()->finished(job, 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)
        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 *job = connection[c].current;
    if (job) {
        bool continueAnyway = false;
        emit job->networkInterface()->sslErrors(job->frame(), job->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 *job = connection[c].current;
    if (job) {
        emit job->networkInterface()->authenticate(job->frame(), job->url(), hostname, port, auth);
        if (auth->isNull())
            connection[c].http->abort();
    }
}

void WebCoreHttp::onProxyAuthenticationRequired(const QNetworkProxy& proxy, QAuthenticator *auth)
{
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    if (job) {
        emit job->networkInterface()->authenticateProxy(job->frame(), job->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;
    }
}

#endif