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

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