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