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