diff options
Diffstat (limited to 'src/network/access/qhttpnetworkconnection.cpp')
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 369 |
1 files changed, 181 insertions, 188 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index dec354b47f..419491a711 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qhttpnetworkconnection_p.h" #include <private/qabstractsocket_p.h> @@ -49,10 +13,13 @@ #include <qauthenticator.h> #include <qcoreapplication.h> #include <private/qdecompresshelper_p.h> +#include <private/qsocketabstraction_p.h> #include <qbuffer.h> #include <qpair.h> #include <qdebug.h> +#include <qspan.h> +#include <qvarlengtharray.h> #ifndef QT_NO_SSL # include <private/qsslsocket_p.h> @@ -66,7 +33,7 @@ QT_BEGIN_NAMESPACE -const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6; +using namespace Qt::StringLiterals; // The pipeline length. So there will be 4 requests in flight. const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; @@ -74,43 +41,33 @@ const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; // This means that there are 2 requests in flight and 2 slots free that will be re-filled. const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2; - -QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, - quint16 port, bool encrypt, - QHttpNetworkConnection::ConnectionType type) -: state(RunningState), - networkLayerState(Unknown), - hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true) - , activeChannelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 - || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct - ? 1 : defaultHttpChannelCount) - , channelCount(defaultHttpChannelCount) -#ifndef QT_NO_NETWORKPROXY - , networkProxy(QNetworkProxy::NoProxy) -#endif - , preConnectRequests(0) - , connectionType(type) +static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType type, + int defaultValue) { - // We allocate all 6 channels even if it's HTTP/2 enabled connection: - // in case the protocol negotiation via NPN/ALPN fails, we will have - // normally working HTTP/1.1. - Q_ASSERT(channelCount >= activeChannelCount); - channels = new QHttpNetworkConnectionChannel[channelCount]; + return (type == QHttpNetworkConnection::ConnectionTypeHTTP2 + || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) + ? 1 + : defaultValue; } -QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, - quint16 port, bool encrypt, - QHttpNetworkConnection::ConnectionType type) -: state(RunningState), networkLayerState(Unknown), - hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true), - activeChannelCount(connectionCount), channelCount(connectionCount) +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate( + quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, + QHttpNetworkConnection::ConnectionType type) + : hostName(hostName), + port(port), + encrypt(encrypt), + activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)), + channelCount(connectionCount), + channels(new QHttpNetworkConnectionChannel[channelCount]), #ifndef QT_NO_NETWORKPROXY - , networkProxy(QNetworkProxy::NoProxy) + networkProxy(QNetworkProxy::NoProxy), #endif - , preConnectRequests(0) - , connectionType(type) + connectionType(type) { - channels = new QHttpNetworkConnectionChannel[channelCount]; + // We allocate all 6 channels even if it's an HTTP/2-enabled + // connection: in case the protocol negotiation via NPN/ALPN fails, + // we will have normally working HTTP/1.1. + Q_ASSERT(channelCount >= activeChannelCount); } @@ -179,7 +136,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection() QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection); } -int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const +int QHttpNetworkConnectionPrivate::indexOf(QIODevice *socket) const { for (int i = 0; i < activeChannelCount; ++i) if (channels[i].socket == socket) @@ -193,7 +150,7 @@ int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const // emitted. This function will check the status of the connection channels if we // have not decided the networkLayerState and will return true if the channel error // should be emitted by the channel. -bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QAbstractSocket *socket) +bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QIODevice *socket) { Q_Q(QHttpNetworkConnection); @@ -251,6 +208,17 @@ qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const return reply.d_func()->responseData.sizeNextBlock(); } +static QByteArray makeAcceptLanguage() +{ + QString systemLocale = QLocale::system().name(); + if (systemLocale == "C"_L1) + return "en,*"_ba; + systemLocale.replace('_'_L1, '-'_L1); + if (systemLocale.startsWith("en-"_L1)) + return (systemLocale + ",*"_L1).toLatin1(); + return (systemLocale + ",en,*"_L1).toLatin1(); +} + void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) { QHttpNetworkRequest &request = messagePair.first; @@ -258,15 +226,16 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) // add missing fields for the request QByteArray value; +#ifndef Q_OS_WASM // check if Content-Length is provided QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); -#ifndef Q_OS_WASM if (uploadByteDevice) { const qint64 contentLength = request.contentLength(); const qint64 uploadDeviceSize = uploadByteDevice->size(); if (contentLength != -1 && uploadDeviceSize != -1) { - // both values known, take the smaller one. - request.setContentLength(qMin(uploadDeviceSize, contentLength)); + // Both values known: use the smaller one. + if (uploadDeviceSize < contentLength) + request.setContentLength(uploadDeviceSize); } else if (contentLength == -1 && uploadDeviceSize != -1) { // content length not supplied by user, but the upload device knows it request.setContentLength(uploadDeviceSize); @@ -300,8 +269,8 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) value = request.headerField("accept-encoding"); if (value.isEmpty()) { #ifndef QT_NO_COMPRESS - const QByteArrayList &acceptedEncoding = QDecompressHelper::acceptedEncoding(); - request.setHeaderField("Accept-Encoding", acceptedEncoding.join(", ")); + const static QByteArray acceptedEncoding = QDecompressHelper::acceptedEncoding().join(", "); + request.setHeaderField("Accept-Encoding", acceptedEncoding); request.d->autoDecompress = true; #else // if zlib is not available set this to false always @@ -314,17 +283,8 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) // not with us, but we work around this by setting // one always. value = request.headerField("accept-language"); - if (value.isEmpty()) { - QString systemLocale = QLocale::system().name().replace(QChar::fromLatin1('_'),QChar::fromLatin1('-')); - QString acceptLanguage; - if (systemLocale == QLatin1String("C")) - acceptLanguage = QString::fromLatin1("en,*"); - else if (systemLocale.startsWith(QLatin1String("en-"))) - acceptLanguage = systemLocale + QLatin1String(",*"); - else - acceptLanguage = systemLocale + QLatin1String(",en,*"); - request.setHeaderField("Accept-Language", std::move(acceptLanguage).toLatin1()); - } + if (value.isEmpty()) + request.setHeaderField("Accept-Language", makeAcceptLanguage()); // set the User Agent value = request.headerField("user-agent"); @@ -337,7 +297,7 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) QByteArray host; if (add.setAddress(hostName)) { if (add.protocol() == QAbstractSocket::IPv6Protocol) - host = '[' + hostName.toLatin1() + ']'; //format the ipv6 in the standard way + host = (u'[' + hostName + u']').toLatin1(); //format the ipv6 in the standard way else host = hostName.toLatin1(); @@ -360,7 +320,7 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) -void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, +void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode) { @@ -425,7 +385,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica // handles the authentication for one channel and eventually re-starts the other channels -bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, +bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend) { Q_ASSERT(socket); @@ -433,7 +393,7 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket resend = false; //create the response header to be used with QAuthenticatorPrivate. - QList<QPair<QByteArray, QByteArray> > fields = reply->header(); + const auto headers = reply->header(); // Check that any of the proposed authenticate methods are supported const QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate"; @@ -449,12 +409,18 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket if (auth->isNull()) auth->detach(); QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth); - priv->parseHttpResponse(fields, isProxy, reply->url().host()); + priv->parseHttpResponse(headers, isProxy, reply->url().host()); // Update method in case it changed if (priv->method == QAuthenticatorPrivate::None) return false; - if (priv->phase == QAuthenticatorPrivate::Done) { + if (priv->phase == QAuthenticatorPrivate::Done || + (priv->phase == QAuthenticatorPrivate::Start + && (priv->method == QAuthenticatorPrivate::Ntlm + || priv->method == QAuthenticatorPrivate::Negotiate))) { + if (priv->phase == QAuthenticatorPrivate::Start) + priv->phase = QAuthenticatorPrivate::Phase1; + pauseConnection(); if (!isProxy) { if (channels[i].authenticationCredentialsSent) { @@ -520,31 +486,37 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket return false; } -QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply) +// Used by the HTTP1 code-path +QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QIODevice *socket, + QHttpNetworkReply *reply) +{ + ParseRedirectResult result = parseRedirectResponse(reply); + if (result.errorCode != QNetworkReply::NoError) { + emitReplyError(socket, reply, result.errorCode); + return {}; + } + return std::move(result.redirectUrl); +} + +QHttpNetworkConnectionPrivate::ParseRedirectResult +QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply) { if (!reply->request().isFollowRedirects()) - return QUrl(); + return {{}, QNetworkReply::NoError}; QUrl redirectUrl; - const QList<QPair<QByteArray, QByteArray> > fields = reply->header(); - for (const QNetworkReply::RawHeaderPair &header : fields) { - if (header.first.compare("location", Qt::CaseInsensitive) == 0) { - redirectUrl = QUrl::fromEncoded(header.second); - break; - } + const QHttpHeaders fields = reply->header(); + if (const auto h = fields.values(QHttpHeaders::WellKnownHeader::Location); !h.empty()) { + redirectUrl = QUrl::fromEncoded(h.first()); } - // If the location url is invalid/empty, we emit ProtocolUnknownError - if (!redirectUrl.isValid()) { - emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); - return QUrl(); - } + // If the location url is invalid/empty, we return ProtocolUnknownError + if (!redirectUrl.isValid()) + return {{}, QNetworkReply::ProtocolUnknownError}; // Check if we have exceeded max redirects allowed - if (reply->request().redirectCount() <= 0) { - emitReplyError(socket, reply, QNetworkReply::TooManyRedirectsError); - return QUrl(); - } + if (reply->request().redirectCount() <= 0) + return {{}, QNetworkReply::TooManyRedirectsError}; // Resolve the URL if it's relative if (redirectUrl.isRelative()) @@ -552,7 +524,7 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke // Check redirect url protocol const QUrl priorUrl(reply->request().url()); - if (redirectUrl.scheme() == QLatin1String("http") || redirectUrl.scheme() == QLatin1String("https")) { + if (redirectUrl.scheme() == "http"_L1 || redirectUrl.scheme() == "https"_L1) { switch (reply->request().redirectPolicy()) { case QNetworkRequest::NoLessSafeRedirectPolicy: // Here we could handle https->http redirects as InsecureProtocolError. @@ -565,8 +537,7 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke if (priorUrl.host() != redirectUrl.host() || priorUrl.scheme() != redirectUrl.scheme() || priorUrl.port() != redirectUrl.port()) { - emitReplyError(socket, reply, QNetworkReply::InsecureRedirectError); - return QUrl(); + return {{}, QNetworkReply::InsecureRedirectError}; } break; case QNetworkRequest::UserVerifiedRedirectPolicy: @@ -575,13 +546,12 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke Q_ASSERT(!"Unexpected redirect policy"); } } else { - emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); - return QUrl(); + return {{}, QNetworkReply::ProtocolUnknownError}; } - return redirectUrl; + return {std::move(redirectUrl), QNetworkReply::NoError}; } -void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request) +void QHttpNetworkConnectionPrivate::createAuthorization(QIODevice *socket, QHttpNetworkRequest &request) { Q_ASSERT(socket); @@ -591,9 +561,15 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*authenticator); // Send "Authorization" header, but not if it's NTLM and the socket is already authenticated. if (priv && priv->method != QAuthenticatorPrivate::None) { - if ((priv->method != QAuthenticatorPrivate::Ntlm - && request.headerField("Authorization").isEmpty()) - || channel.lastStatus == 401) { + const bool ntlmNego = priv->method == QAuthenticatorPrivate::Ntlm + || priv->method == QAuthenticatorPrivate::Negotiate; + const bool authNeeded = channel.lastStatus == 401; + const bool ntlmNegoOk = ntlmNego && authNeeded + && (priv->phase != QAuthenticatorPrivate::Done + || !channel.authenticationCredentialsSent); + const bool otherOk = + !ntlmNego && (authNeeded || request.headerField("Authorization").isEmpty()); + if (ntlmNegoOk || otherOk) { QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), request.url().host()); request.setHeaderField("Authorization", response); @@ -606,7 +582,13 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, priv = QAuthenticatorPrivate::getPrivate(*authenticator); // Send "Proxy-Authorization" header, but not if it's NTLM and the socket is already authenticated. if (priv && priv->method != QAuthenticatorPrivate::None) { - if (priv->method != QAuthenticatorPrivate::Ntlm || channel.lastStatus == 407) { + const bool ntlmNego = priv->method == QAuthenticatorPrivate::Ntlm + || priv->method == QAuthenticatorPrivate::Negotiate; + const bool proxyAuthNeeded = channel.lastStatus == 407; + const bool ntlmNegoOk = ntlmNego && proxyAuthNeeded + && (priv->phase != QAuthenticatorPrivate::Done || !channel.proxyCredentialsSent); + const bool otherOk = !ntlmNego; + if (ntlmNegoOk || otherOk) { QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), networkProxy.hostName()); request.setHeaderField("Proxy-Authorization", response); @@ -705,7 +687,7 @@ void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair) QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); } -bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket) +bool QHttpNetworkConnectionPrivate::dequeueRequest(QIODevice *socket) { int i = 0; if (socket) @@ -749,8 +731,17 @@ QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest() const return QHttpNetworkRequest(); } +QHttpNetworkReply* QHttpNetworkConnectionPrivate::predictNextRequestsReply() const +{ + if (!highPriorityQueue.isEmpty()) + return highPriorityQueue.last().second; + if (!lowPriorityQueue.isEmpty()) + return lowPriorityQueue.last().second; + return nullptr; +} + // this is called from _q_startNextRequest and when a request has been sent down a socket from the channel -void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) +void QHttpNetworkConnectionPrivate::fillPipeline(QIODevice *socket) { // return fast if there is nothing to pipeline if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) @@ -762,7 +753,7 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) if (channels[i].reply == nullptr) return; - if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.length() >= defaultRePipelineLength)) { + if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.size() >= defaultRePipelineLength)) { return; } @@ -778,7 +769,7 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) return; // check if socket is connected - if (socket->state() != QAbstractSocket::ConnectedState) + if (QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState) return; // check for resendCurrent @@ -803,28 +794,28 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) int lengthBefore; while (!highPriorityQueue.isEmpty()) { - lengthBefore = channels[i].alreadyPipelinedRequests.length(); + lengthBefore = channels[i].alreadyPipelinedRequests.size(); fillPipeline(highPriorityQueue, channels[i]); - if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { + if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) { channels[i].pipelineFlush(); return; } - if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) + if (lengthBefore == channels[i].alreadyPipelinedRequests.size()) break; // did not process anything, now do the low prio queue } while (!lowPriorityQueue.isEmpty()) { - lengthBefore = channels[i].alreadyPipelinedRequests.length(); + lengthBefore = channels[i].alreadyPipelinedRequests.size(); fillPipeline(lowPriorityQueue, channels[i]); - if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { + if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) { channels[i].pipelineFlush(); return; } - if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) + if (lengthBefore == channels[i].alreadyPipelinedRequests.size()) break; // did not process anything } @@ -838,7 +829,7 @@ bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, if (queue.isEmpty()) return true; - for (int i = queue.count() - 1; i >= 0; --i) { + for (int i = queue.size() - 1; i >= 0; --i) { HttpMessagePair messagePair = queue.at(i); const QHttpNetworkRequest &request = messagePair.first; @@ -872,16 +863,15 @@ bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, } -QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket, const QString &extraDetail) +QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket, const QString &extraDetail) { QString errorString; switch (errorCode) { - case QNetworkReply::HostNotFoundError: - if (socket) - errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(socket->peerName()); - else - errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(hostName); + case QNetworkReply::HostNotFoundError: { + const QString peerName = socket ? QSocketAbstraction::socketPeerName(socket) : hostName; + errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(peerName); break; + } case QNetworkReply::ConnectionRefusedError: errorString = QCoreApplication::translate("QHttp", "Connection refused"); break; @@ -905,6 +895,8 @@ QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError e break; case QNetworkReply::SslHandshakeFailedError: errorString = QCoreApplication::translate("QHttp", "SSL handshake failed"); + if (socket) + errorString += ": "_L1 + socket->errorString(); break; case QNetworkReply::TooManyRedirectsError: errorString = QCoreApplication::translate("QHttp", "Too many redirects"); @@ -958,7 +950,7 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) } // is the reply inside the pipeline of this channel already? - for (int j = 0; j < channels[i].alreadyPipelinedRequests.length(); j++) { + for (int j = 0; j < channels[i].alreadyPipelinedRequests.size(); j++) { if (channels[i].alreadyPipelinedRequests.at(j).second == reply) { // Remove that HttpMessagePair channels[i].alreadyPipelinedRequests.removeAt(j); @@ -976,23 +968,22 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) return; } } -#ifndef QT_NO_SSL // is the reply inside the H2 pipeline of this channel already? - QMultiMap<int, HttpMessagePair>::iterator it = channels[i].h2RequestsToSend.begin(); - QMultiMap<int, HttpMessagePair>::iterator end = channels[i].h2RequestsToSend.end(); - for (; it != end; ++it) { - if (it.value().second == reply) { - channels[i].h2RequestsToSend.remove(it.key()); - - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); - return; - } + const auto foundReply = [reply](const HttpMessagePair &pair) { + return pair.second == reply; + }; + auto &seq = channels[i].h2RequestsToSend; + const auto end = seq.cend(); + auto it = std::find_if(seq.cbegin(), end, foundReply); + if (it != end) { + seq.erase(it); + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + return; } -#endif } // remove from the high priority queue if (!highPriorityQueue.isEmpty()) { - for (int j = highPriorityQueue.count() - 1; j >= 0; --j) { + for (int j = highPriorityQueue.size() - 1; j >= 0; --j) { HttpMessagePair messagePair = highPriorityQueue.at(j); if (messagePair.second == reply) { highPriorityQueue.removeAt(j); @@ -1003,7 +994,7 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) } // remove from the low priority queue if (!lowPriorityQueue.isEmpty()) { - for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) { + for (int j = lowPriorityQueue.size() - 1; j >= 0; --j) { HttpMessagePair messagePair = lowPriorityQueue.at(j); if (messagePair.second == reply) { lowPriorityQueue.removeAt(j); @@ -1032,6 +1023,11 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() //resend the necessary ones. for (int i = 0; i < activeChannelCount; ++i) { if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) { + if (!channels[i].socket + || channels[i].socket->state() == QAbstractSocket::UnconnectedState) { + if (!channels[i].ensureConnection()) + continue; + } channels[i].resendCurrent = false; // if this is not possible, error will be emitted and connection terminated @@ -1106,7 +1102,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() // If there is not already any connected channels we need to connect a new one. // We do not pair the channel with the request until we know if it is // connected or not. This is to reuse connected channels before we connect new once. - int queuedRequests = highPriorityQueue.count() + lowPriorityQueue.count(); + int queuedRequests = highPriorityQueue.size() + lowPriorityQueue.size(); // in case we have in-flight preconnect requests and normal requests, // we only need one socket for each (preconnect, normal request) pair @@ -1119,7 +1115,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() if (neededOpenChannels <= 0) return; - QQueue<int> channelsToConnect; + QVarLengthArray<int> channelsToConnect; // use previously used channels first for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) { @@ -1135,7 +1131,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) { - channelsToConnect.enqueue(i); + channelsToConnect.push_back(i); neededOpenChannels--; } } @@ -1145,12 +1141,14 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() if (channels[i].socket) continue; - channelsToConnect.enqueue(i); + channelsToConnect.push_back(i); neededOpenChannels--; } - while (!channelsToConnect.isEmpty()) { - const int channel = channelsToConnect.dequeue(); + auto channelToConnectSpan = QSpan{channelsToConnect}; + while (!channelToConnectSpan.isEmpty()) { + const int channel = channelToConnectSpan.front(); + channelToConnectSpan = channelToConnectSpan.sliced(1); if (networkLayerState == IPv4) channels[channel].networkLayerPreference = QAbstractSocket::IPv4Protocol; @@ -1254,23 +1252,30 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info) networkLayerState = QHttpNetworkConnectionPrivate::IPv6; QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection); } else { + auto lookupError = QNetworkReply::HostNotFoundError; +#ifndef QT_NO_NETWORKPROXY + // if the proxy can lookup hostnames, all hostname lookups except for the lookup of the + // proxy hostname are delegated to the proxy. + auto proxyCapabilities = networkProxy.capabilities() | channels[0].proxy.capabilities(); + if (proxyCapabilities & QNetworkProxy::HostNameLookupCapability) + lookupError = QNetworkReply::ProxyNotFoundError; +#endif if (dequeueRequest(channels[0].socket)) { - emitReplyError(channels[0].socket, channels[0].reply, QNetworkReply::HostNotFoundError); + emitReplyError(channels[0].socket, channels[0].reply, lookupError); networkLayerState = QHttpNetworkConnectionPrivate::Unknown; } else if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { - for (const HttpMessagePair &h2Pair : qAsConst(channels[0].h2RequestsToSend)) { + for (const HttpMessagePair &h2Pair : std::as_const(channels[0].h2RequestsToSend)) { // emit error for all replies QHttpNetworkReply *currentReply = h2Pair.second; Q_ASSERT(currentReply); - emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError); + emitReplyError(channels[0].socket, currentReply, lookupError); } } else { - // Should not happen: we start a host lookup before sending a request, - // so it's natural to have requests either in HTTP/2 queue, or in low/high - // priority queues. - qWarning("QHttpNetworkConnectionPrivate::_q_hostLookupFinished" - " could not de-queue request, failed to report HostNotFoundError"); + // We can end up here if a request has been aborted or otherwise failed (e.g. timeout) + // before the host lookup was finished. + qDebug("QHttpNetworkConnectionPrivate::_q_hostLookupFinished" + " could not de-queue request, failed to report HostNotFoundError"); networkLayerState = QHttpNetworkConnectionPrivate::Unknown; } } @@ -1323,18 +1328,6 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel() channels[1].ensureConnection(); } -QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, - QHttpNetworkConnection::ConnectionType connectionType, QObject *parent) - : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt , connectionType)), parent) -{ - Q_D(QHttpNetworkConnection); - d->init(); - if (QNetworkConnectionMonitor::isEnabled()) { - connect(&d->connectionMonitor, &QNetworkConnectionMonitor::reachabilityChanged, - this, &QHttpNetworkConnection::onlineStateChanged, Qt::QueuedConnection); - } -} - QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QHttpNetworkConnection::ConnectionType connectionType) @@ -1395,7 +1388,7 @@ void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy) d->networkProxy = networkProxy; // update the authenticator if (!d->networkProxy.user().isEmpty()) { - for (int i = 0; i < d->activeChannelCount; ++i) { + for (int i = 0; i < d->channelCount; ++i) { d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user()); d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password()); } @@ -1411,7 +1404,7 @@ QNetworkProxy QHttpNetworkConnection::cacheProxy() const void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy) { Q_D(QHttpNetworkConnection); - for (int i = 0; i < d->activeChannelCount; ++i) + for (int i = 0; i < d->channelCount; ++i) d->channels[i].setProxy(networkProxy); } @@ -1422,9 +1415,9 @@ QNetworkProxy QHttpNetworkConnection::transparentProxy() const } #endif -QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() +QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() const { - Q_D(QHttpNetworkConnection); + Q_D(const QHttpNetworkConnection); return d->connectionType; } @@ -1459,13 +1452,13 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config d->channels[i].setSslConfiguration(config); } -QSharedPointer<QSslContext> QHttpNetworkConnection::sslContext() +std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() const { - Q_D(QHttpNetworkConnection); + Q_D(const QHttpNetworkConnection); return d->sslContext; } -void QHttpNetworkConnection::setSslContext(QSharedPointer<QSslContext> context) +void QHttpNetworkConnection::setSslContext(std::shared_ptr<QSslContext> context) { Q_D(QHttpNetworkConnection); d->sslContext = std::move(context); @@ -1557,12 +1550,12 @@ void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpN pauseConnection(); QHttpNetworkReply *reply; if ((connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 - && (chan->switchedToHttp2 || chan->h2RequestsToSend.count() > 0)) + && (chan->switchedToHttp2 || chan->h2RequestsToSend.size() > 0)) || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { // we choose the reply to emit the proxyAuth signal from somewhat arbitrarily, // but that does not matter because the signal will ultimately be emitted // by the QNetworkAccessManager. - Q_ASSERT(chan->h2RequestsToSend.count() > 0); + Q_ASSERT(chan->h2RequestsToSend.size() > 0); reply = chan->h2RequestsToSend.cbegin().value().second; } else { // HTTP reply = chan->reply; |