| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtNetwork module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| //#define QHTTPTHREADDELEGATE_DEBUG |
| #include "qhttpthreaddelegate_p.h" |
| |
| #include <QThread> |
| #include <QTimer> |
| #include <QAuthenticator> |
| #include <QEventLoop> |
| #include <QCryptographicHash> |
| |
| #include "private/qhttpnetworkreply_p.h" |
| #include "private/qnetworkaccesscache_p.h" |
| #include "private/qnoncontiguousbytedevice_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url) |
| { |
| QNetworkReply::NetworkError code; |
| // we've got an error |
| switch (httpStatusCode) { |
| case 400: // Bad Request |
| code = QNetworkReply::ProtocolInvalidOperationError; |
| break; |
| |
| case 401: // Authorization required |
| code = QNetworkReply::AuthenticationRequiredError; |
| break; |
| |
| case 403: // Access denied |
| code = QNetworkReply::ContentAccessDenied; |
| break; |
| |
| case 404: // Not Found |
| code = QNetworkReply::ContentNotFoundError; |
| break; |
| |
| case 405: // Method Not Allowed |
| code = QNetworkReply::ContentOperationNotPermittedError; |
| break; |
| |
| case 407: |
| code = QNetworkReply::ProxyAuthenticationRequiredError; |
| break; |
| |
| case 409: // Resource Conflict |
| code = QNetworkReply::ContentConflictError; |
| break; |
| |
| case 410: // Content no longer available |
| code = QNetworkReply::ContentGoneError; |
| break; |
| |
| case 418: // I'm a teapot |
| code = QNetworkReply::ProtocolInvalidOperationError; |
| break; |
| |
| case 500: // Internal Server Error |
| code = QNetworkReply::InternalServerError; |
| break; |
| |
| case 501: // Server does not support this functionality |
| code = QNetworkReply::OperationNotImplementedError; |
| break; |
| |
| case 503: // Service unavailable |
| code = QNetworkReply::ServiceUnavailableError; |
| break; |
| |
| default: |
| if (httpStatusCode > 500) { |
| // some kind of server error |
| code = QNetworkReply::UnknownServerError; |
| } else if (httpStatusCode >= 400) { |
| // content error we did not handle above |
| code = QNetworkReply::UnknownContentError; |
| } else { |
| qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"", |
| httpStatusCode, qPrintable(url.toString())); |
| code = QNetworkReply::ProtocolFailure; |
| } |
| } |
| |
| return code; |
| } |
| |
| |
| static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &peerVerifyName) |
| { |
| QString result; |
| QUrl copy = url; |
| QString scheme = copy.scheme(); |
| bool isEncrypted = scheme == QLatin1String("https") |
| || scheme == QLatin1String("preconnect-https"); |
| copy.setPort(copy.port(isEncrypted ? 443 : 80)); |
| if (scheme == QLatin1String("preconnect-http")) { |
| copy.setScheme(QLatin1String("http")); |
| } else if (scheme == QLatin1String("preconnect-https")) { |
| copy.setScheme(QLatin1String("https")); |
| } |
| result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath | |
| QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded); |
| |
| #ifndef QT_NO_NETWORKPROXY |
| if (proxy && proxy->type() != QNetworkProxy::NoProxy) { |
| QUrl key; |
| |
| switch (proxy->type()) { |
| case QNetworkProxy::Socks5Proxy: |
| key.setScheme(QLatin1String("proxy-socks5")); |
| break; |
| |
| case QNetworkProxy::HttpProxy: |
| case QNetworkProxy::HttpCachingProxy: |
| key.setScheme(QLatin1String("proxy-http")); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (!key.scheme().isEmpty()) { |
| const QByteArray obfuscatedPassword = QCryptographicHash::hash(proxy->password().toUtf8(), |
| QCryptographicHash::Sha1).toHex(); |
| key.setUserName(proxy->user()); |
| key.setPassword(QString::fromUtf8(obfuscatedPassword)); |
| key.setHost(proxy->hostName()); |
| key.setPort(proxy->port()); |
| key.setQuery(result); |
| result = key.toString(QUrl::FullyEncoded); |
| } |
| } |
| #else |
| Q_UNUSED(proxy) |
| #endif |
| if (!peerVerifyName.isEmpty()) |
| result += QLatin1Char(':') + peerVerifyName; |
| return "http-connection:" + std::move(result).toLatin1(); |
| } |
| |
| class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection, |
| public QNetworkAccessCache::CacheableObject |
| { |
| // Q_OBJECT |
| public: |
| #ifdef QT_NO_BEARERMANAGEMENT |
| QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, |
| QHttpNetworkConnection::ConnectionType connectionType) |
| : QHttpNetworkConnection(hostName, port, encrypt, connectionType) |
| #else |
| QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, |
| QHttpNetworkConnection::ConnectionType connectionType, |
| QSharedPointer<QNetworkSession> networkSession) |
| : QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/0, |
| std::move(networkSession)) |
| #endif |
| { |
| setExpires(true); |
| setShareable(true); |
| } |
| |
| virtual void dispose() override |
| { |
| #if 0 // sample code; do this right with the API |
| Q_ASSERT(!isWorking()); |
| #endif |
| delete this; |
| } |
| }; |
| |
| |
| QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections; |
| |
| |
| QHttpThreadDelegate::~QHttpThreadDelegate() |
| { |
| // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply |
| if (httpReply) { |
| delete httpReply; |
| } |
| |
| // Get the object cache that stores our QHttpNetworkConnection objects |
| // and release the entry for this QHttpNetworkConnection |
| if (connections.hasLocalData() && !cacheKey.isEmpty()) { |
| connections.localData()->releaseEntry(cacheKey); |
| } |
| } |
| |
| |
| QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) : |
| QObject(parent) |
| , ssl(false) |
| , downloadBufferMaximumSize(0) |
| , readBufferMaxSize(0) |
| , bytesEmitted(0) |
| , pendingDownloadData() |
| , pendingDownloadProgress() |
| , synchronous(false) |
| , incomingStatusCode(0) |
| , isPipeliningUsed(false) |
| , isSpdyUsed(false) |
| , incomingContentLength(-1) |
| , removedContentLength(-1) |
| , incomingErrorCode(QNetworkReply::NoError) |
| , downloadBuffer() |
| , httpConnection(0) |
| , httpReply(0) |
| , synchronousRequestLoop(0) |
| { |
| } |
| |
| // This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread |
| void QHttpThreadDelegate::startRequestSynchronously() |
| { |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId(); |
| #endif |
| synchronous = true; |
| |
| QEventLoop synchronousRequestLoop; |
| this->synchronousRequestLoop = &synchronousRequestLoop; |
| |
| // Worst case timeout |
| QTimer::singleShot(30*1000, this, SLOT(abortRequest())); |
| |
| QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection); |
| synchronousRequestLoop.exec(); |
| |
| connections.localData()->releaseEntry(cacheKey); |
| connections.setLocalData(0); |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished"; |
| #endif |
| } |
| |
| |
| // This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread |
| void QHttpThreadDelegate::startRequest() |
| { |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId(); |
| #endif |
| // Check QThreadStorage for the QNetworkAccessCache |
| // If not there, create this connection cache |
| if (!connections.hasLocalData()) { |
| connections.setLocalData(new QNetworkAccessCache()); |
| } |
| |
| // check if we have an open connection to this host |
| QUrl urlCopy = httpRequest.url(); |
| urlCopy.setPort(urlCopy.port(ssl ? 443 : 80)); |
| |
| QHttpNetworkConnection::ConnectionType connectionType |
| = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2 |
| : QHttpNetworkConnection::ConnectionTypeHTTP; |
| if (httpRequest.isHTTP2Direct()) { |
| Q_ASSERT(!httpRequest.isHTTP2Allowed()); |
| connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct; |
| } |
| |
| #if QT_CONFIG(ssl) |
| // See qnetworkreplyhttpimpl, delegate's initialization code. |
| Q_ASSERT(!ssl || incomingSslConfiguration.data()); |
| #endif // QT_CONFIG(ssl) |
| |
| const bool isH2 = httpRequest.isHTTP2Allowed() || httpRequest.isHTTP2Direct(); |
| if (isH2) { |
| #if QT_CONFIG(ssl) |
| if (ssl) { |
| if (!httpRequest.isHTTP2Direct()) { |
| QList<QByteArray> protocols; |
| protocols << QSslConfiguration::ALPNProtocolHTTP2 |
| << QSslConfiguration::NextProtocolHttp1_1; |
| incomingSslConfiguration->setAllowedNextProtocols(protocols); |
| } |
| urlCopy.setScheme(QStringLiteral("h2s")); |
| } else |
| #endif // QT_CONFIG(ssl) |
| { |
| urlCopy.setScheme(QStringLiteral("h2")); |
| } |
| } |
| |
| #ifndef QT_NO_SSL |
| if (!isH2 && httpRequest.isSPDYAllowed() && ssl) { |
| connectionType = QHttpNetworkConnection::ConnectionTypeSPDY; |
| urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests |
| QList<QByteArray> nextProtocols; |
| nextProtocols << QSslConfiguration::NextProtocolSpdy3_0 |
| << QSslConfiguration::NextProtocolHttp1_1; |
| incomingSslConfiguration->setAllowedNextProtocols(nextProtocols); |
| } |
| #endif // QT_NO_SSL |
| |
| #ifndef QT_NO_NETWORKPROXY |
| if (transparentProxy.type() != QNetworkProxy::NoProxy) |
| cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName()); |
| else if (cacheProxy.type() != QNetworkProxy::NoProxy) |
| cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName()); |
| else |
| #endif |
| cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName()); |
| |
| // the http object is actually a QHttpNetworkConnection |
| httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey)); |
| if (!httpConnection) { |
| // no entry in cache; create an object |
| // the http object is actually a QHttpNetworkConnection |
| #ifdef QT_NO_BEARERMANAGEMENT |
| httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, |
| connectionType); |
| #else |
| httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, |
| connectionType, |
| networkSession); |
| #endif // QT_NO_BEARERMANAGEMENT |
| if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 |
| || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { |
| httpConnection->setHttp2Parameters(http2Parameters); |
| } |
| #ifndef QT_NO_SSL |
| // Set the QSslConfiguration from this QNetworkRequest. |
| if (ssl) |
| httpConnection->setSslConfiguration(*incomingSslConfiguration); |
| #endif |
| |
| #ifndef QT_NO_NETWORKPROXY |
| httpConnection->setTransparentProxy(transparentProxy); |
| httpConnection->setCacheProxy(cacheProxy); |
| #endif |
| httpConnection->setPeerVerifyName(httpRequest.peerVerifyName()); |
| // cache the QHttpNetworkConnection corresponding to this cache key |
| connections.localData()->addEntry(cacheKey, httpConnection); |
| } else { |
| if (httpRequest.withCredentials()) { |
| QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr); |
| if (!credential.user.isEmpty() && !credential.password.isEmpty()) { |
| QAuthenticator auth; |
| auth.setUser(credential.user); |
| auth.setPassword(credential.password); |
| httpConnection->d_func()->copyCredentials(-1, &auth, false); |
| } |
| } |
| } |
| |
| // Send the request to the connection |
| httpReply = httpConnection->sendRequest(httpRequest); |
| httpReply->setParent(this); |
| |
| // Connect the reply signals that we need to handle and then forward |
| if (synchronous) { |
| connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot())); |
| connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot())); |
| connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), |
| this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString))); |
| |
| connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), |
| this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*))); |
| #ifndef QT_NO_NETWORKPROXY |
| connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), |
| this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*))); |
| #endif |
| |
| // Don't care about ignored SSL errors for now in the synchronous HTTP case. |
| } else if (!synchronous) { |
| connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot())); |
| connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot())); |
| connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), |
| this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString))); |
| // some signals are only interesting when normal asynchronous style is used |
| connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot())); |
| connect(httpReply,SIGNAL(dataReadProgress(qint64,qint64)), this, SLOT(dataReadProgressSlot(qint64,qint64))); |
| #ifndef QT_NO_SSL |
| connect(httpReply,SIGNAL(encrypted()), this, SLOT(encryptedSlot())); |
| connect(httpReply,SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>))); |
| connect(httpReply,SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), |
| this, SLOT(preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator*))); |
| #endif |
| |
| // In the asynchronous HTTP case we can just forward those signals |
| // Connect the reply signals that we can directly forward |
| connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), |
| this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*))); |
| #ifndef QT_NO_NETWORKPROXY |
| connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), |
| this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); |
| #endif |
| } |
| |
| connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)), |
| this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*))); |
| } |
| |
| // This gets called from the user thread or by the synchronous HTTP timeout timer |
| void QHttpThreadDelegate::abortRequest() |
| { |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous; |
| #endif |
| if (httpReply) { |
| httpReply->abort(); |
| delete httpReply; |
| httpReply = 0; |
| } |
| |
| // Got aborted by the timeout timer |
| if (synchronous) { |
| incomingErrorCode = QNetworkReply::TimeoutError; |
| QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection); |
| } else { |
| //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest() |
| this->deleteLater(); |
| } |
| } |
| |
| void QHttpThreadDelegate::readBufferSizeChanged(qint64 size) |
| { |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::readBufferSizeChanged() size " << size; |
| #endif |
| if (httpReply) { |
| httpReply->setDownstreamLimited(size > 0); |
| httpReply->setReadBufferSize(size); |
| readBufferMaxSize = size; |
| } |
| } |
| |
| void QHttpThreadDelegate::readBufferFreed(qint64 size) |
| { |
| if (readBufferMaxSize) { |
| bytesEmitted -= size; |
| |
| QMetaObject::invokeMethod(this, "readyReadSlot", Qt::QueuedConnection); |
| } |
| } |
| |
| void QHttpThreadDelegate::readyReadSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| // Don't do in zerocopy case |
| if (!downloadBuffer.isNull()) |
| return; |
| |
| if (readBufferMaxSize) { |
| if (bytesEmitted < readBufferMaxSize) { |
| qint64 sizeEmitted = 0; |
| while (httpReply->readAnyAvailable() && (sizeEmitted < (readBufferMaxSize-bytesEmitted))) { |
| if (httpReply->sizeNextBlock() > (readBufferMaxSize-bytesEmitted)) { |
| sizeEmitted = readBufferMaxSize-bytesEmitted; |
| bytesEmitted += sizeEmitted; |
| pendingDownloadData->fetchAndAddRelease(1); |
| emit downloadData(httpReply->read(sizeEmitted)); |
| } else { |
| sizeEmitted = httpReply->sizeNextBlock(); |
| bytesEmitted += sizeEmitted; |
| pendingDownloadData->fetchAndAddRelease(1); |
| emit downloadData(httpReply->readAny()); |
| } |
| } |
| } else { |
| // We need to wait until we empty data from the read buffer in the reply. |
| } |
| |
| } else { |
| while (httpReply->readAnyAvailable()) { |
| pendingDownloadData->fetchAndAddRelease(1); |
| emit downloadData(httpReply->readAny()); |
| } |
| } |
| } |
| |
| void QHttpThreadDelegate::finishedSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode(); |
| #endif |
| |
| // If there is still some data left emit that now |
| while (httpReply->readAnyAvailable()) { |
| pendingDownloadData->fetchAndAddRelease(1); |
| emit downloadData(httpReply->readAny()); |
| } |
| |
| #ifndef QT_NO_SSL |
| if (ssl) |
| emit sslConfigurationChanged(httpReply->sslConfiguration()); |
| #endif |
| |
| if (httpReply->statusCode() >= 400) { |
| // it's an error reply |
| QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply", |
| "Error transferring %1 - server replied: %2")); |
| msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase()); |
| emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg); |
| } |
| |
| if (httpRequest.isFollowRedirects() && httpReply->isRedirecting()) |
| emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1); |
| |
| emit downloadFinished(); |
| |
| QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection); |
| QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); |
| httpReply = 0; |
| } |
| |
| void QHttpThreadDelegate::synchronousFinishedSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode(); |
| #endif |
| if (httpReply->statusCode() >= 400) { |
| // it's an error reply |
| QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply", |
| "Error transferring %1 - server replied: %2")); |
| incomingErrorDetail = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase()); |
| incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()); |
| } |
| |
| synchronousDownloadData = httpReply->readAll(); |
| |
| QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection); |
| QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection); |
| httpReply = 0; |
| } |
| |
| void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail) |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail; |
| #endif |
| |
| #ifndef QT_NO_SSL |
| if (ssl) |
| emit sslConfigurationChanged(httpReply->sslConfiguration()); |
| #endif |
| emit error(errorCode,detail); |
| emit downloadFinished(); |
| |
| |
| QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection); |
| QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); |
| httpReply = 0; |
| } |
| |
| |
| void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail) |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail; |
| #endif |
| incomingErrorCode = errorCode; |
| incomingErrorDetail = detail; |
| |
| synchronousDownloadData = httpReply->readAll(); |
| |
| QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection); |
| QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection); |
| httpReply = 0; |
| } |
| |
| static void downloadBufferDeleter(char *ptr) |
| { |
| delete[] ptr; |
| } |
| |
| void QHttpThreadDelegate::headerChangedSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId(); |
| #endif |
| |
| #ifndef QT_NO_SSL |
| if (ssl) |
| emit sslConfigurationChanged(httpReply->sslConfiguration()); |
| #endif |
| |
| // Is using a zerocopy buffer allowed by user and possible with this reply? |
| if (httpReply->supportsUserProvidedDownloadBuffer() |
| && (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) { |
| QT_TRY { |
| char *buf = new char[httpReply->contentLength()]; // throws if allocation fails |
| if (buf) { |
| downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter); |
| httpReply->setUserProvidedDownloadBuffer(buf); |
| } |
| } QT_CATCH(const std::bad_alloc &) { |
| // in out of memory situations, don't use downloadbuffer. |
| } |
| } |
| |
| // We fetch this into our own |
| incomingHeaders = httpReply->header(); |
| incomingStatusCode = httpReply->statusCode(); |
| incomingReasonPhrase = httpReply->reasonPhrase(); |
| isPipeliningUsed = httpReply->isPipeliningUsed(); |
| incomingContentLength = httpReply->contentLength(); |
| removedContentLength = httpReply->removedContentLength(); |
| isSpdyUsed = httpReply->isSpdyUsed(); |
| |
| emit downloadMetaData(incomingHeaders, |
| incomingStatusCode, |
| incomingReasonPhrase, |
| isPipeliningUsed, |
| downloadBuffer, |
| incomingContentLength, |
| removedContentLength, |
| isSpdyUsed); |
| } |
| |
| void QHttpThreadDelegate::synchronousHeaderChangedSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId(); |
| #endif |
| // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it |
| incomingHeaders = httpReply->header(); |
| incomingStatusCode = httpReply->statusCode(); |
| incomingReasonPhrase = httpReply->reasonPhrase(); |
| isPipeliningUsed = httpReply->isPipeliningUsed(); |
| isSpdyUsed = httpReply->isSpdyUsed(); |
| incomingContentLength = httpReply->contentLength(); |
| } |
| |
| |
| void QHttpThreadDelegate::dataReadProgressSlot(qint64 done, qint64 total) |
| { |
| // If we don't have a download buffer don't attempt to go this codepath |
| // It is not used by QNetworkAccessHttpBackend |
| if (downloadBuffer.isNull()) |
| return; |
| |
| pendingDownloadProgress->fetchAndAddRelease(1); |
| emit downloadProgress(done, total); |
| } |
| |
| void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator) |
| { |
| authenticationManager->cacheCredentials(request.url(), authenticator); |
| } |
| |
| |
| #ifndef QT_NO_SSL |
| void QHttpThreadDelegate::encryptedSlot() |
| { |
| if (!httpReply) |
| return; |
| |
| emit sslConfigurationChanged(httpReply->sslConfiguration()); |
| emit encrypted(); |
| } |
| |
| void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors) |
| { |
| if (!httpReply) |
| return; |
| |
| emit sslConfigurationChanged(httpReply->sslConfiguration()); |
| |
| bool ignoreAll = false; |
| QList<QSslError> specificErrors; |
| emit sslErrors(errors, &ignoreAll, &specificErrors); |
| if (ignoreAll) |
| httpReply->ignoreSslErrors(); |
| if (!specificErrors.isEmpty()) |
| httpReply->ignoreSslErrors(specificErrors); |
| } |
| |
| void QHttpThreadDelegate::preSharedKeyAuthenticationRequiredSlot(QSslPreSharedKeyAuthenticator *authenticator) |
| { |
| if (!httpReply) |
| return; |
| |
| emit preSharedKeyAuthenticationRequired(authenticator); |
| } |
| #endif |
| |
| void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a) |
| { |
| if (!httpReply) |
| return; |
| |
| Q_UNUSED(request); |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId(); |
| #endif |
| |
| // Ask the credential cache |
| QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a); |
| if (!credential.isNull()) { |
| a->setUser(credential.user); |
| a->setPassword(credential.password); |
| } |
| |
| // Disconnect this connection now since we only want to ask the authentication cache once. |
| QObject::disconnect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), |
| this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*))); |
| } |
| |
| #ifndef QT_NO_NETWORKPROXY |
| void QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a) |
| { |
| if (!httpReply) |
| return; |
| |
| #ifdef QHTTPTHREADDELEGATE_DEBUG |
| qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId(); |
| #endif |
| // Ask the credential cache |
| QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a); |
| if (!credential.isNull()) { |
| a->setUser(credential.user); |
| a->setPassword(credential.password); |
| } |
| |
| #ifndef QT_NO_NETWORKPROXY |
| // Disconnect this connection now since we only want to ask the authentication cache once. |
| QObject::disconnect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), |
| this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*))); |
| #endif |
| } |
| |
| #endif |
| |
| QT_END_NAMESPACE |