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