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

source code of qtbase/src/network/access/qhttpthreaddelegate.cpp