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 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
185 QHttpNetworkConnection::ConnectionType connectionType)
186 : QHttpNetworkConnection(hostName, port, encrypt, connectionType)
187 {
188 setExpires(true);
189 setShareable(true);
190 }
191
192 virtual void dispose() override
193 {
194#if 0 // sample code; do this right with the API
195 Q_ASSERT(!isWorking());
196#endif
197 delete this;
198 }
199};
200
201
202QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
203
204
205QHttpThreadDelegate::~QHttpThreadDelegate()
206{
207 // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply
208 if (httpReply) {
209 delete httpReply;
210 }
211
212 // Get the object cache that stores our QHttpNetworkConnection objects
213 // and release the entry for this QHttpNetworkConnection
214 if (connections.hasLocalData() && !cacheKey.isEmpty()) {
215 connections.localData()->releaseEntry(cacheKey);
216 }
217}
218
219
220QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
221 QObject(parent)
222 , ssl(false)
223 , downloadBufferMaximumSize(0)
224 , readBufferMaxSize(0)
225 , bytesEmitted(0)
226 , pendingDownloadData()
227 , pendingDownloadProgress()
228 , synchronous(false)
229 , incomingStatusCode(0)
230 , isPipeliningUsed(false)
231 , isHttp2Used(false)
232 , incomingContentLength(-1)
233 , removedContentLength(-1)
234 , incomingErrorCode(QNetworkReply::NoError)
235 , downloadBuffer()
236 , httpConnection(nullptr)
237 , httpReply(nullptr)
238 , synchronousRequestLoop(nullptr)
239{
240}
241
242// This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread
243void QHttpThreadDelegate::startRequestSynchronously()
244{
245#ifdef QHTTPTHREADDELEGATE_DEBUG
246 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId();
247#endif
248 synchronous = true;
249
250 QEventLoop synchronousRequestLoop;
251 this->synchronousRequestLoop = &synchronousRequestLoop;
252
253 // Worst case timeout
254 QTimer::singleShot(30*1000, this, SLOT(abortRequest()));
255
256 QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
257 synchronousRequestLoop.exec();
258
259 connections.localData()->releaseEntry(cacheKey);
260 connections.setLocalData(nullptr);
261
262#ifdef QHTTPTHREADDELEGATE_DEBUG
263 qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished";
264#endif
265}
266
267
268// This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread
269void QHttpThreadDelegate::startRequest()
270{
271#ifdef QHTTPTHREADDELEGATE_DEBUG
272 qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
273#endif
274 // Check QThreadStorage for the QNetworkAccessCache
275 // If not there, create this connection cache
276 if (!connections.hasLocalData()) {
277 connections.setLocalData(new QNetworkAccessCache());
278 }
279
280 // check if we have an open connection to this host
281 QUrl urlCopy = httpRequest.url();
282 urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
283
284 QHttpNetworkConnection::ConnectionType connectionType
285 = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
286 : QHttpNetworkConnection::ConnectionTypeHTTP;
287 if (httpRequest.isHTTP2Direct()) {
288 Q_ASSERT(!httpRequest.isHTTP2Allowed());
289 connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct;
290 }
291
292#if QT_CONFIG(ssl)
293 // See qnetworkreplyhttpimpl, delegate's initialization code.
294 Q_ASSERT(!ssl || incomingSslConfiguration.data());
295#endif // QT_CONFIG(ssl)
296
297 const bool isH2 = httpRequest.isHTTP2Allowed() || httpRequest.isHTTP2Direct();
298 if (isH2) {
299#if QT_CONFIG(ssl)
300 if (ssl) {
301 if (!httpRequest.isHTTP2Direct()) {
302 QList<QByteArray> protocols;
303 protocols << QSslConfiguration::ALPNProtocolHTTP2
304 << QSslConfiguration::NextProtocolHttp1_1;
305 incomingSslConfiguration->setAllowedNextProtocols(protocols);
306 }
307 urlCopy.setScheme(QStringLiteral("h2s"));
308 } else
309#endif // QT_CONFIG(ssl)
310 {
311 urlCopy.setScheme(QStringLiteral("h2"));
312 }
313 }
314
315#ifndef QT_NO_NETWORKPROXY
316 if (transparentProxy.type() != QNetworkProxy::NoProxy)
317 cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
318 else if (cacheProxy.type() != QNetworkProxy::NoProxy)
319 cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName());
320 else
321#endif
322 cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName());
323
324 // the http object is actually a QHttpNetworkConnection
325 httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
326 if (!httpConnection) {
327 // no entry in cache; create an object
328 // the http object is actually a QHttpNetworkConnection
329 httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
330 connectionType);
331 if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
332 || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
333 httpConnection->setHttp2Parameters(http2Parameters);
334 }
335#ifndef QT_NO_SSL
336 // Set the QSslConfiguration from this QNetworkRequest.
337 if (ssl)
338 httpConnection->setSslConfiguration(*incomingSslConfiguration);
339#endif
340
341#ifndef QT_NO_NETWORKPROXY
342 httpConnection->setTransparentProxy(transparentProxy);
343 httpConnection->setCacheProxy(cacheProxy);
344#endif
345 httpConnection->setPeerVerifyName(httpRequest.peerVerifyName());
346 // cache the QHttpNetworkConnection corresponding to this cache key
347 connections.localData()->addEntry(cacheKey, httpConnection);
348 } else {
349 if (httpRequest.withCredentials()) {
350 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr);
351 if (!credential.user.isEmpty() && !credential.password.isEmpty()) {
352 QAuthenticator auth;
353 auth.setUser(credential.user);
354 auth.setPassword(credential.password);
355 httpConnection->d_func()->copyCredentials(-1, &auth, false);
356 }
357 }
358 }
359
360 // Send the request to the connection
361 httpReply = httpConnection->sendRequest(httpRequest);
362 httpReply->setParent(this);
363
364 // Connect the reply signals that we need to handle and then forward
365 if (synchronous) {
366 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
367 connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
368 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
369 this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
370
371 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
372 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
373#ifndef QT_NO_NETWORKPROXY
374 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
375 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
376#endif
377
378 // Don't care about ignored SSL errors for now in the synchronous HTTP case.
379 } else if (!synchronous) {
380 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
381 connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
382 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
383 this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
384 // some signals are only interesting when normal asynchronous style is used
385 connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
386 connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
387#ifndef QT_NO_SSL
388 connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
389 connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
390 connect(httpReply,SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
391 this, SLOT(preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*)));
392#endif
393
394 // In the asynchronous HTTP case we can just forward those signals
395 // Connect the reply signals that we can directly forward
396 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
397 this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
398#ifndef QT_NO_NETWORKPROXY
399 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
400 this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
401#endif
402 }
403
404 connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
405 this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
406 if (httpReply->errorCode() != QNetworkReply::NoError) {
407 if (synchronous)
408 synchronousFinishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
409 else
410 finishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
411 }
412}
413
414// This gets called from the user thread or by the synchronous HTTP timeout timer
415void QHttpThreadDelegate::abortRequest()
416{
417#ifdef QHTTPTHREADDELEGATE_DEBUG
418 qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous;
419#endif
420 if (httpReply) {
421 httpReply->abort();
422 delete httpReply;
423 httpReply = nullptr;
424 }
425
426 // Got aborted by the timeout timer
427 if (synchronous) {
428 incomingErrorCode = QNetworkReply::TimeoutError;
429 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
430 } else {
431 //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
432 this->deleteLater();
433 }
434}
435
436void QHttpThreadDelegate::readBufferSizeChanged(qint64 size)
437{
438#ifdef QHTTPTHREADDELEGATE_DEBUG
439 qDebug() << "QHttpThreadDelegate::readBufferSizeChanged() size " << size;
440#endif
441 if (httpReply) {
442 httpReply->setDownstreamLimited(size > 0);
443 httpReply->setReadBufferSize(size);
444 readBufferMaxSize = size;
445 }
446}
447
448void QHttpThreadDelegate::readBufferFreed(qint64 size)
449{
450 if (readBufferMaxSize) {
451 bytesEmitted -= size;
452
453 QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection);
454 }
455}
456
457void QHttpThreadDelegate::readyReadSlot()
458{
459 if (!httpReply)
460 return;
461
462 // Don't do in zerocopy case
463 if (!downloadBuffer.isNull())
464 return;
465
466 if (readBufferMaxSize) {
467 if (bytesEmitted < readBufferMaxSize) {
468 qint64 sizeEmitted = 0;
469 while (httpReply->readAnyAvailable() && (sizeEmitted < (readBufferMaxSize-bytesEmitted))) {
470 if (httpReply->sizeNextBlock() > (readBufferMaxSize-bytesEmitted)) {
471 sizeEmitted = readBufferMaxSize-bytesEmitted;
472 bytesEmitted += sizeEmitted;
473 pendingDownloadData->fetchAndAddRelease(1);
474 emit downloadData(httpReply->read(sizeEmitted));
475 } else {
476 sizeEmitted = httpReply->sizeNextBlock();
477 bytesEmitted += sizeEmitted;
478 pendingDownloadData->fetchAndAddRelease(1);
479 emit downloadData(httpReply->readAny());
480 }
481 }
482 } else {
483 // We need to wait until we empty data from the read buffer in the reply.
484 }
485
486 } else {
487 while (httpReply->readAnyAvailable()) {
488 pendingDownloadData->fetchAndAddRelease(1);
489 emit downloadData(httpReply->readAny());
490 }
491 }
492}
493
494void QHttpThreadDelegate::finishedSlot()
495{
496 if (!httpReply)
497 return;
498
499#ifdef QHTTPTHREADDELEGATE_DEBUG
500 qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
501#endif
502
503 // If there is still some data left emit that now
504 while (httpReply->readAnyAvailable()) {
505 pendingDownloadData->fetchAndAddRelease(1);
506 emit downloadData(httpReply->readAny());
507 }
508
509#ifndef QT_NO_SSL
510 if (ssl)
511 emit sslConfigurationChanged(httpReply->sslConfiguration());
512#endif
513
514 if (httpReply->statusCode() >= 400) {
515 // it's an error reply
516 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
517 "Error transferring %1 - server replied: %2"));
518 msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
519 emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
520 }
521
522 if (httpRequest.isFollowRedirects() && httpReply->isRedirecting())
523 emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1);
524
525 emit downloadFinished();
526
527 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
528 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
529 httpReply = nullptr;
530}
531
532void QHttpThreadDelegate::synchronousFinishedSlot()
533{
534 if (!httpReply)
535 return;
536
537#ifdef QHTTPTHREADDELEGATE_DEBUG
538 qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
539#endif
540 if (httpReply->statusCode() >= 400) {
541 // it's an error reply
542 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
543 "Error transferring %1 - server replied: %2"));
544 incomingErrorDetail = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
545 incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
546 }
547
548 synchronousDownloadData = httpReply->readAll();
549
550 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
551 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
552 httpReply = nullptr;
553}
554
555void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
556{
557 if (!httpReply)
558 return;
559
560#ifdef QHTTPTHREADDELEGATE_DEBUG
561 qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
562#endif
563
564#ifndef QT_NO_SSL
565 if (ssl)
566 emit sslConfigurationChanged(httpReply->sslConfiguration());
567#endif
568 emit error(errorCode,detail);
569 emit downloadFinished();
570
571
572 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
573 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
574 httpReply = nullptr;
575}
576
577
578void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
579{
580 if (!httpReply)
581 return;
582
583#ifdef QHTTPTHREADDELEGATE_DEBUG
584 qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
585#endif
586 incomingErrorCode = errorCode;
587 incomingErrorDetail = detail;
588
589 synchronousDownloadData = httpReply->readAll();
590
591 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
592 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
593 httpReply = nullptr;
594}
595
596static void downloadBufferDeleter(char *ptr)
597{
598 delete[] ptr;
599}
600
601void QHttpThreadDelegate::headerChangedSlot()
602{
603 if (!httpReply)
604 return;
605
606#ifdef QHTTPTHREADDELEGATE_DEBUG
607 qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId();
608#endif
609
610#ifndef QT_NO_SSL
611 if (ssl)
612 emit sslConfigurationChanged(httpReply->sslConfiguration());
613#endif
614
615 // Is using a zerocopy buffer allowed by user and possible with this reply?
616 if (httpReply->supportsUserProvidedDownloadBuffer()
617 && (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) {
618 QT_TRY {
619 char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
620 if (buf) {
621 downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
622 httpReply->setUserProvidedDownloadBuffer(buf);
623 }
624 } QT_CATCH(const std::bad_alloc &) {
625 // in out of memory situations, don't use downloadbuffer.
626 }
627 }
628
629 // We fetch this into our own
630 incomingHeaders = httpReply->header();
631 incomingStatusCode = httpReply->statusCode();
632 incomingReasonPhrase = httpReply->reasonPhrase();
633 isPipeliningUsed = httpReply->isPipeliningUsed();
634 incomingContentLength = httpReply->contentLength();
635 removedContentLength = httpReply->removedContentLength();
636 isHttp2Used = httpReply->isHttp2Used();
637
638 emit downloadMetaData(incomingHeaders,
639 incomingStatusCode,
640 incomingReasonPhrase,
641 isPipeliningUsed,
642 downloadBuffer,
643 incomingContentLength,
644 removedContentLength,
645 isHttp2Used);
646}
647
648void QHttpThreadDelegate::synchronousHeaderChangedSlot()
649{
650 if (!httpReply)
651 return;
652
653#ifdef QHTTPTHREADDELEGATE_DEBUG
654 qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId();
655#endif
656 // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it
657 incomingHeaders = httpReply->header();
658 incomingStatusCode = httpReply->statusCode();
659 incomingReasonPhrase = httpReply->reasonPhrase();
660 isPipeliningUsed = httpReply->isPipeliningUsed();
661 isHttp2Used = httpReply->isHttp2Used();
662 incomingContentLength = httpReply->contentLength();
663}
664
665
666void QHttpThreadDelegate::dataReadProgressSlot(qint64 done, qint64 total)
667{
668 // If we don't have a download buffer don't attempt to go this codepath
669 // It is not used by QNetworkAccessHttpBackend
670 if (downloadBuffer.isNull())
671 return;
672
673 pendingDownloadProgress->fetchAndAddRelease(1);
674 emit downloadProgress(done, total);
675}
676
677void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
678{
679 authenticationManager->cacheCredentials(request.url(), authenticator);
680}
681
682
683#ifndef QT_NO_SSL
684void QHttpThreadDelegate::encryptedSlot()
685{
686 if (!httpReply)
687 return;
688
689 emit sslConfigurationChanged(httpReply->sslConfiguration());
690 emit encrypted();
691}
692
693void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
694{
695 if (!httpReply)
696 return;
697
698 emit sslConfigurationChanged(httpReply->sslConfiguration());
699
700 bool ignoreAll = false;
701 QList<QSslError> specificErrors;
702 emit sslErrors(errors, &ignoreAll, &specificErrors);
703 if (ignoreAll)
704 httpReply->ignoreSslErrors();
705 if (!specificErrors.isEmpty())
706 httpReply->ignoreSslErrors(specificErrors);
707}
708
709void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
710{
711 if (!httpReply)
712 return;
713
714 emit preSharedKeyAuthenticationRequired(authenticator);
715}
716#endif
717
718void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a)
719{
720 if (!httpReply)
721 return;
722
723 Q_UNUSED(request);
724#ifdef QHTTPTHREADDELEGATE_DEBUG
725 qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
726#endif
727
728 // Ask the credential cache
729 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a);
730 if (!credential.isNull()) {
731 a->setUser(credential.user);
732 a->setPassword(credential.password);
733 }
734
735 // Disconnect this connection now since we only want to ask the authentication cache once.
736 QObject::disconnect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
737 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
738}
739
740#ifndef QT_NO_NETWORKPROXY
741void QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a)
742{
743 if (!httpReply)
744 return;
745
746#ifdef QHTTPTHREADDELEGATE_DEBUG
747 qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
748#endif
749 // Ask the credential cache
750 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a);
751 if (!credential.isNull()) {
752 a->setUser(credential.user);
753 a->setPassword(credential.password);
754 }
755
756#ifndef QT_NO_NETWORKPROXY
757 // Disconnect this connection now since we only want to ask the authentication cache once.
758 QObject::disconnect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
759 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
760#endif
761}
762
763#endif
764
765QT_END_NAMESPACE
766