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
53 QT_BEGIN_NAMESPACE
54
statusCodeFromHttp(int httpStatusCode,const QUrl & url)55 static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
56 {
57 QNetworkReply::NetworkError code;
58 // we've got an error
59 switch (httpStatusCode) {
60 case 400: // Bad Request
61 code = QNetworkReply::ProtocolInvalidOperationError;
62 break;
63
64 case 401: // Authorization required
65 code = QNetworkReply::AuthenticationRequiredError;
66 break;
67
68 case 403: // Access denied
69 code = QNetworkReply::ContentAccessDenied;
70 break;
71
72 case 404: // Not Found
73 code = QNetworkReply::ContentNotFoundError;
74 break;
75
76 case 405: // Method Not Allowed
77 code = QNetworkReply::ContentOperationNotPermittedError;
78 break;
79
80 case 407:
81 code = QNetworkReply::ProxyAuthenticationRequiredError;
82 break;
83
84 case 409: // Resource Conflict
85 code = QNetworkReply::ContentConflictError;
86 break;
87
88 case 410: // Content no longer available
89 code = QNetworkReply::ContentGoneError;
90 break;
91
92 case 418: // I'm a teapot
93 code = QNetworkReply::ProtocolInvalidOperationError;
94 break;
95
96 case 500: // Internal Server Error
97 code = QNetworkReply::InternalServerError;
98 break;
99
100 case 501: // Server does not support this functionality
101 code = QNetworkReply::OperationNotImplementedError;
102 break;
103
104 case 503: // Service unavailable
105 code = QNetworkReply::ServiceUnavailableError;
106 break;
107
108 default:
109 if (httpStatusCode > 500) {
110 // some kind of server error
111 code = QNetworkReply::UnknownServerError;
112 } else if (httpStatusCode >= 400) {
113 // content error we did not handle above
114 code = QNetworkReply::UnknownContentError;
115 } else {
116 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
117 httpStatusCode, qPrintable(url.toString()));
118 code = QNetworkReply::ProtocolFailure;
119 }
120 }
121
122 return code;
123 }
124
125
makeCacheKey(QUrl & url,QNetworkProxy * proxy,const QString & peerVerifyName)126 static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &peerVerifyName)
127 {
128 QString result;
129 QUrl copy = url;
130 QString scheme = copy.scheme();
131 bool isEncrypted = scheme == QLatin1String("https")
132 || scheme == QLatin1String("preconnect-https");
133 copy.setPort(copy.port(isEncrypted ? 443 : 80));
134 if (scheme == QLatin1String("preconnect-http")) {
135 copy.setScheme(QLatin1String("http"));
136 } else if (scheme == QLatin1String("preconnect-https")) {
137 copy.setScheme(QLatin1String("https"));
138 }
139 result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath |
140 QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded);
141
142 #ifndef QT_NO_NETWORKPROXY
143 if (proxy && proxy->type() != QNetworkProxy::NoProxy) {
144 QUrl key;
145
146 switch (proxy->type()) {
147 case QNetworkProxy::Socks5Proxy:
148 key.setScheme(QLatin1String("proxy-socks5"));
149 break;
150
151 case QNetworkProxy::HttpProxy:
152 case QNetworkProxy::HttpCachingProxy:
153 key.setScheme(QLatin1String("proxy-http"));
154 break;
155
156 default:
157 break;
158 }
159
160 if (!key.scheme().isEmpty()) {
161 const QByteArray obfuscatedPassword = QCryptographicHash::hash(proxy->password().toUtf8(),
162 QCryptographicHash::Sha1).toHex();
163 key.setUserName(proxy->user());
164 key.setPassword(QString::fromUtf8(obfuscatedPassword));
165 key.setHost(proxy->hostName());
166 key.setPort(proxy->port());
167 key.setQuery(result);
168 result = key.toString(QUrl::FullyEncoded);
169 }
170 }
171 #else
172 Q_UNUSED(proxy)
173 #endif
174 if (!peerVerifyName.isEmpty())
175 result += QLatin1Char(':') + peerVerifyName;
176 return "http-connection:" + std::move(result).toLatin1();
177 }
178
179 class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
180 public QNetworkAccessCache::CacheableObject
181 {
182 // Q_OBJECT
183 public:
184 #ifdef QT_NO_BEARERMANAGEMENT
QNetworkAccessCachedHttpConnection(const QString & hostName,quint16 port,bool encrypt,QHttpNetworkConnection::ConnectionType connectionType)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
dispose()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
210 QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
211
212
~QHttpThreadDelegate()213 QHttpThreadDelegate::~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(cacheKey);
224 }
225 }
226
227
QHttpThreadDelegate(QObject * parent)228 QHttpThreadDelegate::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
startRequestSynchronously()251 void 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(30*1000, this, SLOT(abortRequest()));
263
264 QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
265 synchronousRequestLoop.exec();
266
267 connections.localData()->releaseEntry(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
startRequest()277 void 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(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(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
337 else if (cacheProxy.type() != QNetworkProxy::NoProxy)
338 cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName());
339 else
340 #endif
341 cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName());
342
343 // the http object is actually a QHttpNetworkConnection
344 httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(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(cacheKey, httpConnection);
373 } else {
374 if (httpRequest.withCredentials()) {
375 QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), 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(-1, &auth, false);
381 }
382 }
383 }
384
385 // Send the request to the connection
386 httpReply = httpConnection->sendRequest(httpRequest);
387 httpReply->setParent(this);
388
389 // Connect the reply signals that we need to handle and then forward
390 if (synchronous) {
391 connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
392 connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
393 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
394 this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
395
396 connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
397 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
398 #ifndef QT_NO_NETWORKPROXY
399 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
400 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(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
406 connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
407 connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
408 this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
409 // some signals are only interesting when normal asynchronous style is used
410 connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
411 connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64)));
412 #ifndef QT_NO_SSL
413 connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
414 connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
415 connect(httpReply,SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
416 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(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
422 this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
423 #ifndef QT_NO_NETWORKPROXY
424 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
425 this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
426 #endif
427 }
428
429 connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
430 this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
431 if (httpReply->errorCode() != QNetworkReply::NoError) {
432 if (synchronous)
433 synchronousFinishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
434 else
435 finishedWithErrorSlot(httpReply->errorCode(), httpReply->errorString());
436 }
437 }
438
439 // This gets called from the user thread or by the synchronous HTTP timeout timer
abortRequest()440 void 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(synchronousRequestLoop, "quit", Qt::QueuedConnection);
455 } else {
456 //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
457 this->deleteLater();
458 }
459 }
460
readBufferSizeChanged(qint64 size)461 void 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
readBufferFreed(qint64 size)473 void QHttpThreadDelegate::readBufferFreed(qint64 size)
474 {
475 if (readBufferMaxSize) {
476 bytesEmitted -= size;
477
478 QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection);
479 }
480 }
481
readyReadSlot()482 void 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(1);
499 emit downloadData(httpReply->read(sizeEmitted));
500 } else {
501 sizeEmitted = httpReply->sizeNextBlock();
502 bytesEmitted += sizeEmitted;
503 pendingDownloadData->fetchAndAddRelease(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(1);
514 emit downloadData(httpReply->readAny());
515 }
516 }
517 }
518
finishedSlot()519 void 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(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(httpRequest.url().toString(), httpReply->reasonPhrase());
544 emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
545 }
546
547 if (httpRequest.isFollowRedirects() && httpReply->isRedirecting())
548 emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1);
549
550 emit downloadFinished();
551
552 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
553 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
554 httpReply = nullptr;
555 }
556
synchronousFinishedSlot()557 void 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(httpRequest.url().toString(), httpReply->reasonPhrase());
570 incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
571 }
572
573 synchronousDownloadData = httpReply->readAll();
574
575 QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
576 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
577 httpReply = nullptr;
578 }
579
finishedWithErrorSlot(QNetworkReply::NetworkError errorCode,const QString & detail)580 void 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(httpReply, "deleteLater", Qt::QueuedConnection);
598 QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
599 httpReply = nullptr;
600 }
601
602
synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode,const QString & detail)603 void 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(httpReply, "deleteLater", Qt::QueuedConnection);
617 QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
618 httpReply = nullptr;
619 }
620
downloadBufferDeleter(char * ptr)621 static void downloadBufferDeleter(char *ptr)
622 {
623 delete[] ptr;
624 }
625
headerChangedSlot()626 void 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
synchronousHeaderChangedSlot()673 void 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
dataReadProgressSlot(qint64 done,qint64 total)691 void 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(1);
699 emit downloadProgress(done, total);
700 }
701
cacheCredentialsSlot(const QHttpNetworkRequest & request,QAuthenticator * authenticator)702 void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
703 {
704 authenticationManager->cacheCredentials(request.url(), authenticator);
705 }
706
707
708 #ifndef QT_NO_SSL
encryptedSlot()709 void QHttpThreadDelegate::encryptedSlot()
710 {
711 if (!httpReply)
712 return;
713
714 emit sslConfigurationChanged(httpReply->sslConfiguration());
715 emit encrypted();
716 }
717
sslErrorsSlot(const QList<QSslError> & errors)718 void 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(specificErrors);
732 }
733
preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator * authenticator)734 void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator)
735 {
736 if (!httpReply)
737 return;
738
739 emit preSharedKeyAuthenticationRequired(authenticator);
740 }
741 #endif
742
synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest & request,QAuthenticator * a)743 void 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(httpRequest.url(), 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(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
762 this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
763 }
764
765 #ifndef QT_NO_NETWORKPROXY
synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy & p,QAuthenticator * a)766 void 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(p, 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(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
784 this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
785 #endif
786 }
787
788 #endif
789
790 QT_END_NAMESPACE
791