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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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