diff options
Diffstat (limited to 'src/network/access/qhttpnetworkconnection.cpp')
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 205 |
1 files changed, 94 insertions, 111 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index dc9c9f610f..419491a711 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -13,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> @@ -32,56 +35,35 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -// Note: Only used from auto tests, normal usage is via QHttp1Configuration -const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6; - // The pipeline length. So there will be 4 requests in flight. const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; // Only re-fill the pipeline if there's defaultRePipelineLength slots free in the pipeline. // 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), - 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]; - - activeChannelCount = (type == QHttpNetworkConnection::ConnectionTypeHTTP2 || - type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) - ? 1 : connectionCount; // 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. @@ -154,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) @@ -168,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); @@ -226,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; @@ -276,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 @@ -290,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 == "C"_L1) - acceptLanguage = QString::fromLatin1("en,*"); - else if (systemLocale.startsWith("en-"_L1)) - acceptLanguage = systemLocale + ",*"_L1; - else - acceptLanguage = systemLocale + ",en,*"_L1; - 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"); @@ -313,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(); @@ -336,7 +320,7 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) -void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, +void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode) { @@ -401,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); @@ -409,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"; @@ -425,14 +409,15 @@ 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 || (priv->phase == QAuthenticatorPrivate::Start - && priv->method == QAuthenticatorPrivate::Ntlm)) { + && (priv->method == QAuthenticatorPrivate::Ntlm + || priv->method == QAuthenticatorPrivate::Negotiate))) { if (priv->phase == QAuthenticatorPrivate::Start) priv->phase = QAuthenticatorPrivate::Phase1; @@ -502,7 +487,7 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket } // Used by the HTTP1 code-path -QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket, +QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QIODevice *socket, QHttpNetworkReply *reply) { ParseRedirectResult result = parseRedirectResponse(reply); @@ -520,12 +505,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply) 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 return ProtocolUnknownError @@ -569,7 +551,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply) return {std::move(redirectUrl), QNetworkReply::NoError}; } -void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request) +void QHttpNetworkConnectionPrivate::createAuthorization(QIODevice *socket, QHttpNetworkRequest &request) { Q_ASSERT(socket); @@ -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) @@ -759,7 +741,7 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::predictNextRequestsReply() con } // 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()) @@ -787,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 @@ -881,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; @@ -915,7 +896,7 @@ QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError e case QNetworkReply::SslHandshakeFailedError: errorString = QCoreApplication::translate("QHttp", "SSL handshake failed"); if (socket) - errorString += QStringLiteral(": ") + socket->errorString(); + errorString += ": "_L1 + socket->errorString(); break; case QNetworkReply::TooManyRedirectsError: errorString = QCoreApplication::translate("QHttp", "Too many redirects"); @@ -987,19 +968,18 @@ 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()) { @@ -1043,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 @@ -1130,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) { @@ -1146,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--; } } @@ -1156,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; @@ -1265,8 +1252,16 @@ 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) { @@ -1274,7 +1269,7 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info) // 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 { // We can end up here if a request has been aborted or otherwise failed (e.g. timeout) @@ -1333,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) @@ -1432,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; } @@ -1469,9 +1452,9 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config d->channels[i].setSslConfiguration(config); } -std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() +std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() const { - Q_D(QHttpNetworkConnection); + Q_D(const QHttpNetworkConnection); return d->sslContext; } |