blob: 21c635980776da66ef08410ba2550199573b50b9 [file] [log] [blame]
/****************************************************************************
**
** 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$
**
****************************************************************************/
#include "qhttpnetworkconnection_p.h"
#include <private/qabstractsocket_p.h>
#include "qhttpnetworkconnectionchannel_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 <qbuffer.h>
#include <qpair.h>
#include <qdebug.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
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
#ifndef QT_NO_SSL
|| type == QHttpNetworkConnection::ConnectionTypeSPDY
#endif
? 1 : defaultHttpChannelCount)
, channelCount(defaultHttpChannelCount)
#ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy)
#endif
, preConnectRequests(0)
, connectionType(type)
{
// We allocate all 6 channels even if it's SPDY or 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];
}
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)
#ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy)
#endif
, preConnectRequests(0)
, connectionType(type)
{
channels = new QHttpNetworkConnectionChannel[channelCount];
}
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;
#ifndef QT_NO_BEARERMANAGEMENT
//push session down to channels
channels[i].networkSession = networkSession;
#endif
}
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 (channels[i].socket) {
#ifndef QT_NO_SSL
if (encrypt)
QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
else
#endif
QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket);
}
}
}
void QHttpNetworkConnectionPrivate::resumeConnection()
{
state = RunningState;
// Enable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
if (channels[i].socket) {
#ifndef QT_NO_SSL
if (encrypt)
QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
else
#endif
QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket);
// Resume pending upload if needed
if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
}
}
// queue _q_startNextRequest
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
}
int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *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(QAbstractSocket *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();
}
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
{
QHttpNetworkRequest &request = messagePair.first;
QHttpNetworkReply *reply = messagePair.second;
// add missing fields for the request
QByteArray value;
// 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, take the smaller one.
request.setContentLength(qMin(uploadDeviceSize, contentLength));
} 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");
}
}
// 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
request.setHeaderField("Accept-Encoding", "gzip, deflate");
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()) {
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());
}
// 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 (value.isEmpty()) {
QHostAddress add;
QByteArray host;
if (add.setAddress(hostName)) {
if (add.protocol() == QAbstractSocket::IPv6Protocol)
host = '[' + hostName.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(QAbstractSocket *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 = 0;
if (channels[i].protocolHandler)
channels[i].protocolHandler->setReply(0);
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) {
const QHttpNetworkConnectionChannel &channel = channels[fromChannel];
const QAuthenticatorPrivate::Method method = isProxy ? channel.proxyAuthMethod : channel.authMethod;
if (method == QAuthenticatorPrivate::Ntlm || method == QAuthenticatorPrivate::Negotiate)
return;
}
// select another channel
QAuthenticator* otherAuth = 0;
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(QAbstractSocket *socket, QHttpNetworkReply *reply,
bool isProxy, bool &resend)
{
Q_ASSERT(socket);
Q_ASSERT(reply);
resend = false;
//create the response header to be used with QAuthenticatorPrivate.
QList<QPair<QByteArray, QByteArray> > fields = reply->header();
//find out the type of authentication protocol requested.
QAuthenticatorPrivate::Method authMethod = reply->d_func()->authenticationMethod(isProxy);
if (authMethod != QAuthenticatorPrivate::None) {
int i = indexOf(socket);
//Use a single authenticator for all domains. ### change later to use domain/realm
QAuthenticator* auth = 0;
if (isProxy) {
auth = &channels[i].proxyAuthenticator;
channels[i].proxyAuthMethod = authMethod;
} else {
auth = &channels[i].authenticator;
channels[i].authMethod = authMethod;
}
//proceed with the authentication.
if (auth->isNull())
auth->detach();
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
priv->parseHttpResponse(fields, isProxy);
if (priv->phase == QAuthenticatorPrivate::Done) {
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 = 0;
if (isProxy)
channels[i].proxyAuthenticator = QAuthenticator();
else
channels[i].authenticator = QAuthenticator();
// authentication is cancelled, send the current contents to the user.
emit channels[i].reply->headerChanged();
emit channels[i].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;
}
QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply)
{
if (!reply->request().isFollowRedirects())
return QUrl();
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;
}
}
// If the location url is invalid/empty, we emit ProtocolUnknownError
if (!redirectUrl.isValid()) {
emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
return QUrl();
}
// Check if we have exceeded max redirects allowed
if (reply->request().redirectCount() <= 0) {
emitReplyError(socket, reply, QNetworkReply::TooManyRedirectsError);
return QUrl();
}
// 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());
if (redirectUrl.scheme() == QLatin1String("http") || redirectUrl.scheme() == QLatin1String("https")) {
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() != redirectUrl.scheme()
|| priorUrl.port() != redirectUrl.port()) {
emitReplyError(socket, reply, QNetworkReply::InsecureRedirectError);
return QUrl();
}
break;
case QNetworkRequest::UserVerifiedRedirectPolicy:
break;
default:
Q_ASSERT(!"Unexpected redirect policy");
}
} else {
emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
return QUrl();
}
return redirectUrl;
}
void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request)
{
Q_ASSERT(socket);
int i = indexOf(socket);
// Send "Authorization" header, but not if it's NTLM and the socket is already authenticated.
if (channels[i].authMethod != QAuthenticatorPrivate::None) {
if ((channels[i].authMethod != QAuthenticatorPrivate::Ntlm && request.headerField("Authorization").isEmpty()) || channels[i].lastStatus == 401) {
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator);
if (priv && priv->method != QAuthenticatorPrivate::None) {
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), request.url().host());
request.setHeaderField("Authorization", response);
channels[i].authenticationCredentialsSent = true;
}
}
}
#if QT_CONFIG(networkproxy)
// Send "Proxy-Authorization" header, but not if it's NTLM and the socket is already authenticated.
if (channels[i].proxyAuthMethod != QAuthenticatorPrivate::None) {
if (!(channels[i].proxyAuthMethod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) {
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator);
if (priv && priv->method != QAuthenticatorPrivate::None) {
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), networkProxy.hostName());
request.setHeaderField("Proxy-Authorization", response);
channels[i].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 = qMakePair(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 { // SPDY, HTTP/2 ('h2' mode)
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].spdyRequestsToSend.insert(request.priority(), pair);
}
#ifndef Q_OS_WINRT
// 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 ) {
#else // !Q_OS_WINRT
{
// Skip the host lookup part for winrt. Host lookup and proxy handling are done by Windows
// internally and networkLayerPreference is ignored on this platform. Instead of refactoring
// the whole approach we just pretend that everything important is known here.
networkLayerState = IPv4;
#endif
// 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].spdyRequestsToSend.insert(QHttpNetworkRequest::HighPriority, pair);
}
highPriorityQueue.clear();
for (auto &pair : lowPriorityQueue) {
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].spdyRequestsToSend.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(QAbstractSocket *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();
}
// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *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 == 0)
return;
if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.length() >= 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 (socket->state() != 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.length();
fillPipeline(highPriorityQueue, channels[i]);
if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) {
channels[i].pipelineFlush();
return;
}
if (lengthBefore == channels[i].alreadyPipelinedRequests.length())
break; // did not process anything, now do the low prio queue
}
while (!lowPriorityQueue.isEmpty()) {
lengthBefore = channels[i].alreadyPipelinedRequests.length();
fillPipeline(lowPriorityQueue, channels[i]);
if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) {
channels[i].pipelineFlush();
return;
}
if (lengthBefore == channels[i].alreadyPipelinedRequests.length())
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.count() - 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, QAbstractSocket *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);
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");
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 = 0;
if (channels[i].protocolHandler)
channels[i].protocolHandler->setReply(0);
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.length(); 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;
}
}
#ifndef QT_NO_SSL
// is the reply inside the SPDY pipeline of this channel already?
QMultiMap<int, HttpMessagePair>::iterator it = channels[i].spdyRequestsToSend.begin();
QMultiMap<int, HttpMessagePair>::iterator end = channels[i].spdyRequestsToSend.end();
for (; it != end; ++it) {
if (it.value().second == reply) {
channels[i].spdyRequestsToSend.remove(it.key());
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) {
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.count() - 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)) {
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() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
}
}
break;
}
case QHttpNetworkConnection::ConnectionTypeHTTP2Direct:
case QHttpNetworkConnection::ConnectionTypeHTTP2:
case QHttpNetworkConnection::ConnectionTypeSPDY: {
if (channels[0].spdyRequestsToSend.isEmpty() && channels[0].switchedToHttp2)
return;
if (networkLayerState == IPv4)
channels[0].networkLayerPreference = QAbstractSocket::IPv4Protocol;
else if (networkLayerState == IPv6)
channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[0].ensureConnection();
if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
&& !channels[0].pendingEncrypt && channels[0].spdyRequestsToSend.size())
channels[0].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 && channels[i].socket->state() == 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.count() + lowPriorityQueue.count();
// 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);
}
for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
bool connectChannel = false;
if (channels[i].socket) {
if ((channels[i].socket->state() == QAbstractSocket::ConnectingState)
|| (channels[i].socket->state() == QAbstractSocket::HostLookupState)
|| channels[i].pendingEncrypt) // pendingEncrypt == "EncryptingState"
neededOpenChannels--;
if (neededOpenChannels <= 0)
break;
if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState))
connectChannel = true;
} else { // not previously used channel
connectChannel = true;
}
if (connectChannel) {
if (networkLayerState == IPv4)
channels[i].networkLayerPreference = QAbstractSocket::IPv4Protocol;
else if (networkLayerState == IPv6)
channels[i].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[i].ensureConnection();
neededOpenChannels--;
}
}
}
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 {
if (dequeueRequest(channels[0].socket)) {
emitReplyError(channels[0].socket, channels[0].reply, QNetworkReply::HostNotFoundError);
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
} else if (connectionType == QHttpNetworkConnection::ConnectionTypeSPDY
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
for (const HttpMessagePair &spdyPair : qAsConst(channels[0].spdyRequestsToSend)) {
// emit error for all replies
QHttpNetworkReply *currentReply = spdyPair.second;
Q_ASSERT(currentReply);
emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError);
}
} else {
// Should not happen: we start a host lookup before sending a request,
// so it's natural to have requests either in SPDY/HTTP/2 queue,
// or in low/high priority queues.
qWarning("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;
#ifndef QT_NO_BEARERMANAGEMENT
if (networkSession) {
const QNetworkConfiguration::BearerType bearerType = networkSession->configuration().bearerType();
if (bearerType == QNetworkConfiguration::Bearer2G)
timeout = 800;
else if (bearerType == QNetworkConfiguration::BearerCDMA2000)
timeout = 500;
else if (bearerType == QNetworkConfiguration::BearerWCDMA)
timeout = 500;
else if (bearerType == QNetworkConfiguration::BearerHSPA)
timeout = 400;
}
#endif
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();
}
#ifndef QT_NO_BEARERMANAGEMENT
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType connectionType,
QObject *parent, QSharedPointer<QNetworkSession> networkSession)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt, connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->networkSession = std::move(networkSession);
d->init();
if (QNetworkStatusMonitor::isEnabled()) {
connect(&d->connectionMonitor, &QNetworkConnectionMonitor::reachabilityChanged,
this, &QHttpNetworkConnection::onlineStateChanged, Qt::QueuedConnection);
}
}
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, QObject *parent,
QSharedPointer<QNetworkSession> networkSession,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->networkSession = std::move(networkSession);
d->init();
if (QNetworkStatusMonitor::isEnabled()) {
connect(&d->connectionMonitor, &QNetworkConnectionMonitor::reachabilityChanged,
this, &QHttpNetworkConnection::onlineStateChanged, Qt::QueuedConnection);
}
}
#else
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 (QNetworkStatusMonitor::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)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->init();
if (QNetworkStatusMonitor::isEnabled()) {
connect(&d->connectionMonitor, &QNetworkConnectionMonitor::reachabilityChanged,
this, &QHttpNetworkConnection::onlineStateChanged, Qt::QueuedConnection);
}
}
#endif // QT_NO_BEARERMANAGEMENT
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->activeChannelCount; ++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->activeChannelCount; ++i)
d->channels[i].setProxy(networkProxy);
}
QNetworkProxy QHttpNetworkConnection::transparentProxy() const
{
Q_D(const QHttpNetworkConnection);
return d->channels[0].proxy;
}
#endif
QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType()
{
Q_D(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;
}
// 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);
}
QSharedPointer<QSslContext> QHttpNetworkConnection::sslContext()
{
Q_D(QHttpNetworkConnection);
return d->sslContext;
}
void QHttpNetworkConnection::setSslContext(QSharedPointer<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
for (int i = 0; i < d->activeChannelCount; ++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
for (int i = 0; i < d->activeChannelCount; ++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();
}
// We don't care, this connection is broken from our POV.
d->connectionMonitor.stopMonitoring();
}
#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
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
#if QT_CONFIG(ssl)
|| connectionType == QHttpNetworkConnection::ConnectionTypeSPDY
#endif
) {
// 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->spdyRequestsToSend.count() > 0);
reply = chan->spdyRequestsToSend.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"