/* Copyright (C) 2006 Enrico Ros Copyright (C) 2007 Trolltech ASA Copyright (C) 2007 Staikos Computing Services Inc. 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 #include "qwebframe.h" #include "qwebnetworkinterface.h" #include "qwebnetworkinterface_p.h" #include "qwebobjectpluginconnector.h" #include "qwebpage.h" #include #include #include #include #include #include #include #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 = 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) { } /*! \internal */ QWebNetworkJob::~QWebNetworkJob() { delete d; d = 0; } /*! 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; } /*! The last error of the Job. */ QString QWebNetworkJob::errorString() const { return d->errorString; } /*! 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; } /*! 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; } 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->connector = 0; 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->connector = 0; job->d->interface->cancelJob(job); handle->getInternal()->m_job = 0; } 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 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(); 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.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) { 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 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) { 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 if (!job->d->connector) { 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); } } 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); 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 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(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(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; } /*! 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; } } /*! 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; } /*! Constructs a QWebNetworkInterface object. */ QWebNetworkInterface::QWebNetworkInterface(QObject *parent) : QObject(parent) { d = new QWebNetworkInterfacePrivate; d->q = this; if (!s_manager) s_manager = new QWebNetworkManager; } /*! 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; } } if (statusCode == 404) { QHttpResponseHeader response; response.setStatusLine(404); job->setResponse(response); } 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); } void QWebNetworkInterface::started(QWebNetworkJob* job) { Q_ASSERT(s_manager); s_manager->queueStart(job); } void QWebNetworkInterface::data(QWebNetworkJob* job, const QByteArray& data) { Q_ASSERT(s_manager); s_manager->queueData(job, data); } void QWebNetworkInterface::finished(QWebNetworkJob* job, int errorCode) { Q_ASSERT(s_manager); s_manager->queueFinished(job, errorCode); } ///////////////////////////////////////////////////////////////////////////// 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&)), this, SLOT(onSslErrors(const QList&))); } } 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(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(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& 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; } }