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

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