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