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(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 | |
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(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 | |
179 | class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection, |
180 | public QNetworkAccessCache::CacheableObject |
181 | { |
182 | // Q_OBJECT |
183 | public: |
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 | |
210 | QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections; |
211 | |
212 | |
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(key: cacheKey); |
224 | } |
225 | } |
226 | |
227 | |
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 |
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(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 |
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(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 |
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(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 | |
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 | |
473 | void 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 | |
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(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 | |
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(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 | |
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(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 | |
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(obj: httpReply, member: "deleteLater" , type: Qt::QueuedConnection); |
598 | QMetaObject::invokeMethod(obj: this, member: "deleteLater" , type: Qt::QueuedConnection); |
599 | httpReply = nullptr; |
600 | } |
601 | |
602 | |
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(obj: httpReply, member: "deleteLater" , type: Qt::QueuedConnection); |
617 | QMetaObject::invokeMethod(obj: synchronousRequestLoop, member: "quit" , type: Qt::QueuedConnection); |
618 | httpReply = nullptr; |
619 | } |
620 | |
621 | static void downloadBufferDeleter(char *ptr) |
622 | { |
623 | delete[] ptr; |
624 | } |
625 | |
626 | void QHttpThreadDelegate::() |
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 | |
673 | void QHttpThreadDelegate::() |
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 | |
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(valueToAdd: 1); |
699 | emit downloadProgress(done, total); |
700 | } |
701 | |
702 | void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator) |
703 | { |
704 | authenticationManager->cacheCredentials(url: request.url(), auth: authenticator); |
705 | } |
706 | |
707 | |
708 | #ifndef QT_NO_SSL |
709 | void QHttpThreadDelegate::encryptedSlot() |
710 | { |
711 | if (!httpReply) |
712 | return; |
713 | |
714 | emit sslConfigurationChanged(httpReply->sslConfiguration()); |
715 | emit encrypted(); |
716 | } |
717 | |
718 | void QHttpThreadDelegate::(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 | |
734 | void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator) |
735 | { |
736 | if (!httpReply) |
737 | return; |
738 | |
739 | emit preSharedKeyAuthenticationRequired(authenticator); |
740 | } |
741 | #endif |
742 | |
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(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 |
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(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 | |
790 | QT_END_NAMESPACE |
791 | |