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