1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40//#define QHTTPTHREADDELEGATE_DEBUG
41#include "qhttpthreaddelegate_p.h"
42
43#include <QThread>
44#include <QTimer>
45#include <QAuthenticator>
46#include <QEventLoop>
47#include <QCryptographicHash>
48
49#include "private/qhttpnetworkreply_p.h"
50#include "private/qnetworkaccesscache_p.h"
51#include "private/qnoncontiguousbytedevice_p.h"
52
53QT_BEGIN_NAMESPACE
54
55static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
56{
57 QNetworkReply::NetworkError code;
58 // we've got an error
59 switch (httpStatusCode) {
60 case 400: // Bad Request
61 code = QNetworkReply::ProtocolInvalidOperationError;
62 break;
63
64 case 401: // Authorization required
65 code = QNetworkReply::AuthenticationRequiredError;
66 break;
67
68 case 403: // Access denied
69 code = QNetworkReply::ContentAccessDenied;
70 break;
71
72 case 404: // Not Found
73 code = QNetworkReply::ContentNotFoundError;
74 break;
75
76 case 405: // Method Not Allowed
77 code = QNetworkReply::ContentOperationNotPermittedError;
78 break;
79
80 case 407:
81 code = QNetworkReply::ProxyAuthenticationRequiredError;
82 break;
83
84 case 409: // Resource Conflict
85 code = QNetworkReply::ContentConflictError;
86 break;
87
88 case 410: // Content no longer available
89 code = QNetworkReply::ContentGoneError;
90 break;
91
92 case 418: // I'm a teapot
93 code = QNetworkReply::ProtocolInvalidOperationError;
94 break;
95
96 case 500: // Internal Server Error
97 code = QNetworkReply::InternalServerError;
98 break;
99
100 case 501: // Server does not support this functionality
101 code = QNetworkReply::OperationNotImplementedError;
102 break;
103
104 case 503: // Service unavailable
105 code = QNetworkReply::ServiceUnavailableError;
106 break;
107
108 default:
109 if (httpStatusCode > 500) {
110 // some kind of server error
111 code = QNetworkReply::UnknownServerError;
112 } else if (httpStatusCode >= 400) {
113 // content error we did not handle above
114 code = QNetworkReply::UnknownContentError;
115 } else {
116 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
117 httpStatusCode, qPrintable(url.toString()));
118 code = QNetworkReply::ProtocolFailure;
119 }
120 }
121
122 return code;
123}
124
125
126static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &peerVerifyName)
127{
128 QString result;
129 QUrl copy = url;
130 QString scheme = copy.scheme();
131 bool isEncrypted = scheme == QLatin1String("https")
132 || scheme == QLatin1String("preconnect-https");
133 copy.setPort(copy.port(isEncrypted ? 443 : 80));
134 if (scheme == QLatin1String("preconnect-http")) {
135 copy.setScheme(QLatin1String("http"));
136 } else if (scheme == QLatin1String("preconnect-https")) {
137 copy.setScheme(QLatin1String("https"));
138 }
139 result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath |
140 QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded);
141
142#ifndef QT_NO_NETWORKPROXY
143 if (proxy && proxy->type() != QNetworkProxy::NoProxy) {
144 QUrl key;
145
146 switch (proxy->type()) {
147 case QNetworkProxy::Socks5Proxy:
148 key.setScheme(QLatin1String("proxy-socks5"));
149 break;
150
151 case QNetworkProxy::HttpProxy:
152 case QNetworkProxy::HttpCachingProxy:
153 key.setScheme(QLatin1String("proxy-http"));
154 break;
155
156 default:
157 break;
158 }
159
160 if (!key.scheme().isEmpty()) {
161 const QByteArray obfuscatedPassword = QCryptographicHash::hash(proxy->password().toUtf8(),
162 QCryptographicHash::Sha1).toHex();
163 key.setUserName(proxy->user());
164 key.setPassword(QString::fromUtf8(obfuscatedPassword));
165 key.setHost(proxy->hostName());
166 key.setPort(proxy->port());
167 key.setQuery(result);
168 result = key.toString(QUrl::FullyEncoded);
169 }
170 }
171#else
172 Q_UNUSED(proxy)
173#endif
174 if (!peerVerifyName.isEmpty())
175 result += QLatin1Char(':') + peerVerifyName;
176 return "http-connection:" + std::move(result).toLatin1();
177}
178
179class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
180 public QNetworkAccessCache::CacheableObject
181{
182 // Q_OBJECT
183public:
184#ifdef QT_NO_BEARERMANAGEMENT
185 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
186 QHttpNetworkConnection::ConnectionType connectionType)
187 : QHttpNetworkConnection(hostName, port, encrypt, connectionType)
188#else
189 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
190 QHttpNetworkConnection::ConnectionType connectionType,
191 QSharedPointer<QNetworkSession> networkSession)
192 : QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/0,
193 std::move(networkSession))
194#endif
195 {
196 setExpires(true);
197 setShareable(true);
198 }
199
200 virtual void dispose() override
201 {
202#if 0 // sample code; do this right with the API
203 Q_ASSERT(!isWorking());
204#endif
205 delete this;
206 }
207};
208
209
210QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
211
212
213QHttpThreadDelegate::~QHttpThreadDelegate()
214{
215 // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply
216 if (httpReply) {
217 delete httpReply;
218 }
219
220 // Get the object cache that stores our QHttpNetworkConnection objects
221 // and release the entry for this QHttpNetworkConnection
222 if (connections.hasLocalData() && !cacheKey.isEmpty()) {
223 connections.localData()->releaseEntry(cacheKey);
224 }
225}
226
227
228QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
229 QObject(parent)
230 , ssl(false)
231 , downloadBufferMaximumSize(0)
232 , readBufferMaxSize(0)
233 , bytesEmitted(0)
234 , pendingDownloadData()
235 , pendingDownloadProgress()
236 , synchronous(false)
237 , incomingStatusCode(0)
238 , isPipeliningUsed(false)
239 , isSpdyUsed(false)
240 , incomingContentLength(-1)
241 , removedContentLength(-1)
242 , incomingErrorCode(QNetworkReply::NoError)
243 , downloadBuffer()
244 , httpConnection(0)
245 , httpReply(0)
246 , synchronousRequestLoop(0)
247{
248}
249
250// This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread
251void QHttpThreadDelegate::startRequestSynchronously()
252{
253#ifdef QHTTPTHREADDELEGATE_DEBUG
254 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId();
255#endif
256 synchronous = true;
257
258 QEventLoop synchronousRequestLoop;
259 this->synchronousRequestLoop = &synchronousRequestLoop;
260
261 // Worst case timeout
262 QTimer::singleShot(30*1000, this, SLOT(abortRequest()));
263
264 QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
265 synchronousRequestLoop.exec();
266
267 connections.localData()->releaseEntry(cacheKey);
268 connections.setLocalData(0);
269
270#ifdef QHTTPTHREADDELEGATE_DEBUG
271 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished";
272#endif
273}
274
275
276// This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread
277void QHttpThreadDelegate::startRequest()
278{
279#ifdef QHTTPTHREADDELEGATE_DEBUG
280 qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
281#endif
282 // Check QThreadStorage for the QNetworkAccessCache
283 // If not there, create this connection cache
284 if (!connections.hasLocalData()) {
285 connections.setLocalData(new QNetworkAccessCache());
286 }
287
288 // check if we have an open connection to this host
289 QUrl urlCopy = httpRequest.url();
290 urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
291
292 QHttpNetworkConnection::ConnectionType connectionType
293 = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
294 : QHttpNetworkConnection::ConnectionTypeHTTP;
295 if (httpRequest.isHTTP2Direct()) {
296 Q_ASSERT(!httpRequest.isHTTP2Allowed());
297 connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct;
298 }
299
300 const bool isH2 = httpRequest.isHTTP2Allowed() || httpRequest.isHTTP2Direct();
301 if (isH2) {
302#if QT_CONFIG(ssl)
303 if (ssl) {
304 if (!httpRequest.isHTTP2Direct()) {
305 QList<QByteArray> protocols;
306 protocols << QSslConfiguration::ALPNProtocolHTTP2
307 << QSslConfiguration::NextProtocolHttp1_1;
308 incomingSslConfiguration->setAllowedNextProtocols(protocols);
309 }
310 urlCopy.setScheme(QStringLiteral("h2s"));
311 } else
312#endif // QT_CONFIG(ssl)
313 {
314 urlCopy.setScheme(QStringLiteral("h2"));
315 }
316 }
317
318#ifndef QT_NO_SSL
319 if (ssl && !incomingSslConfiguration.data())
320 incomingSslConfiguration.reset(new QSslConfiguration);
321
322 if (!isH2 && httpRequest.isSPDYAllowed() && ssl) {
323 connectionType = QHttpNetworkConnection::ConnectionTypeSPDY;
324 urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests
325 QList<QByteArray> nextProtocols;
326 nextProtocols << QSslConfiguration::NextProtocolSpdy3_0
327 << QSslConfiguration::NextProtocolHttp1_1;
328 incomingSslConfiguration->setAllowedNextProtocols(nextProtocols);
329 }
330#endif // QT_NO_SSL
331
332#ifndef QT_NO_NETWORKPROXY
333 if (transparentProxy.type() != QNetworkProxy::NoProxy)
334 cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
335 else if (cacheProxy.type() != QNetworkProxy::NoProxy)
336 cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName());
337 else
338#endif
339 cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName());
340
341 // the http object is actually a QHttpNetworkConnection
342 httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
343 if (!httpConnection) {
344 // no entry in cache; create an object
345 // the http object is actually a QHttpNetworkConnection
346#ifdef QT_NO_BEARERMANAGEMENT
347 httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
348 connectionType);
349#else
350 httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
351 connectionType,
352 networkSession);
353#endif // QT_NO_BEARERMANAGEMENT
354 if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
355 && http2Parameters.validate()) {
356 httpConnection->setHttp2Parameters(http2Parameters);
357 } // else we ignore invalid parameters and use our own defaults.
358#ifndef QT_NO_SSL
359 // Set the QSslConfiguration from this QNetworkRequest.
360 if (ssl)
361 httpConnection->setSslConfiguration(*incomingSslConfiguration);
362#endif
363
364#ifndef QT_NO_NETWORKPROXY
365 httpConnection->setTransparentProxy(transparentProxy);
366 httpConnection->setCacheProxy(cacheProxy);
367#endif
368 httpConnection->setPeerVerifyName(httpRequest.peerVerifyName());
369 // cache the QHttpNetworkConnection corresponding to this cache key
370 connections.localData()->addEntry(cacheKey, httpConnection);
371 } else {
372 if (httpRequest.withCredentials()) {
373 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr);
374 if (!credential.user.isEmpty() && !credential.password.isEmpty()) {
375 QAuthenticator auth;
376 auth.setUser(credential.user);
377 auth.setPassword(credential.password);
378 httpConnection->d_func()->copyCredentials(-1, &auth, false);
379 }
380 }
381 }
382
383 // Send the request to the connection
384 httpReply = httpConnection->sendRequest(httpRequest);
385 httpReply->setParent(this);
386
387 // Connect the reply signals that we need to handle and then forward
388 if (synchronous) {
389 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
390 connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
391 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
392 this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
393
394 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
395 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
396#ifndef QT_NO_NETWORKPROXY
397 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
398 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
399#endif
400
401 // Don't care about ignored SSL errors for now in the synchronous HTTP case.
402 } else if (!synchronous) {
403 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
404 connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
405 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
406 this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
407 // some signals are only interesting when normal asynchronous style is used
408 connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
409 connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
410#ifndef QT_NO_SSL
411 connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
412 connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
413 connect(httpReply,SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
414 this, SLOT(preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)));
415#endif
416
417 // In the asynchronous HTTP case we can just forward those signals
418 // Connect the reply signals that we can directly forward
419 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
420 this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
421#ifndef QT_NO_NETWORKPROXY
422 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
423 this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
424#endif
425 }
426
427 connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
428 this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
429}
430
431// This gets called from the user thread or by the synchronous HTTP timeout timer
432void QHttpThreadDelegate::abortRequest()
433{
434#ifdef QHTTPTHREADDELEGATE_DEBUG
435 qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous;
436#endif
437 if (httpReply) {
438 httpReply->abort();
439 delete httpReply;
440 httpReply = 0;
441 }
442
443 // Got aborted by the timeout timer
444 if (synchronous) {
445 incomingErrorCode = QNetworkReply::TimeoutError;
446 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
447 } else {
448 //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
449 this->deleteLater();
450 }
451}
452
453void QHttpThreadDelegate::readBufferSizeChanged(qint64 size)
454{
455#ifdef QHTTPTHREADDELEGATE_DEBUG
456 qDebug() << "QHttpThreadDelegate::readBufferSizeChanged() size " << size;
457#endif
458 if (httpReply) {
459 httpReply->setDownstreamLimited(size > 0);
460 httpReply->setReadBufferSize(size);
461 readBufferMaxSize = size;
462 }
463}
464
465void QHttpThreadDelegate::readBufferFreed(qint64 size)
466{
467 if (readBufferMaxSize) {
468 bytesEmitted -= size;
469
470 QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection);
471 }
472}
473
474void QHttpThreadDelegate::readyReadSlot()
475{
476 if (!httpReply)
477 return;
478
479 // Don't do in zerocopy case
480 if (!downloadBuffer.isNull())
481 return;
482
483 if (readBufferMaxSize) {
484 if (bytesEmitted < readBufferMaxSize) {
485 qint64 sizeEmitted = 0;
486 while (httpReply->readAnyAvailable() && (sizeEmitted < (readBufferMaxSize-bytesEmitted))) {
487 if (httpReply->sizeNextBlock() > (readBufferMaxSize-bytesEmitted)) {
488 sizeEmitted = readBufferMaxSize-bytesEmitted;
489 bytesEmitted += sizeEmitted;
490 pendingDownloadData->fetchAndAddRelease(1);
491 emit downloadData(httpReply->read(sizeEmitted));
492 } else {
493 sizeEmitted = httpReply->sizeNextBlock();
494 bytesEmitted += sizeEmitted;
495 pendingDownloadData->fetchAndAddRelease(1);
496 emit downloadData(httpReply->readAny());
497 }
498 }
499 } else {
500 // We need to wait until we empty data from the read buffer in the reply.
501 }
502
503 } else {
504 while (httpReply->readAnyAvailable()) {
505 pendingDownloadData->fetchAndAddRelease(1);
506 emit downloadData(httpReply->readAny());
507 }
508 }
509}
510
511void QHttpThreadDelegate::finishedSlot()
512{
513 if (!httpReply)
514 return;
515
516#ifdef QHTTPTHREADDELEGATE_DEBUG
517 qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
518#endif
519
520 // If there is still some data left emit that now
521 while (httpReply->readAnyAvailable()) {
522 pendingDownloadData->fetchAndAddRelease(1);
523 emit downloadData(httpReply->readAny());
524 }
525
526#ifndef QT_NO_SSL
527 if (ssl)
528 emit sslConfigurationChanged(httpReply->sslConfiguration());
529#endif
530
531 if (httpReply->statusCode() >= 400) {
532 // it's an error reply
533 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
534 "Error transferring %1 - server replied: %2"));
535 msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
536 emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
537 }
538
539 if (httpRequest.isFollowRedirects() && httpReply->isRedirecting())
540 emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1);
541
542 emit downloadFinished();
543
544 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
545 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
546 httpReply = 0;
547}
548
549void QHttpThreadDelegate::synchronousFinishedSlot()
550{
551 if (!httpReply)
552 return;
553
554#ifdef QHTTPTHREADDELEGATE_DEBUG
555 qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
556#endif
557 if (httpReply->statusCode() >= 400) {
558 // it's an error reply
559 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
560 "Error transferring %1 - server replied: %2"));
561 incomingErrorDetail = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
562 incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
563 }
564
565 synchronousDownloadData = httpReply->readAll();
566
567 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
568 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
569 httpReply = 0;
570}
571
572void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
573{
574 if (!httpReply)
575 return;
576
577#ifdef QHTTPTHREADDELEGATE_DEBUG
578 qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
579#endif
580
581#ifndef QT_NO_SSL
582 if (ssl)
583 emit sslConfigurationChanged(httpReply->sslConfiguration());
584#endif
585 emit error(errorCode,detail);
586 emit downloadFinished();
587
588
589 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
590 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
591 httpReply = 0;
592}
593
594
595void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
596{
597 if (!httpReply)
598 return;
599
600#ifdef QHTTPTHREADDELEGATE_DEBUG
601 qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
602#endif
603 incomingErrorCode = errorCode;
604 incomingErrorDetail = detail;
605
606 synchronousDownloadData = httpReply->readAll();
607
608 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
609 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
610 httpReply = 0;
611}
612
613static void downloadBufferDeleter(char *ptr)
614{
615 delete[] ptr;
616}
617
618void QHttpThreadDelegate::headerChangedSlot()
619{
620 if (!httpReply)
621 return;
622
623#ifdef QHTTPTHREADDELEGATE_DEBUG
624 qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId();
625#endif
626
627#ifndef QT_NO_SSL
628 if (ssl)
629 emit sslConfigurationChanged(httpReply->sslConfiguration());
630#endif
631
632 // Is using a zerocopy buffer allowed by user and possible with this reply?
633 if (httpReply->supportsUserProvidedDownloadBuffer()
634 && (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) {
635 QT_TRY {
636 char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
637 if (buf) {
638 downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
639 httpReply->setUserProvidedDownloadBuffer(buf);
640 }
641 } QT_CATCH(const std::bad_alloc &) {
642 // in out of memory situations, don't use downloadbuffer.
643 }
644 }
645
646 // We fetch this into our own
647 incomingHeaders = httpReply->header();
648 incomingStatusCode = httpReply->statusCode();
649 incomingReasonPhrase = httpReply->reasonPhrase();
650 isPipeliningUsed = httpReply->isPipeliningUsed();
651 incomingContentLength = httpReply->contentLength();
652 removedContentLength = httpReply->removedContentLength();
653 isSpdyUsed = httpReply->isSpdyUsed();
654
655 emit downloadMetaData(incomingHeaders,
656 incomingStatusCode,
657 incomingReasonPhrase,
658 isPipeliningUsed,
659 downloadBuffer,
660 incomingContentLength,
661 removedContentLength,
662 isSpdyUsed);
663}
664
665void QHttpThreadDelegate::synchronousHeaderChangedSlot()
666{
667 if (!httpReply)
668 return;
669
670#ifdef QHTTPTHREADDELEGATE_DEBUG
671 qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId();
672#endif
673 // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it
674 incomingHeaders = httpReply->header();
675 incomingStatusCode = httpReply->statusCode();
676 incomingReasonPhrase = httpReply->reasonPhrase();
677 isPipeliningUsed = httpReply->isPipeliningUsed();
678 isSpdyUsed = httpReply->isSpdyUsed();
679 incomingContentLength = httpReply->contentLength();
680}
681
682
683void QHttpThreadDelegate::dataReadProgressSlot(qint64 done, qint64 total)
684{
685 // If we don't have a download buffer don't attempt to go this codepath
686 // It is not used by QNetworkAccessHttpBackend
687 if (downloadBuffer.isNull())
688 return;
689
690 pendingDownloadProgress->fetchAndAddRelease(1);
691 emit downloadProgress(done, total);
692}
693
694void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
695{
696 authenticationManager->cacheCredentials(request.url(), authenticator);
697}
698
699
700#ifndef QT_NO_SSL
701void QHttpThreadDelegate::encryptedSlot()
702{
703 if (!httpReply)
704 return;
705
706 emit sslConfigurationChanged(httpReply->sslConfiguration());
707 emit encrypted();
708}
709
710void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
711{
712 if (!httpReply)
713 return;
714
715 emit sslConfigurationChanged(httpReply->sslConfiguration());
716
717 bool ignoreAll = false;
718 QList<QSslError> specificErrors;
719 emit sslErrors(errors, &ignoreAll, &specificErrors);
720 if (ignoreAll)
721 httpReply->ignoreSslErrors();
722 if (!specificErrors.isEmpty())
723 httpReply->ignoreSslErrors(specificErrors);
724}
725
726void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
727{
728 if (!httpReply)
729 return;
730
731 emit preSharedKeyAuthenticationRequired(authenticator);
732}
733#endif
734
735void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a)
736{
737 if (!httpReply)
738 return;
739
740 Q_UNUSED(request);
741#ifdef QHTTPTHREADDELEGATE_DEBUG
742 qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
743#endif
744
745 // Ask the credential cache
746 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a);
747 if (!credential.isNull()) {
748 a->setUser(credential.user);
749 a->setPassword(credential.password);
750 }
751
752 // Disconnect this connection now since we only want to ask the authentication cache once.
753 QObject::disconnect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
754 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
755}
756
757#ifndef QT_NO_NETWORKPROXY
758void QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a)
759{
760 if (!httpReply)
761 return;
762
763#ifdef QHTTPTHREADDELEGATE_DEBUG
764 qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
765#endif
766 // Ask the credential cache
767 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a);
768 if (!credential.isNull()) {
769 a->setUser(credential.user);
770 a->setPassword(credential.password);
771 }
772
773#ifndef QT_NO_NETWORKPROXY
774 // Disconnect this connection now since we only want to ask the authentication cache once.
775 QObject::disconnect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
776 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
777#endif
778}
779
780#endif
781
782QT_END_NAMESPACE
783