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