Rename QWebNetworkJob::request() into QWebNetworkJob::httpHeader() and added
[WebKit-https.git] / WebKitQt / Api / qwebnetworkinterface.cpp
1 /*
2   Copyright (C) 2006 Enrico Ros <enrico.ros@m31engineering.it>
3   Copyright (C) 2007 Trolltech ASA
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Library General Public
7   License as published by the Free Software Foundation; either
8   version 2 of the License, or (at your option) any later version.
9   
10   This library is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   Library General Public License for more details.
14   
15   You should have received a copy of the GNU Library General Public License
16   along with this library; see the file COPYING.LIB.  If not, write to
17   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18   Boston, MA 02111-1307, USA.
19   
20   This class provides all functionality needed for loading images, style sheets and html
21   pages from the web. It has a memory cache for these objects.
22 */
23 #include <qglobal.h>
24 #include "qwebnetworkinterface.h"
25 #include "qwebnetworkinterface_p.h"
26 #include "qwebobjectpluginconnector.h"
27 #include <qdebug.h>
28 #include <qfile.h>
29 #include <qurl.h>
30
31 #include "ResourceHandle.h"
32 #include "ResourceHandleClient.h"
33 #include "ResourceHandleInternal.h"
34 #include "MimeTypeRegistry.h"
35 #include "CookieJar.h"
36
37 #define notImplemented() qDebug("FIXME: UNIMPLEMENTED: %s:%d (%s)", __FILE__, __LINE__, __FUNCTION__)
38
39 #if 0
40 #define DEBUG qDebug
41 #else
42 #define DEBUG if (1) {} else qDebug
43 #endif
44
45 static QWebNetworkInterface *default_interface = 0;
46 static QWebNetworkManager *manager = 0;
47
48 using namespace WebCore;
49
50 uint qHash(const HostInfo &info)
51 {
52     return qHash(info.host) + info.port;
53 }
54
55 static bool operator==(const HostInfo &i1, const HostInfo &i2)
56 {
57     return i1.port == i2.port && i1.host == i2.host;
58 }
59
60 void QWebNetworkRequestPrivate::init(const WebCore::ResourceRequest &resourceRequest)
61 {
62     KURL url = resourceRequest.url();
63     QUrl qurl = QString(url.url());
64     init(resourceRequest.httpMethod(), qurl, &resourceRequest);
65 }
66
67 void QWebNetworkRequestPrivate::init(const QString &method, const QUrl &url, const WebCore::ResourceRequest *resourceRequest)
68 {
69     httpHeader = QHttpRequestHeader(method, url.toEncoded(QUrl::RemoveScheme|QUrl::RemoveAuthority));
70     httpHeader.setValue(QLatin1String("User-Agent"),
71                          QLatin1String("Mozilla/5.0 (PC; U; Intel; Linux; en) AppleWebKit/420+ (KHTML, like Gecko)"));
72     httpHeader.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
73     setURL(url);
74
75     if (resourceRequest) {
76         const QString scheme = url.scheme().toLower();
77         if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
78             QString cookies = WebCore::cookies(resourceRequest->url());
79             if (!cookies.isEmpty())
80                 httpHeader.setValue(QLatin1String("Cookie"), cookies);
81         }
82
83
84         const HTTPHeaderMap& loaderHeaders = resourceRequest->httpHeaderFields();
85         HTTPHeaderMap::const_iterator end = loaderHeaders.end();
86         for (HTTPHeaderMap::const_iterator it = loaderHeaders.begin(); it != end; ++it)
87             httpHeader.setValue(it->first, it->second);
88
89         // handle and perform a 'POST' request
90         if (method == "POST") {
91             DeprecatedString pd = resourceRequest->httpBody()->flattenToString().deprecatedString();
92             postData = QByteArray(pd.ascii(), pd.length());
93             httpHeader.setValue(QLatin1String("content-length"), QString::number(postData.size()));
94         }
95     }
96 }
97
98 void QWebNetworkRequestPrivate::setURL(const QUrl &u)
99 {
100     url = u;
101     int port = url.port();
102     if (port > 0 && port != 80)
103         httpHeader.setValue(QLatin1String("Host"), url.host() + QLatin1Char(':') + QString::number(port));
104     else
105         httpHeader.setValue(QLatin1String("Host"), url.host());
106 }
107
108 QWebNetworkRequest::QWebNetworkRequest()
109     : d(new QWebNetworkRequestPrivate)
110 {
111 }
112
113 QWebNetworkRequest::QWebNetworkRequest(const QWebNetworkRequest &other)
114     : d(new QWebNetworkRequestPrivate(*other.d))
115 {
116 }
117
118 QWebNetworkRequest &QWebNetworkRequest::operator=(const QWebNetworkRequest &other)
119 {
120     *d = *other.d;
121     return *this;
122 }
123
124 QWebNetworkRequest::QWebNetworkRequest(QWebNetworkRequestPrivate *priv)
125     : d(new QWebNetworkRequestPrivate(*priv))
126 {
127 }
128
129 QWebNetworkRequest::~QWebNetworkRequest()
130 {
131     delete d;
132 }
133
134 QUrl QWebNetworkRequest::url() const
135 {
136     return d->url;
137 }
138
139 void QWebNetworkRequest::setUrl(const QUrl &url)
140 {
141     d->setURL(url);
142 }
143
144 QHttpRequestHeader QWebNetworkRequest::httpHeader() const
145 {
146     return d->httpHeader;
147 }
148
149 void QWebNetworkRequest::setHttpHeader(const QHttpRequestHeader &header) const
150 {
151     d->httpHeader = header;
152 }
153
154 QByteArray QWebNetworkRequest::postData() const
155 {
156     return d->postData;
157 }
158
159 void QWebNetworkRequest::setPostData(const QByteArray &data)
160 {
161     d->postData = data;
162 }
163
164 /*!
165   \class QWebNetworkJob
166
167   The QWebNetworkJob class represents a network job, that needs to be
168   processed by the QWebNetworkInterface.
169
170   This class is only required when implementing a new network layer (or
171   support for a special protocol) using QWebNetworkInterface.
172
173   QWebNetworkJob objects are created and owned by the QtWebKit library.
174   Most of it's properties are read-only.
175
176   The job is reference counted. This can be used to ensure that the job doesn't
177   get deleted while it's still stored in some data structure.
178 */
179
180 /*!
181   \internal
182 */
183 QWebNetworkJob::QWebNetworkJob()
184     : d(new QWebNetworkJobPrivate)
185 {
186     d->ref = 1;
187     d->redirected = false;
188     d->interface = 0;
189 }
190
191 /*!
192   \internal
193 */
194 QWebNetworkJob::~QWebNetworkJob()
195 {
196     delete d;
197 }
198
199 /*!
200   The requested URL
201 */
202 QUrl QWebNetworkJob::url() const
203 {
204     return d->request.url;
205 }
206
207 /*!
208   Post data associated with the job
209 */
210 QByteArray QWebNetworkJob::postData() const
211 {
212     return d->request.postData;
213 }
214
215 /*!
216   The HTTP request header that should be used to download the job.
217 */
218 QHttpRequestHeader QWebNetworkJob::httpHeader() const
219 {
220     return d->request.httpHeader;
221 }
222
223 /*!
224   The complete network request that should be used to download the job.
225 */
226 QWebNetworkRequest QWebNetworkJob::request() const
227 {
228     return QWebNetworkRequest(&d->request);
229 }
230
231 /*!
232   The HTTP response header received from the network.
233 */
234 QHttpResponseHeader QWebNetworkJob::response() const
235 {
236     return d->response;
237 }
238
239 /*!
240   Sets the HTTP reponse header. The response header has to be called before
241   emitting QWebNetworkInterface::started.
242 */
243 void QWebNetworkJob::setResponse(const QHttpResponseHeader &response)
244 {
245     d->response = response;
246 }
247
248 /*!
249   returns true if the job has been cancelled by the WebKit framework
250 */
251 bool QWebNetworkJob::cancelled() const
252 {
253     return !d->resourceHandle && !d->connector;
254 }
255
256 /*!
257   reference the job.
258 */
259 void QWebNetworkJob::ref()
260 {
261     ++d->ref;
262 }
263
264 /*!
265   derefence the job.
266
267   If the reference count drops to 0 this method also deletes the job.
268
269   Returns false if the reference count has dropped to 0.
270 */
271 bool QWebNetworkJob::deref()
272 {
273     if (!--d->ref) {
274         delete this;
275         return false;
276     }
277     return true;
278 }
279
280 /*!
281    Returns the network interface that is associated with this job.
282 */
283 QWebNetworkInterface *QWebNetworkJob::networkInterface() const
284 {
285     return d->interface;
286 }
287
288 /*!
289   \class QWebNetworkManager
290   \internal
291 */
292 QWebNetworkManager::QWebNetworkManager()
293     : QObject(0)
294 {
295 }
296
297 QWebNetworkManager *QWebNetworkManager::self()
298 {
299     // ensure everything's constructed and connected
300     QWebNetworkInterface::defaultInterface();
301
302     return manager;
303 }
304
305 bool QWebNetworkManager::add(ResourceHandle *handle, QWebNetworkInterface *interface)
306 {
307     ASSERT(resource);
308
309     if (!interface)
310         interface = default_interface;
311
312     ASSERT(interface);
313
314     QWebNetworkJob *job = new QWebNetworkJob();
315     handle->getInternal()->m_job = job;
316     job->d->resourceHandle = handle;
317     job->d->interface = interface;
318     job->d->connector = 0;
319
320     job->d->request.init(handle->request());
321
322     if (handle->method() != "POST" && handle->method() != "GET") {
323         // don't know what to do! (probably a request error!!)
324         // but treat it like a 'GET' request
325         qWarning("REQUEST: [%s]\n", qPrintable(job->d->request.httpHeader.toString()));
326     }
327
328     DEBUG() << "QWebNetworkManager::add:" <<  job->d->request.httpHeader.toString();
329
330     interface->addJob(job);
331
332     return true;
333 }
334
335 void QWebNetworkManager::cancel(ResourceHandle *handle)
336 {
337     QWebNetworkJob *job = handle->getInternal()->m_job;
338     if (!job)
339         return;
340     job->d->resourceHandle = 0;
341     job->d->connector = 0;
342     job->d->interface->cancelJob(job);
343     handle->getInternal()->m_job = 0;
344 }
345
346 void QWebNetworkManager::started(QWebNetworkJob *job)
347 {
348     ResourceHandleClient* client = 0;
349     if (job->d->resourceHandle) {
350         client = job->d->resourceHandle->client();
351         if (!client)
352             return;
353     } else if (!job->d->connector) {
354         return;
355     }
356
357     DEBUG() << "ResourceHandleManager::receivedResponse:";
358     DEBUG() << job->d->response.toString();
359
360     QStringList cookies = job->d->response.allValues("Set-Cookie");
361     KURL url(job->url().toString());
362     foreach (QString c, cookies) {
363         setCookies(url, url, c);
364     }
365     QString contentType = job->d->response.value("Content-Type");
366     QString encoding;
367     int idx = contentType.indexOf(QLatin1Char(';'));
368     if (idx > 0) {
369         QString remainder = contentType.mid(idx + 1).toLower();
370         contentType = contentType.left(idx).trimmed();
371
372         idx = remainder.indexOf("charset");
373         if (idx >= 0) {
374             idx = remainder.indexOf(QLatin1Char('='), idx);
375             if (idx >= 0)
376                 encoding = remainder.mid(idx + 1).trimmed();
377         }
378     }
379     if (contentType.isEmpty()) {
380         // let's try to guess from the extension
381         QString extension = job->d->request.url.path();
382         int index = extension.lastIndexOf(QLatin1Char('.'));
383         if (index > 0) {
384             extension = extension.mid(index + 1);
385             contentType = MimeTypeRegistry::getMIMETypeForExtension(extension);
386         }
387     }
388 //     qDebug() << "Content-Type=" << contentType;
389 //     qDebug() << "Encoding=" << encoding;
390
391
392     ResourceResponse response(url, contentType,
393                               0 /* FIXME */,
394                               encoding,
395                               String() /* FIXME */);
396
397     int statusCode = job->d->response.statusCode();
398     response.setHTTPStatusCode(statusCode);
399     /* Fill in the other fields */
400
401     if (statusCode >= 300 && statusCode < 400) {
402         // we're on a redirect page! if the 'Location:' field is valid, we redirect
403         QString location = job->d->response.value("location");
404         DEBUG() << "Redirection";
405         if (!location.isEmpty()) {
406             ResourceRequest newRequest = job->d->resourceHandle->request();
407             newRequest.setURL(KURL(newRequest.url(), DeprecatedString(location)));
408             if (client)
409                 client->willSendRequest(job->d->resourceHandle, newRequest, response);
410             job->d->request.httpHeader.setRequest(job->d->request.httpHeader.method(), newRequest.url().path() + newRequest.url().query());
411             job->d->request.setURL(QString(newRequest.url().url()));
412             job->d->redirected = true;
413             return;
414         }
415     }
416
417     if (client)
418         client->didReceiveResponse(job->d->resourceHandle, response);
419     if (job->d->connector)
420         emit job->d->connector->started(job);
421     
422 }
423
424 void QWebNetworkManager::data(QWebNetworkJob *job, const QByteArray &data)
425 {
426     ResourceHandleClient* client = 0;
427     if (job->d->resourceHandle) {
428         client = job->d->resourceHandle->client();
429         if (!client)
430             return;
431     } else if (!job->d->connector) {
432         return;
433     }
434
435     if (job->d->redirected)
436         return; // don't emit the "Document has moved here" type of HTML
437
438     DEBUG() << "receivedData" << job->d->request.url.path();
439     if (client)
440         client->didReceiveData(job->d->resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
441     if (job->d->connector)
442         emit job->d->connector->data(job, data);
443     
444 }
445
446 void QWebNetworkManager::finished(QWebNetworkJob *job, int errorCode)
447 {
448     ResourceHandleClient* client = 0;
449     if (job->d->resourceHandle) {
450         client = job->d->resourceHandle->client();
451         if (!client)
452             return;
453     } else if (!job->d->connector) {
454         job->deref();
455         return;
456     }
457
458     DEBUG() << "receivedFinished" << errorCode << job->url();
459
460     if (job->d->redirected) {
461         job->d->redirected = false;
462         job->d->interface->addJob(job);
463         return;
464     }
465     
466     if (job->d->resourceHandle)
467         job->d->resourceHandle->getInternal()->m_job = 0;
468
469     if (client) {
470         if (errorCode) {
471             //FIXME: error setting error was removed from ResourceHandle
472             client->didFail(job->d->resourceHandle,
473                             ResourceError(job->d->request.url.host(), job->d->response.statusCode(),
474                                           job->d->request.url.toString(), String()));
475         } else {
476             client->didFinishLoading(job->d->resourceHandle);
477         }
478     }
479
480     if (job->d->connector)
481         emit job->d->connector->finished(job, errorCode);
482     
483     DEBUG() << "receivedFinished done" << job->d->request.url;
484
485     job->deref();
486 }
487
488 void QWebNetworkManager::addHttpJob(QWebNetworkJob *job)
489 {
490     HostInfo hostInfo(job->url());
491     WebCoreHttp *httpConnection = m_hostMapping.value(hostInfo);
492     if (!httpConnection) {
493         // #### fix custom ports
494         DEBUG() << "   new connection to" << hostInfo.host << hostInfo.port;
495         httpConnection = new WebCoreHttp(this, hostInfo);
496         QObject::connect(httpConnection, SIGNAL(connectionClosed(const WebCore::HostInfo&)),
497                          this, SLOT(httpConnectionClosed(const WebCore::HostInfo&)));
498
499         m_hostMapping[hostInfo] = httpConnection;
500     }
501     httpConnection->request(job);
502 }
503
504 void QWebNetworkManager::cancelHttpJob(QWebNetworkJob *job)
505 {
506     WebCoreHttp *httpConnection = m_hostMapping.value(job->url());
507     if (httpConnection)
508         httpConnection->cancel(job);
509 }
510
511 void QWebNetworkManager::httpConnectionClosed(const WebCore::HostInfo &info)
512 {
513     WebCoreHttp *connection = m_hostMapping.take(info);
514     delete connection;
515 }
516
517 void QWebNetworkInterfacePrivate::sendFileData(QWebNetworkJob* job, int statusCode, const QByteArray &data)
518 {
519     int error = statusCode >= 400 ? 1 : 0;
520     if (!job->cancelled()) {
521         QHttpResponseHeader response;
522         response.setStatusLine(statusCode);
523         job->setResponse(response);
524         emit q->started(job);
525         if (!data.isEmpty())
526             emit q->data(job, data);
527     }
528     emit q->finished(job, error);
529 }
530
531 void QWebNetworkInterfacePrivate::parseDataUrl(QWebNetworkJob* job)
532 {
533     QByteArray data = job->url().toString().toLatin1();
534     //qDebug() << "handling data url:" << data; 
535
536     ASSERT(data.startsWith("data:"));
537
538     // Here's the syntax of data URLs:
539     // dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
540     // mediatype  := [ type "/" subtype ] *( ";" parameter )
541     // data       := *urlchar
542     // parameter  := attribute "=" value
543     QByteArray header;
544     bool base64 = false;
545
546     int index = data.indexOf(',');
547     if (index != -1) {
548         header = data.mid(5, index - 5);
549         header = header.toLower();
550         //qDebug() << "header=" << header;
551         data = data.mid(index+1);
552         //qDebug() << "data=" << data;
553
554         if (header.endsWith(";base64")) {
555             //qDebug() << "base64";
556             base64 = true;
557             header = header.left(header.length() - 7);
558             //qDebug() << "mime=" << header;
559         }        
560     } else {
561         data = QByteArray();
562     }
563     if (base64) {
564         data = QByteArray::fromBase64(data);
565     } else {
566         data = QUrl::fromPercentEncoding(data).toLatin1();
567     }
568
569     if (header.isEmpty()) 
570         header = "text/plain;charset=US-ASCII";
571     int statusCode = data.isEmpty() ? 404 : 200;
572     QHttpResponseHeader response;
573     response.setContentType(header);
574     response.setContentLength(data.size());
575     job->setResponse(response);
576
577     sendFileData(job, statusCode, data);
578 }
579
580 /*!
581   \class QWebNetworkInterface
582
583   The QWebNetworkInterface class provides an abstraction layer for
584   WebKit's network interface.  It allows to completely replace or
585   extend the builtin network layer.
586
587   QWebNetworkInterface contains two virtual methods, addJob and
588   cancelJob that have to be reimplemented when implementing your own
589   networking layer.
590
591   QWebNetworkInterface can by default handle the http, https, file and
592   data URI protocols.
593   
594 */
595
596 /*!
597   Sets a new default interface that will be used by all of WebKit
598   for downloading data from the internet.
599 */
600 void QWebNetworkInterface::setDefaultInterface(QWebNetworkInterface *defaultInterface)
601 {
602     if (default_interface == defaultInterface)
603         return;
604     if (default_interface)
605         delete default_interface;
606     default_interface = defaultInterface;
607 }
608
609 /*!
610   Returns the default interface that will be used by WebKit. If no
611   default interface has been set, QtWebkit will create an instance of
612   QWebNetworkInterface to do the work.
613 */
614 QWebNetworkInterface *QWebNetworkInterface::defaultInterface()
615 {
616     if (!default_interface)
617         setDefaultInterface(new QWebNetworkInterface);
618     return default_interface;
619 }
620
621
622 /*!
623   Constructs a QWebNetworkInterface object.
624 */
625 QWebNetworkInterface::QWebNetworkInterface(QObject *parent)
626     : QObject(parent)
627 {
628     d = new QWebNetworkInterfacePrivate;
629     d->q = this;
630
631     if (!manager)
632         manager = new QWebNetworkManager;
633
634     QObject::connect(this, SIGNAL(started(QWebNetworkJob*)),
635                      manager, SLOT(started(QWebNetworkJob*)), Qt::QueuedConnection);
636     QObject::connect(this, SIGNAL(data(QWebNetworkJob*, const QByteArray &)),
637                      manager, SLOT(data(QWebNetworkJob*, const QByteArray &)), Qt::QueuedConnection);
638     QObject::connect(this, SIGNAL(finished(QWebNetworkJob*, int)),
639                      manager, SLOT(finished(QWebNetworkJob*, int)), Qt::QueuedConnection);
640 }
641
642 /*!
643   Destructs the QWebNetworkInterface object.
644 */
645 QWebNetworkInterface::~QWebNetworkInterface()
646 {
647     delete d;
648 }
649
650 /*!
651   This virtual method gets called whenever QtWebkit needs to add a
652   new job to download.
653
654   The QWebNetworkInterface should process this job, by first emitting
655   the started signal, then emitting data repeatedly as new data for
656   the Job is available, and finally ending the job with emitting a
657   finished signal.
658
659   After the finished signal has been emitted, the QWebNetworkInterface
660   is not allowed to access the job anymore.
661 */
662 void QWebNetworkInterface::addJob(QWebNetworkJob *job)
663 {
664     QString protocol = job->url().scheme();
665     if (protocol == QLatin1String("http")) {
666         QWebNetworkManager::self()->addHttpJob(job);
667         return;
668     }
669
670     // "file", "data" and all unhandled stuff go through here
671     //DEBUG() << "fileRequest";
672     DEBUG() << "FileLoader::request" << job->url();
673
674     if (job->cancelled()) {
675         d->sendFileData(job, 400, QByteArray());
676         return;
677     }
678
679     QUrl url = job->url();
680     if (protocol == QLatin1String("data")) {
681         d->parseDataUrl(job);
682         return;
683     }
684
685     int statusCode = 200;
686     QByteArray data;
687     if (!(protocol.isEmpty() || protocol == QLatin1String("file"))) {
688         statusCode = 404;
689     } else if (job->postData().isEmpty()) {
690         QFile f(url.path());
691         DEBUG() << "opening" << QString(url.path());
692
693         if (f.open(QIODevice::ReadOnly)) {
694             QHttpResponseHeader response;
695             response.setStatusLine(200);
696             job->setResponse(response);
697             data = f.readAll();
698         } else {
699             statusCode = 404;
700         }
701     } else {
702         statusCode = 404;
703     }
704     d->sendFileData(job, statusCode, data);
705 }
706
707 /*!
708   This virtual method gets called whenever QtWebkit needs to cancel a
709   new job.
710
711   The QWebNetworkInterface acknowledge the canceling of the job, by
712   emitting the finished signal with an error code of 1. After emitting
713   the finished signal, the interface should not access the job
714   anymore.
715 */
716 void QWebNetworkInterface::cancelJob(QWebNetworkJob *job)
717 {
718     QString protocol = job->url().scheme();
719     if (protocol == QLatin1String("http"))
720         QWebNetworkManager::self()->cancelHttpJob(job);
721 }
722
723 /////////////////////////////////////////////////////////////////////////////
724 WebCoreHttp::WebCoreHttp(QObject* parent, const HostInfo &hi)
725     : QObject(parent), info(hi),
726       m_inCancel(false)
727 {
728     for (int i = 0; i < 2; ++i) {
729         connection[i].http = new QHttp(info.host, info.port);
730         connection[i].current = 0;
731         connect(connection[i].http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
732                 this, SLOT(onResponseHeaderReceived(const QHttpResponseHeader&)));
733         connect(connection[i].http, SIGNAL(readyRead(const QHttpResponseHeader&)),
734                 this, SLOT(onReadyRead()));
735         connect(connection[i].http, SIGNAL(requestFinished(int, bool)),
736                 this, SLOT(onRequestFinished(int, bool)));
737         connect(connection[i].http, SIGNAL(stateChanged(int)),
738                 this, SLOT(onStateChanged(int)));
739     }
740 }
741
742 WebCoreHttp::~WebCoreHttp()
743 {
744 }
745
746 void WebCoreHttp::request(QWebNetworkJob *job)
747 {
748     DEBUG() << ">>>>>>>>>>>>>> WebCoreHttp::request";
749     DEBUG() << job->httpHeader().toString() << "\n";
750     m_pendingRequests.append(job);
751
752     scheduleNextRequest();
753 }
754
755 void WebCoreHttp::scheduleNextRequest()
756 {
757     int c = 0;
758     for (; c < 2; ++c) {
759         if (!connection[c].current)
760             break;
761     }
762     if (c >= 2)
763         return;
764
765     QWebNetworkJob *job = 0;
766     while (!job && !m_pendingRequests.isEmpty()) {
767         job = m_pendingRequests.takeFirst();
768         if (job->cancelled()) {
769             emit job->networkInterface()->finished(job, 1);
770             job = 0;
771         }
772     }
773     if (!job)
774         return;
775     
776     QHttp *http = connection[c].http;
777     QByteArray postData = job->postData();
778     if (!postData.isEmpty())
779         http->request(job->httpHeader(), postData);
780     else
781         http->request(job->httpHeader());
782     connection[c].current = job;
783
784     DEBUG() << "WebCoreHttp::scheduleNextRequest: using connection" << c;
785 //     DEBUG() << job->request.toString();
786 }
787
788 int WebCoreHttp::getConnection()
789 {
790     QObject *o = sender();
791     int c;
792     if (o == connection[0].http) {
793         c = 0;
794     } else {
795         Q_ASSERT(o == connection[1].http);
796         c = 1;
797     }
798     //Q_ASSERT(connection[c].current);
799     return c;
800 }
801
802 void WebCoreHttp::onResponseHeaderReceived(const QHttpResponseHeader &resp)
803 {
804     int c = getConnection();
805     QWebNetworkJob *job = connection[c].current;
806     DEBUG() << "WebCoreHttp::slotResponseHeaderReceived connection=" << c;
807
808     job->setResponse(resp);
809
810     emit job->networkInterface()->started(job);
811 }
812
813 void WebCoreHttp::onReadyRead()
814 {
815     int c = getConnection();
816     QWebNetworkJob *req = connection[c].current;
817     QHttp *http = connection[c].http;
818     DEBUG() << "WebCoreHttp::slotReadyRead connection=" << c;
819
820     QByteArray data;
821     data.resize(http->bytesAvailable());
822     http->read(data.data(), data.length());
823     emit req->networkInterface()->data(req, data);
824 }
825
826 void WebCoreHttp::onRequestFinished(int, bool error)
827 {
828     int c = getConnection();
829     QWebNetworkJob *req = connection[c].current;
830     if (!req) {
831         scheduleNextRequest();
832         return;
833     }
834     QHttp *http = connection[c].http;
835     DEBUG() << "WebCoreHttp::slotFinished connection=" << c << error << req;
836
837     if (error)
838         DEBUG() << "   error: " << http->errorString();
839
840     if (!error && http->bytesAvailable()) {
841         QByteArray data;
842         data.resize(http->bytesAvailable());
843         http->read(data.data(), data.length());
844         emit req->networkInterface()->data(req, data);
845     }
846     emit req->networkInterface()->finished(req, error ? 1 : 0);
847
848     connection[c].current = 0;
849     scheduleNextRequest();
850 }
851
852 void WebCoreHttp::onStateChanged(int state)
853 {
854     if (state == QHttp::Closing || state == QHttp::Unconnected) {
855         if (!m_inCancel && m_pendingRequests.isEmpty()
856             && !connection[0].current && !connection[1].current)
857             emit connectionClosed(info);
858     }
859 }
860
861 void WebCoreHttp::cancel(QWebNetworkJob* request)
862 {
863     bool doEmit = true;
864     m_inCancel = true;
865     for (int i = 0; i < 2; ++i) {
866         if (request == connection[i].current) {
867             connection[i].http->abort();
868             doEmit = false;
869         }
870     }
871     if (!m_pendingRequests.removeAll(request))
872         doEmit = false;
873     m_inCancel = false;
874
875     if (doEmit)
876         emit request->networkInterface()->finished(request, 1);
877
878     if (m_pendingRequests.isEmpty()
879         && !connection[0].current && !connection[1].current)
880         emit connectionClosed(info);
881 }
882
883 HostInfo::HostInfo(const QUrl& url)
884     : protocol(url.scheme())
885     , host(url.host())
886     , port(url.port())
887 {
888     if (port < 0) {
889         if (protocol == QLatin1String("http"))
890             port = 80;
891         else if (protocol == QLatin1String("https"))
892             port = 443;
893     }
894 }
895