Revision control

Copy as Markdown

// 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
// Qt-Security score:critical reason:network-protocol
#include "qhttpnetworkconnection_p.h"
#include <private/qabstractsocket_p.h>
#include "qhttpnetworkconnectionchannel_p.h"
#include <private/qhttp2protocolhandler_p.h>
#include "private/qnoncontiguousbytedevice_p.h"
#include <private/qnetworkrequest_p.h>
#include <private/qobject_p.h>
#include <private/qauthenticator_p.h>
#include "private/qhostinfo_p.h"
#include <qnetworkproxy.h>
#include <qauthenticator.h>
#include <qcoreapplication.h>
#include <private/qdecompresshelper_p.h>
#include <private/qsocketabstraction_p.h>
#include <qbuffer.h>
#include <qdebug.h>
#include <qspan.h>
#include <qvarlengtharray.h>
#ifndef QT_NO_SSL
# include <private/qsslsocket_p.h>
# include <QtNetwork/qsslkey.h>
# include <QtNetwork/qsslcipher.h>
# include <QtNetwork/qsslconfiguration.h>
# include <QtNetwork/qsslerror.h>
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// 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;
static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType type,
int defaultValue)
{
return (type == QHttpNetworkConnection::ConnectionTypeHTTP2
|| type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct)
? 1
: defaultValue;
}
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
: hostName(hostName),
port(port),
encrypt(encrypt),
isLocalSocket(isLocalSocket),
activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
channelCount(connectionCount),
channels(new QHttpNetworkConnectionChannel[channelCount]),
#ifndef QT_NO_NETWORKPROXY
networkProxy(QNetworkProxy::NoProxy),
#endif
connectionType(type)
{
if (isLocalSocket) // Don't try to do host lookup for local sockets
networkLayerState = IPv4;
// 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);
}
QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
{
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
QObject::disconnect(channels[i].socket, nullptr, &channels[i], nullptr);
channels[i].socket->close();
delete channels[i].socket;
}
}
delete []channels;
}
void QHttpNetworkConnectionPrivate::init()
{
Q_Q(QHttpNetworkConnection);
for (int i = 0; i < channelCount; i++) {
channels[i].setConnection(this->q_func());
channels[i].ssl = encrypt;
}
delayedConnectionTimer.setSingleShot(true);
QObject::connect(&delayedConnectionTimer, SIGNAL(timeout()), q, SLOT(_q_connectDelayedChannel()));
}
void QHttpNetworkConnectionPrivate::pauseConnection()
{
state = PausedState;
// Disable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
QAbstractSocketPrivate::pauseSocketNotifiers(absSocket);
#if QT_CONFIG(localserver)
} else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
// @todo how would we do this?
#if 0 // @todo Enable this when there is a debug category for this
qDebug() << "Should pause socket but there is no way to do it for local sockets";
#endif
#endif
}
}
}
void QHttpNetworkConnectionPrivate::resumeConnection()
{
state = RunningState;
// Enable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
QAbstractSocketPrivate::resumeSocketNotifiers(absSocket);
// Resume pending upload if needed
if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
#if QT_CONFIG(localserver)
} else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
#if 0 // @todo Enable this when there is a debug category for this
qDebug() << "Should resume socket but there is no way to do it for local sockets";
#endif
#endif
}
}
// queue _q_startNextRequest
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
}
int QHttpNetworkConnectionPrivate::indexOf(QIODevice *socket) const
{
for (int i = 0; i < activeChannelCount; ++i)
if (channels[i].socket == socket)
return i;
qFatal("Called with unknown socket object.");
return 0;
}
// If the connection is in the HostLookupPendening state channel errors should not always be
// 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(QIODevice *socket)
{
Q_Q(QHttpNetworkConnection);
bool emitError = true;
int i = indexOf(socket);
int otherSocket = (i == 0 ? 1 : 0);
// If the IPv4 connection still isn't started we need to start it now.
if (delayedConnectionTimer.isActive()) {
delayedConnectionTimer.stop();
channels[otherSocket].ensureConnection();
}
if (activeChannelCount < channelCount) {
if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6)
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
channels[0].close();
emitError = true;
} else {
if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6) {
if (channels[otherSocket].isSocketBusy() && (channels[otherSocket].state != QHttpNetworkConnectionChannel::ClosingState)) {
// this was the first socket to fail.
channels[i].close();
emitError = false;
}
else {
// Both connection attempts has failed.
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
channels[i].close();
emitError = true;
}
} else {
if (((networkLayerState == QHttpNetworkConnectionPrivate::IPv4) && (channels[i].networkLayerPreference != QAbstractSocket::IPv4Protocol))
|| ((networkLayerState == QHttpNetworkConnectionPrivate::IPv6) && (channels[i].networkLayerPreference != QAbstractSocket::IPv6Protocol))) {
// First connection worked so this is the second one to complete and it failed.
channels[i].close();
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
emitError = false;
}
if (networkLayerState == QHttpNetworkConnectionPrivate::Unknown)
qWarning("We got a connection error when networkLayerState is Unknown");
}
}
return emitError;
}
qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailable(const QHttpNetworkReply &reply) const
{
return reply.d_func()->responseData.byteAmount();
}
qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) 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();
}
static QStringView removeZoneId(QStringView ipv6HostAddress)
{
const auto zoneIdentfierIndex = ipv6HostAddress.indexOf(u'%');
// Only perform a minimal sanity check, as at this point the
// ipv6HostAddress was already used successfully to establish the connection.
if (zoneIdentfierIndex == -1) {
return ipv6HostAddress;
}
return ipv6HostAddress.left(zoneIdentfierIndex);
}
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
{
QHttpNetworkRequest &request = messagePair.first;
QHttpNetworkReply *reply = messagePair.second;
// add missing fields for the request
QByteArray value;
#ifndef Q_OS_WASM
// check if Content-Length is provided
QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
if (uploadByteDevice) {
const qint64 contentLength = request.contentLength();
const qint64 uploadDeviceSize = uploadByteDevice->size();
if (contentLength != -1 && uploadDeviceSize != -1) {
// 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);
} else if (contentLength != -1 && uploadDeviceSize == -1) {
// everything OK, the user supplied us the contentLength
} else if (Q_UNLIKELY(contentLength == -1 && uploadDeviceSize == -1)) {
qFatal("QHttpNetworkConnectionPrivate: Neither content-length nor upload device size were given");
}
}
#endif
// set the Connection/Proxy-Connection: Keep-Alive headers
#ifndef QT_NO_NETWORKPROXY
if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) {
value = request.headerField("proxy-connection");
if (value.isEmpty())
request.setHeaderField("Proxy-Connection", "Keep-Alive");
} else {
#endif
value = request.headerField("connection");
if (value.isEmpty())
request.setHeaderField("Connection", "Keep-Alive");
#ifndef QT_NO_NETWORKPROXY
}
#endif
// If the request had a accept-encoding set, we better not mess
// with it. If it was not set, we announce that we understand gzip
// and remember this fact in request.d->autoDecompress so that
// we can later decompress the HTTP reply if it has such an
// encoding.
value = request.headerField("accept-encoding");
if (value.isEmpty()) {
#ifndef QT_NO_COMPRESS
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
request.d->autoDecompress = false;
#endif
}
// some websites mandate an accept-language header and fail
// if it is not sent. This is a problem with the website and
// not with us, but we work around this by setting
// one always.
value = request.headerField("accept-language");
if (value.isEmpty())
request.setHeaderField("Accept-Language", makeAcceptLanguage());
// set the User Agent
value = request.headerField("user-agent");
if (value.isEmpty())
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
if (isLocalSocket && value.isEmpty()) {
// The local socket connections might have a full file path, and that
// may not be suitable for the Host header. But we can use whatever the
// user has set in the URL.
request.prependHeaderField("Host", request.url().host().toLocal8Bit());
} else if (value.isEmpty()) {
QHostAddress add;
QByteArray host;
if (add.setAddress(hostName)) {
if (add.protocol() == QAbstractSocket::IPv6Protocol)
host = (u'[' + removeZoneId(hostName) + u']').toLatin1(); //format the ipv6 in the standard way
else
host = hostName.toLatin1();
} else {
host = QUrl::toAce(hostName);
}
int port = request.url().port();
if (port != -1) {
host += ':';
host += QByteArray::number(port);
}
request.prependHeaderField("Host", host);
}
reply->d_func()->requestIsPrepared = true;
}
void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket,
QHttpNetworkReply *reply,
QNetworkReply::NetworkError errorCode)
{
Q_Q(QHttpNetworkConnection);
int i = 0;
if (socket)
i = indexOf(socket);
if (reply) {
// this error matters only to this reply
reply->d_func()->errorString = errorDetail(errorCode, socket);
emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
// remove the corrupt data if any
reply->d_func()->eraseData();
// Clean the channel
channels[i].close();
channels[i].reply = nullptr;
if (channels[i].protocolHandler)
channels[i].protocolHandler->setReply(nullptr);
channels[i].request = QHttpNetworkRequest();
if (socket)
channels[i].requeueCurrentlyPipelinedRequests();
// send the next request
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
}
}
void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy)
{
Q_ASSERT(auth);
// NTLM and Negotiate do multi-phase authentication.
// Copying credentialsbetween authenticators would mess things up.
if (fromChannel >= 0) {
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
if (priv
&& (priv->method == QAuthenticatorPrivate::Ntlm
|| priv->method == QAuthenticatorPrivate::Negotiate)) {
return;
}
}
// select another channel
QAuthenticator* otherAuth = nullptr;
for (int i = 0; i < activeChannelCount; ++i) {
if (i == fromChannel)
continue;
if (isProxy)
otherAuth = &channels[i].proxyAuthenticator;
else
otherAuth = &channels[i].authenticator;
// if the credentials are different, copy them
if (otherAuth->user().compare(auth->user()))
otherAuth->setUser(auth->user());
if (otherAuth->password().compare(auth->password()))
otherAuth->setPassword(auth->password());
}
}
// handles the authentication for one channel and eventually re-starts the other channels
bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply,
bool isProxy, bool &resend)
{
Q_ASSERT(socket);
Q_ASSERT(reply);
resend = false;
//create the response header to be used with QAuthenticatorPrivate.
const auto headers = reply->header();
// Check that any of the proposed authenticate methods are supported
const QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate";
const QByteArrayList &authenticationMethods = reply->d_func()->headerFieldValues(header);
const bool isSupported = std::any_of(authenticationMethods.begin(), authenticationMethods.end(),
QAuthenticatorPrivate::isMethodSupported);
if (isSupported) {
int i = indexOf(socket);
//Use a single authenticator for all domains. ### change later to use domain/realm
QAuthenticator *auth = isProxy ? &channels[i].proxyAuthenticator
: &channels[i].authenticator;
//proceed with the authentication.
if (auth->isNull())
auth->detach();
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
priv->parseHttpResponse(headers, isProxy);
// 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::Negotiate))) {
if (priv->phase == QAuthenticatorPrivate::Start)
priv->phase = QAuthenticatorPrivate::Phase1;
pauseConnection();
if (!isProxy) {
if (channels[i].authenticationCredentialsSent) {
auth->detach();
priv = QAuthenticatorPrivate::getPrivate(*auth);
priv->hasFailed = true;
priv->phase = QAuthenticatorPrivate::Done;
channels[i].authenticationCredentialsSent = false;
}
emit reply->authenticationRequired(reply->request(), auth);
#ifndef QT_NO_NETWORKPROXY
} else {
if (channels[i].proxyCredentialsSent) {
auth->detach();
priv = QAuthenticatorPrivate::getPrivate(*auth);
priv->hasFailed = true;
priv->phase = QAuthenticatorPrivate::Done;
channels[i].proxyCredentialsSent = false;
}
emit reply->proxyAuthenticationRequired(networkProxy, auth);
#endif
}
resumeConnection();
if (priv->phase != QAuthenticatorPrivate::Done) {
// send any pending requests
copyCredentials(i, auth, isProxy);
}
} else if (priv->phase == QAuthenticatorPrivate::Start) {
// If the url's authenticator has a 'user' set we will end up here (phase is only set to 'Done' by
// parseHttpResponse above if 'user' is empty). So if credentials were supplied with the request,
// such as in the case of an XMLHttpRequest, this is our only opportunity to cache them.
emit reply->cacheCredentials(reply->request(), auth);
}
// - Changing values in QAuthenticator will reset the 'phase'. Therefore if it is still "Done"
// then nothing was filled in by the user or the cache
// - If withCredentials has been set to false (e.g. by Qt WebKit for a cross-origin XMLHttpRequest) then
// we need to bail out if authentication is required.
if (priv->phase == QAuthenticatorPrivate::Done || !reply->request().withCredentials()) {
// Reset authenticator so the next request on that channel does not get messed up
auth = nullptr;
if (isProxy)
channels[i].proxyAuthenticator = QAuthenticator();
else
channels[i].authenticator = QAuthenticator();
// authentication is cancelled, send the current contents to the user.
emit reply->headerChanged();
emit reply->readyRead();
QNetworkReply::NetworkError errorCode =
isProxy
? QNetworkReply::ProxyAuthenticationRequiredError
: QNetworkReply::AuthenticationRequiredError;
reply->d_func()->errorString = errorDetail(errorCode, socket);
emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
// ### at this point the reply could be deleted
return true;
}
//resend the request
resend = true;
return true;
}
return false;
}
// 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 {{}, QNetworkReply::NoError};
QUrl redirectUrl;
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
if (!redirectUrl.isValid())
return {{}, QNetworkReply::ProtocolUnknownError};
// Check if we have exceeded max redirects allowed
if (reply->request().redirectCount() <= 0)
return {{}, QNetworkReply::TooManyRedirectsError};
// Resolve the URL if it's relative
if (redirectUrl.isRelative())
redirectUrl = reply->request().url().resolved(redirectUrl);
// Check redirect url protocol
const QUrl priorUrl(reply->request().url());
const QString targetUrlScheme = redirectUrl.scheme();
if (targetUrlScheme == "http"_L1 || targetUrlScheme == "https"_L1
|| targetUrlScheme.startsWith("unix"_L1)) {
switch (reply->request().redirectPolicy()) {
case QNetworkRequest::NoLessSafeRedirectPolicy:
// Here we could handle https->http redirects as InsecureProtocolError.
// However, if HSTS is enabled and redirectUrl.host() is a known STS
// host, then we'll replace its scheme and this won't downgrade protocol,
// after all. We cannot access QNAM's STS cache from here, so delegate
// this check to QNetworkReplyHttpImpl.
break;
case QNetworkRequest::SameOriginRedirectPolicy:
if (priorUrl.host() != redirectUrl.host()
|| priorUrl.scheme() != targetUrlScheme
|| priorUrl.port() != redirectUrl.port()) {
return {{}, QNetworkReply::InsecureRedirectError};
}
break;
case QNetworkRequest::UserVerifiedRedirectPolicy:
break;
default:
Q_ASSERT(!"Unexpected redirect policy");
}
} else {
return {{}, QNetworkReply::ProtocolUnknownError};
}
return {std::move(redirectUrl), QNetworkReply::NoError};
}
void QHttpNetworkConnectionPrivate::createAuthorization(QIODevice *socket, QHttpNetworkRequest &request)
{
Q_ASSERT(socket);
QHttpNetworkConnectionChannel &channel = channels[indexOf(socket)];
QAuthenticator *authenticator = &channel.authenticator;
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) {
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);
channel.authenticationCredentialsSent = true;
}
}
#if QT_CONFIG(networkproxy)
authenticator = &channel.proxyAuthenticator;
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) {
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);
channel.proxyCredentialsSent = true;
}
}
#endif // QT_CONFIG(networkproxy)
}
QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request)
{
Q_Q(QHttpNetworkConnection);
// The reply component of the pair is created initially.
QHttpNetworkReply *reply = new QHttpNetworkReply(request.url());
reply->setRequest(request);
reply->d_func()->connection = q;
reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later
HttpMessagePair pair = std::pair(request, reply);
if (request.isPreConnect())
preConnectRequests++;
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP
|| (!encrypt && connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 && !channels[0].switchedToHttp2)) {
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
break;
case QHttpNetworkRequest::NormalPriority:
case QHttpNetworkRequest::LowPriority:
lowPriorityQueue.prepend(pair);
break;
}
}
else { // HTTP/2 ('h2' mode)
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].h2RequestsToSend.insert(request.priority(), pair);
}
// For Happy Eyeballs the networkLayerState is set to Unknown
// until we have started the first connection attempt. So no
// request will be started until we know if IPv4 or IPv6
// should be used.
if (networkLayerState == Unknown || networkLayerState == HostLookupPending) {
startHostInfoLookup();
} else if ( networkLayerState == IPv4 || networkLayerState == IPv6 ) {
// this used to be called via invokeMethod and a QueuedConnection
// It is the only place _q_startNextRequest is called directly without going
// through the event loop using a QueuedConnection.
// This is dangerous because of recursion that might occur when emitting
// signals as DirectConnection from this code path. Therefore all signal
// emissions that can come out from this code path need to
// be QueuedConnection.
// We are currently trying to fine-tune this.
_q_startNextRequest();
}
return reply;
}
void QHttpNetworkConnectionPrivate::fillHttp2Queue()
{
for (auto &pair : highPriorityQueue) {
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].h2RequestsToSend.insert(QHttpNetworkRequest::HighPriority, pair);
}
highPriorityQueue.clear();
for (auto &pair : lowPriorityQueue) {
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].h2RequestsToSend.insert(pair.first.priority(), pair);
}
lowPriorityQueue.clear();
}
void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair)
{
Q_Q(QHttpNetworkConnection);
QHttpNetworkRequest request = pair.first;
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
break;
case QHttpNetworkRequest::NormalPriority:
case QHttpNetworkRequest::LowPriority:
lowPriorityQueue.prepend(pair);
break;
}
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
}
bool QHttpNetworkConnectionPrivate::dequeueRequest(QIODevice *socket)
{
int i = 0;
if (socket)
i = indexOf(socket);
if (!highPriorityQueue.isEmpty()) {
// remove from queue before sendRequest! else we might pipeline the same request again
HttpMessagePair messagePair = highPriorityQueue.takeLast();
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
updateChannel(i, messagePair);
return true;
}
if (!lowPriorityQueue.isEmpty()) {
// remove from queue before sendRequest! else we might pipeline the same request again
HttpMessagePair messagePair = lowPriorityQueue.takeLast();
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
updateChannel(i, messagePair);
return true;
}
return false;
}
void QHttpNetworkConnectionPrivate::updateChannel(int i, const HttpMessagePair &messagePair)
{
channels[i].request = messagePair.first;
channels[i].reply = messagePair.second;
// Now that reply is assigned a channel, correct reply to channel association
// previously set in queueRequest.
channels[i].reply->d_func()->connectionChannel = &channels[i];
}
QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest() const
{
if (!highPriorityQueue.isEmpty())
return highPriorityQueue.last().first;
if (!lowPriorityQueue.isEmpty())
return lowPriorityQueue.last().first;
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(QIODevice *socket)
{
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
int i = indexOf(socket);
// return fast if there was no reply right now processed
if (channels[i].reply == nullptr)
return;
if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.size() >= defaultRePipelineLength)) {
return;
}
if (channels[i].pipeliningSupported != QHttpNetworkConnectionChannel::PipeliningProbablySupported)
return;
// the current request that is in must already support pipelining
if (!channels[i].request.isPipeliningAllowed())
return;
// the current request must be a idempotent (right now we only check GET)
if (channels[i].request.operation() != QHttpNetworkRequest::Get)
return;
// check if socket is connected
if (QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState)
return;
// check for resendCurrent
if (channels[i].resendCurrent)
return;
// we do not like authentication stuff
// ### make sure to be OK with this in later releases
if (!channels[i].authenticator.isNull()
&& (!channels[i].authenticator.user().isEmpty()
|| !channels[i].authenticator.password().isEmpty()))
return;
if (!channels[i].proxyAuthenticator.isNull()
&& (!channels[i].proxyAuthenticator.user().isEmpty()
|| !channels[i].proxyAuthenticator.password().isEmpty()))
return;
// must be in ReadingState or WaitingState
if (! (channels[i].state == QHttpNetworkConnectionChannel::WaitingState
|| channels[i].state == QHttpNetworkConnectionChannel::ReadingState))
return;
int lengthBefore;
while (!highPriorityQueue.isEmpty()) {
lengthBefore = channels[i].alreadyPipelinedRequests.size();
fillPipeline(highPriorityQueue, channels[i]);
if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) {
channels[i].pipelineFlush();
return;
}
if (lengthBefore == channels[i].alreadyPipelinedRequests.size())
break; // did not process anything, now do the low prio queue
}
while (!lowPriorityQueue.isEmpty()) {
lengthBefore = channels[i].alreadyPipelinedRequests.size();
fillPipeline(lowPriorityQueue, channels[i]);
if (channels[i].alreadyPipelinedRequests.size() >= defaultPipelineLength) {
channels[i].pipelineFlush();
return;
}
if (lengthBefore == channels[i].alreadyPipelinedRequests.size())
break; // did not process anything
}
channels[i].pipelineFlush();
}
// returns true when the processing of a queue has been done
bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel)
{
if (queue.isEmpty())
return true;
for (int i = queue.size() - 1; i >= 0; --i) {
HttpMessagePair messagePair = queue.at(i);
const QHttpNetworkRequest &request = messagePair.first;
// we currently do not support pipelining if HTTP authentication is used
if (!request.url().userInfo().isEmpty())
continue;
// take only GET requests
if (request.operation() != QHttpNetworkRequest::Get)
continue;
if (!request.isPipeliningAllowed())
continue;
// remove it from the queue
queue.takeAt(i);
// we modify the queue we iterate over here, but since we return from the function
// afterwards this is fine.
// actually send it
if (!messagePair.second->d_func()->requestIsPrepared)
prepareRequest(messagePair);
channel.pipelineInto(messagePair);
// return false because we processed something and need to process again
return false;
}
// return true, the queue has been processed and not changed
return true;
}
QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket, const QString &extraDetail)
{
QString errorString;
switch (errorCode) {
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;
case QNetworkReply::RemoteHostClosedError:
errorString = QCoreApplication::translate("QHttp", "Connection closed");
break;
case QNetworkReply::TimeoutError:
errorString = QCoreApplication::translate("QAbstractSocket", "Socket operation timed out");
break;
case QNetworkReply::ProxyAuthenticationRequiredError:
errorString = QCoreApplication::translate("QHttp", "Proxy requires authentication");
break;
case QNetworkReply::AuthenticationRequiredError:
errorString = QCoreApplication::translate("QHttp", "Host requires authentication");
break;
case QNetworkReply::ProtocolFailure:
errorString = QCoreApplication::translate("QHttp", "Data corrupted");
break;
case QNetworkReply::ProtocolUnknownError:
errorString = QCoreApplication::translate("QHttp", "Unknown protocol specified");
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");
break;
case QNetworkReply::InsecureRedirectError:
errorString = QCoreApplication::translate("QHttp", "Insecure redirect");
break;
default:
// all other errors are treated as QNetworkReply::UnknownNetworkError
errorString = extraDetail;
break;
}
return errorString;
}
// this is called from the destructor of QHttpNetworkReply. It is called when
// the reply was finished correctly or when it was aborted.
void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
{
Q_Q(QHttpNetworkConnection);
// check if the reply is currently being processed or it is pipelined in
for (int i = 0; i < activeChannelCount; ++i) {
// is the reply associated the currently processing of this channel?
if (channels[i].reply == reply) {
channels[i].reply = nullptr;
if (channels[i].protocolHandler)
channels[i].protocolHandler->setReply(nullptr);
channels[i].request = QHttpNetworkRequest();
channels[i].resendCurrent = false;
if (!reply->isFinished() && !channels[i].alreadyPipelinedRequests.isEmpty()) {
// the reply had to be prematurely removed, e.g. it was not finished
// therefore we have to requeue the already pipelined requests.
channels[i].requeueCurrentlyPipelinedRequests();
}
// if HTTP mandates we should close
// or the reply is not finished yet, e.g. it was aborted
// we have to close that connection
if (reply->d_func()->isConnectionCloseEnabled() || !reply->isFinished()) {
if (reply->isAborted()) {
channels[i].abort();
} else {
channels[i].close();
}
}
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
return;
}
// is the reply inside the pipeline of this channel already?
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);
channels[i].requeueCurrentlyPipelinedRequests();
// Since some requests had already been pipelined, but we removed
// one and re-queued the others
// we must force a connection close after the request that is
// currently in processing has been finished.
if (channels[i].reply)
channels[i].reply->d_func()->forceConnectionCloseEnabled = true;
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
return;
}
}
// is the reply inside the H2 pipeline of this channel already?
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;
}
// Check if the h2 protocol handler already started processing it
if ((connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
|| channels[i].switchedToHttp2)
&& channels[i].protocolHandler) {
if (channels[i].protocolHandler->tryRemoveReply(reply))
return;
}
}
// remove from the high priority queue
if (!highPriorityQueue.isEmpty()) {
for (int j = highPriorityQueue.size() - 1; j >= 0; --j) {
HttpMessagePair messagePair = highPriorityQueue.at(j);
if (messagePair.second == reply) {
highPriorityQueue.removeAt(j);
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
return;
}
}
}
// remove from the low priority queue
if (!lowPriorityQueue.isEmpty()) {
for (int j = lowPriorityQueue.size() - 1; j >= 0; --j) {
HttpMessagePair messagePair = lowPriorityQueue.at(j);
if (messagePair.second == reply) {
lowPriorityQueue.removeAt(j);
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
return;
}
}
}
}
// This function must be called from the event loop. The only
// exception is documented in QHttpNetworkConnectionPrivate::queueRequest
// although it is called _q_startNextRequest, it will actually start multiple requests when possible
void QHttpNetworkConnectionPrivate::_q_startNextRequest()
{
// If there is no network layer state decided we should not start any new requests.
if (networkLayerState == Unknown || networkLayerState == HostLookupPending || networkLayerState == IPv4or6)
return;
// If the QHttpNetworkConnection is currently paused then bail out immediately
if (state == PausedState)
return;
//resend the necessary ones.
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
if (!channels[i].socket
|| QSocketAbstraction::socketState(channels[i].socket) == QAbstractSocket::UnconnectedState) {
if (!channels[i].ensureConnection())
continue;
}
channels[i].resendCurrent = false;
// if this is not possible, error will be emitted and connection terminated
if (!channels[i].resetUploadData())
continue;
channels[i].sendRequest();
}
}
// dequeue new ones
switch (connectionType) {
case QHttpNetworkConnection::ConnectionTypeHTTP: {
// return fast if there is nothing to do
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
// try to get a free AND connected socket
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].socket) {
if (!channels[i].reply && !channels[i].isSocketBusy()
&& QSocketAbstraction::socketState(channels[i].socket)
== QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
}
}
break;
}
case QHttpNetworkConnection::ConnectionTypeHTTP2Direct:
case QHttpNetworkConnection::ConnectionTypeHTTP2: {
auto &channel = channels[0];
if (channel.h2RequestsToSend.isEmpty() && !channel.reply
&& highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) {
return;
}
if (networkLayerState == IPv4)
channel.networkLayerPreference = QAbstractSocket::IPv4Protocol;
else if (networkLayerState == IPv6)
channel.networkLayerPreference = QAbstractSocket::IPv6Protocol;
channel.ensureConnection();
// Connection is going away: hold new requests until the channel reconnects.
if (channel.isPendingReconnect())
return;
if (auto *s = channel.socket; s
&& QSocketAbstraction::socketState(s) == QAbstractSocket::ConnectedState
&& !channel.pendingEncrypt) {
if (channel.h2RequestsToSend.size()) {
channel.sendRequest();
} else if (!channel.reply && !channel.switchedToHttp2) {
// This covers an edge-case where we're already connected and the "connected"
// signal was already sent, but we didn't have any request available at the time,
// so it was missed. As such we need to dequeue a request and send it now that we
// have one.
dequeueRequest(channel.socket);
channel.sendRequest();
}
}
break;
}
}
// try to push more into all sockets
// ### FIXME we should move this to the beginning of the function
// as soon as QtWebkit is properly using the pipelining
// (e.g. not for XMLHttpRequest or the first page load)
// ### FIXME we should also divide the requests more even
// on the connected sockets
//tryToFillPipeline(socket);
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
for (int i = 0; i < activeChannelCount; i++) {
if (channels[i].socket
&& QSocketAbstraction::socketState(channels[i].socket)
== QAbstractSocket::ConnectedState) {
fillPipeline(channels[i].socket);
}
}
// 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.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
int neededOpenChannels = queuedRequests;
if (preConnectRequests > 0) {
int normalRequests = queuedRequests - preConnectRequests;
neededOpenChannels = qMax(normalRequests, preConnectRequests);
}
if (neededOpenChannels <= 0)
return;
QVarLengthArray<int> channelsToConnect;
// use previously used channels first
for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
if (!channels[i].socket)
continue;
using State = QAbstractSocket::SocketState;
if ((QSocketAbstraction::socketState(channels[i].socket) == State::ConnectingState)
|| (QSocketAbstraction::socketState(channels[i].socket) == State::HostLookupState)
|| channels[i].pendingEncrypt) { // pendingEncrypt == "EncryptingState"
neededOpenChannels--;
continue;
}
if (!channels[i].reply && !channels[i].isSocketBusy()
&& (QSocketAbstraction::socketState(channels[i].socket) == State::UnconnectedState)) {
channelsToConnect.push_back(i);
neededOpenChannels--;
}
}
// use other channels
for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
if (channels[i].socket)
continue;
channelsToConnect.push_back(i);
neededOpenChannels--;
}
auto channelToConnectSpan = QSpan{channelsToConnect};
while (!channelToConnectSpan.isEmpty()) {
const int channel = channelToConnectSpan.front();
channelToConnectSpan = channelToConnectSpan.sliced(1);
if (networkLayerState == IPv4)
channels[channel].networkLayerPreference = QAbstractSocket::IPv4Protocol;
else if (networkLayerState == IPv6)
channels[channel].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[channel].ensureConnection();
}
}
void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply)
{
for (int i = 0 ; i < activeChannelCount; ++i) {
if (channels[i].reply == reply) {
// emulate a readyRead() from the socket
QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection);
return;
}
}
}
// The first time we start the connection is used we do not know if we
// should use IPv4 or IPv6. So we start a hostlookup to figure this out.
// Later when we do the connection the socket will not need to do another
// lookup as then the hostinfo will already be in the cache.
void QHttpNetworkConnectionPrivate::startHostInfoLookup()
{
networkLayerState = HostLookupPending;
// check if we already now can decide if this is IPv4 or IPv6
QString lookupHost = hostName;
#ifndef QT_NO_NETWORKPROXY
if (networkProxy.capabilities() & QNetworkProxy::HostNameLookupCapability) {
lookupHost = networkProxy.hostName();
} else if (channels[0].proxy.capabilities() & QNetworkProxy::HostNameLookupCapability) {
lookupHost = channels[0].proxy.hostName();
}
#endif
QHostAddress temp;
if (temp.setAddress(lookupHost)) {
const QAbstractSocket::NetworkLayerProtocol protocol = temp.protocol();
if (protocol == QAbstractSocket::IPv4Protocol) {
networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
return;
} else if (protocol == QAbstractSocket::IPv6Protocol) {
networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
return;
}
} else {
int hostLookupId;
bool immediateResultValid = false;
QHostInfo hostInfo = qt_qhostinfo_lookup(lookupHost,
this->q_func(),
SLOT(_q_hostLookupFinished(QHostInfo)),
&immediateResultValid,
&hostLookupId);
if (immediateResultValid) {
_q_hostLookupFinished(hostInfo);
}
}
}
void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info)
{
bool bIpv4 = false;
bool bIpv6 = false;
bool foundAddress = false;
if (networkLayerState == IPv4 || networkLayerState == IPv6 || networkLayerState == IPv4or6)
return;
const auto addresses = info.addresses();
for (const QHostAddress &address : addresses) {
const QAbstractSocket::NetworkLayerProtocol protocol = address.protocol();
if (protocol == QAbstractSocket::IPv4Protocol) {
if (!foundAddress) {
foundAddress = true;
delayIpv4 = false;
}
bIpv4 = true;
} else if (protocol == QAbstractSocket::IPv6Protocol) {
if (!foundAddress) {
foundAddress = true;
delayIpv4 = true;
}
bIpv6 = true;
}
}
if (bIpv4 && bIpv6)
startNetworkLayerStateLookup();
else if (bIpv4) {
networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
} else if (bIpv6) {
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, lookupError);
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
} else if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
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, lookupError);
}
} else {
// 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;
}
}
}
// This will be used if the host lookup found both and Ipv4 and
// Ipv6 address. Then we will start up two connections and pick
// the network layer of the one that finish first. The second
// connection will then be disconnected.
void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup()
{
if (activeChannelCount > 1) {
// At this time all channels should be unconnected.
Q_ASSERT(!channels[0].isSocketBusy());
Q_ASSERT(!channels[1].isSocketBusy());
networkLayerState = IPv4or6;
channels[0].networkLayerPreference = QAbstractSocket::IPv4Protocol;
channels[1].networkLayerPreference = QAbstractSocket::IPv6Protocol;
int timeout = 300;
delayedConnectionTimer.start(timeout);
if (delayIpv4)
channels[1].ensureConnection();
else
channels[0].ensureConnection();
} else {
networkLayerState = IPv4or6;
channels[0].networkLayerPreference = QAbstractSocket::AnyIPProtocol;
channels[0].ensureConnection();
}
}
void QHttpNetworkConnectionPrivate::networkLayerDetected(QAbstractSocket::NetworkLayerProtocol protocol)
{
for (int i = 0 ; i < activeChannelCount; ++i) {
if ((channels[i].networkLayerPreference != protocol) && (channels[i].state == QHttpNetworkConnectionChannel::ConnectingState)) {
channels[i].close();
}
}
}
void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
{
if (delayIpv4)
channels[0].ensureConnection();
else
channels[1].ensureConnection();
}
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->init();
}
QHttpNetworkConnection::~QHttpNetworkConnection()
{
}
QString QHttpNetworkConnection::hostName() const
{
Q_D(const QHttpNetworkConnection);
return d->hostName;
}
quint16 QHttpNetworkConnection::port() const
{
Q_D(const QHttpNetworkConnection);
return d->port;
}
QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request)
{
Q_D(QHttpNetworkConnection);
return d->queueRequest(request);
}
void QHttpNetworkConnection::fillHttp2Queue()
{
Q_D(QHttpNetworkConnection);
d->fillHttp2Queue();
}
bool QHttpNetworkConnection::isSsl() const
{
Q_D(const QHttpNetworkConnection);
return d->encrypt;
}
QHttpNetworkConnectionChannel *QHttpNetworkConnection::channels() const
{
return d_func()->channels;
}
#ifndef QT_NO_NETWORKPROXY
void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy)
{
Q_D(QHttpNetworkConnection);
d->networkProxy = networkProxy;
// update the authenticator
if (!d->networkProxy.user().isEmpty()) {
for (int i = 0; i < d->channelCount; ++i) {
d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user());
d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password());
}
}
}
QNetworkProxy QHttpNetworkConnection::cacheProxy() const
{
Q_D(const QHttpNetworkConnection);
return d->networkProxy;
}
void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy)
{
Q_D(QHttpNetworkConnection);
for (int i = 0; i < d->channelCount; ++i)
d->channels[i].setProxy(networkProxy);
}
QNetworkProxy QHttpNetworkConnection::transparentProxy() const
{
Q_D(const QHttpNetworkConnection);
return d->channels[0].proxy;
}
#endif
QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() const
{
Q_D(const QHttpNetworkConnection);
return d->connectionType;
}
void QHttpNetworkConnection::setConnectionType(ConnectionType type)
{
Q_D(QHttpNetworkConnection);
d->connectionType = type;
}
QHttp2Configuration QHttpNetworkConnection::http2Parameters() const
{
Q_D(const QHttpNetworkConnection);
return d->http2Parameters;
}
void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration &params)
{
Q_D(QHttpNetworkConnection);
d->http2Parameters = params;
}
QTcpKeepAliveConfiguration QHttpNetworkConnection::tcpKeepAliveParameters() const
{
Q_D(const QHttpNetworkConnection);
return d->tcpKeepAliveConfiguration;
}
void QHttpNetworkConnection::setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config)
{
Q_D(QHttpNetworkConnection);
d->tcpKeepAliveConfiguration = config;
}
// SSL support below
#ifndef QT_NO_SSL
void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config)
{
Q_D(QHttpNetworkConnection);
if (!d->encrypt)
return;
// set the config on all channels
for (int i = 0; i < d->activeChannelCount; ++i)
d->channels[i].setSslConfiguration(config);
}
std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() const
{
Q_D(const QHttpNetworkConnection);
return d->sslContext;
}
void QHttpNetworkConnection::setSslContext(std::shared_ptr<QSslContext> context)
{
Q_D(QHttpNetworkConnection);
d->sslContext = std::move(context);
}
void QHttpNetworkConnection::ignoreSslErrors(int channel)
{
Q_D(QHttpNetworkConnection);
if (!d->encrypt)
return;
if (channel == -1) { // ignore for all channels
// We need to ignore for all channels, even the ones that are not in use just in case they
// will be in the future.
for (int i = 0; i < d->channelCount; ++i) {
d->channels[i].ignoreSslErrors();
}
} else {
d->channels[channel].ignoreSslErrors();
}
}
void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int channel)
{
Q_D(QHttpNetworkConnection);
if (!d->encrypt)
return;
if (channel == -1) { // ignore for all channels
// We need to ignore for all channels, even the ones that are not in use just in case they
// will be in the future.
for (int i = 0; i < d->channelCount; ++i) {
d->channels[i].ignoreSslErrors(errors);
}
} else {
d->channels[channel].ignoreSslErrors(errors);
}
}
#endif //QT_NO_SSL
void QHttpNetworkConnection::preConnectFinished()
{
d_func()->preConnectRequests--;
}
QString QHttpNetworkConnection::peerVerifyName() const
{
Q_D(const QHttpNetworkConnection);
return d->peerVerifyName;
}
void QHttpNetworkConnection::setPeerVerifyName(const QString &peerName)
{
Q_D(QHttpNetworkConnection);
d->peerVerifyName = peerName;
}
void QHttpNetworkConnection::onlineStateChanged(bool isOnline)
{
Q_D(QHttpNetworkConnection);
if (isOnline) {
// If we did not have any 'isOffline' previously - well, good
// to know, we are 'online' apparently.
return;
}
for (int i = 0; i < d->activeChannelCount; i++) {
auto &channel = d->channels[i];
channel.emitFinishedWithError(QNetworkReply::TemporaryNetworkFailureError, "Temporary network failure.");
channel.close();
}
}
#ifndef QT_NO_NETWORKPROXY
// only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not
// from QHttpNetworkConnectionChannel::handleAuthenticationChallenge
// e.g. it is for SOCKS proxies which require authentication.
void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth)
{
// Also pause the connection because socket notifiers may fire while an user
// dialog is displaying
pauseConnection();
QHttpNetworkReply *reply;
if ((connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
&& (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.size() > 0);
reply = chan->h2RequestsToSend.cbegin().value().second;
} else { // HTTP
reply = chan->reply;
}
Q_ASSERT(reply);
emit reply->proxyAuthenticationRequired(proxy, auth);
resumeConnection();
int i = indexOf(chan->socket);
copyCredentials(i, auth, true);
}
#endif
QT_END_NAMESPACE
#include "moc_qhttpnetworkconnection_p.cpp"