Skip to content

Commit

Permalink
Http: Support unix+http: scheme in http backend
Browse files Browse the repository at this point in the history
[ChangeLog][QtNetwork][QNetworkAccessManager] QNetworkAccessManager now
supports local connections using the uri schemes unix+http: or
local+http:.

Fixes: QTBUG-102855
Change-Id: I1f47b74ab42b51d97b3c555cc3afd6ccd272e1ed
Reviewed-by: Mate Barany <[email protected]>
  • Loading branch information
Morten242 committed May 15, 2024
1 parent 956795b commit cb8e5e0
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 19 deletions.
15 changes: 10 additions & 5 deletions src/network/access/qhttpnetworkconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType

QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type)
bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
: hostName(hostName),
port(port),
encrypt(encrypt),
isLocalSocket(isLocalSocket),
activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
channelCount(connectionCount),
channels(new QHttpNetworkConnectionChannel[channelCount]),
Expand All @@ -64,6 +65,8 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
#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.
Expand Down Expand Up @@ -533,7 +536,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)

// Check redirect url protocol
const QUrl priorUrl(reply->request().url());
if (redirectUrl.scheme() == "http"_L1 || redirectUrl.scheme() == "https"_L1) {
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.
Expand All @@ -544,7 +549,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
break;
case QNetworkRequest::SameOriginRedirectPolicy:
if (priorUrl.host() != redirectUrl.host()
|| priorUrl.scheme() != redirectUrl.scheme()
|| priorUrl.scheme() != targetUrlScheme
|| priorUrl.port() != redirectUrl.port()) {
return {{}, QNetworkReply::InsecureRedirectError};
}
Expand Down Expand Up @@ -1346,9 +1351,9 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
}

QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, QObject *parent,
quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
Expand Down
7 changes: 5 additions & 2 deletions src/network/access/qhttpnetworkconnection_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ class Q_NETWORK_EXPORT QHttpNetworkConnection : public QObject
};

QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80,
bool encrypt = false, QObject *parent = nullptr,
bool encrypt = false, bool isLocalSocket = false,
QObject *parent = nullptr,
ConnectionType connectionType = ConnectionTypeHTTP);
~QHttpNetworkConnection();

Expand Down Expand Up @@ -155,7 +156,8 @@ class QHttpNetworkConnectionPrivate : public QObjectPrivate
};

QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port,
bool encrypt, QHttpNetworkConnection::ConnectionType type);
bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType type);
~QHttpNetworkConnectionPrivate();
void init();

Expand Down Expand Up @@ -205,6 +207,7 @@ class QHttpNetworkConnectionPrivate : public QObjectPrivate
QString hostName;
quint16 port;
bool encrypt;
bool isLocalSocket;
bool delayIpv4 = true;

// Number of channels we are trying to use at the moment:
Expand Down
2 changes: 2 additions & 0 deletions src/network/access/qhttpnetworkconnectionchannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ void QHttpNetworkConnectionChannel::init()
#ifndef QT_NO_SSL
if (connection->d_func()->encrypt)
socket = new QSslSocket;
else if (connection->d_func()->isLocalSocket)
socket = new QLocalSocket;
else
socket = new QTcpSocket;
#else
Expand Down
22 changes: 15 additions & 7 deletions src/network/access/qhttpthreaddelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &p
QUrl copy = url;
QString scheme = copy.scheme();
bool isEncrypted = scheme == "https"_L1 || scheme == "preconnect-https"_L1;
copy.setPort(copy.port(isEncrypted ? 443 : 80));
const bool isLocalSocket = scheme.startsWith("unix"_L1);
if (!isLocalSocket)
copy.setPort(copy.port(isEncrypted ? 443 : 80));
if (scheme == "preconnect-http"_L1)
copy.setScheme("http"_L1);
else if (scheme == "preconnect-https"_L1)
Expand Down Expand Up @@ -145,9 +147,9 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
{
// Q_OBJECT
public:
QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType connectionType)
: QHttpNetworkConnection(connectionCount, hostName, port, encrypt, /*parent=*/nullptr, connectionType)
: QHttpNetworkConnection(connectionCount, hostName, port, encrypt, isLocalSocket, /*parent=*/nullptr, connectionType)
{
setExpires(true);
setShareable(true);
Expand Down Expand Up @@ -244,7 +246,9 @@ void QHttpThreadDelegate::startRequest()

// check if we have an open connection to this host
QUrl urlCopy = httpRequest.url();
urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
const bool isLocalSocket = urlCopy.scheme().startsWith("unix"_L1);
if (!isLocalSocket)
urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));

QHttpNetworkConnection::ConnectionType connectionType
= httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
Expand Down Expand Up @@ -279,7 +283,10 @@ void QHttpThreadDelegate::startRequest()
} else
#endif // QT_CONFIG(ssl)
{
urlCopy.setScheme(QStringLiteral("h2"));
if (isLocalSocket)
urlCopy.setScheme(QStringLiteral("unix+h2"));
else
urlCopy.setScheme(QStringLiteral("h2"));
}
}

Expand All @@ -297,8 +304,9 @@ void QHttpThreadDelegate::startRequest()
if (!httpConnection) {
// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
httpConnection = new QNetworkAccessCachedHttpConnection(http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
connectionType);
httpConnection = new QNetworkAccessCachedHttpConnection(
http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
isLocalSocket, connectionType);
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
httpConnection->setHttp2Parameters(http2Parameters);
Expand Down
15 changes: 14 additions & 1 deletion src/network/access/qnetworkaccessmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,13 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
bool isLocalFile = req.url().isLocalFile();
QString scheme = req.url().scheme();

// Remap local+http to unix+http to make further processing easier
if (scheme == "local+http"_L1) {
scheme = u"unix+http"_s;
QUrl url = req.url();
url.setScheme(scheme);
req.setUrl(url);
}

// fast path for GET on file:// URLs
// The QNetworkAccessFileBackend will right now only be used for PUT
Expand Down Expand Up @@ -1296,11 +1303,15 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
u"https",
u"preconnect-https",
#endif
u"unix+http",
};
// Since Qt 5 we use the new QNetworkReplyHttpImpl
if (std::find(std::begin(httpSchemes), std::end(httpSchemes), scheme) != std::end(httpSchemes)) {

#ifndef QT_NO_SSL
if (isStrictTransportSecurityEnabled() && d->stsCache.isKnownHost(request.url())) {
const bool isLocalSocket = scheme.startsWith("unix"_L1);
if (!isLocalSocket && isStrictTransportSecurityEnabled()
&& d->stsCache.isKnownHost(request.url())) {
QUrl stsUrl(request.url());
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
Expand Down Expand Up @@ -1391,6 +1402,8 @@ QStringList QNetworkAccessManager::supportedSchemesImplementation() const
// Those ones don't exist in backends
#if QT_CONFIG(http)
schemes << QStringLiteral("http");
schemes << QStringLiteral("unix+http");
schemes << QStringLiteral("local+http");
#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
schemes << QStringLiteral("https");
Expand Down
12 changes: 8 additions & 4 deletions src/network/access/qnetworkreplyhttpimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,8 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
url = redirectUrl;

if (managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
const bool wasLocalSocket = schemeBefore.startsWith("unix"_L1);
if (!wasLocalSocket && managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
// and if the URI contains an explicit port component of "80",
Expand All @@ -1242,9 +1243,12 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
url.setPort(443);
}

const bool isLessSafe = schemeBefore == "https"_L1 && url.scheme() == "http"_L1;
if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy
&& isLessSafe) {
// Just to be on the safe side for local sockets, any changes to the scheme
// are considered less safe
const bool changingLocalScheme = wasLocalSocket && url.scheme() != schemeBefore;
const bool isLessSafe = changingLocalScheme
|| (schemeBefore == "https"_L1 && url.scheme() == "http"_L1);
if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy && isLessSafe) {
error(QNetworkReply::InsecureRedirectError,
QCoreApplication::translate("QHttp", "Insecure redirect"));
return;
Expand Down

0 comments on commit cb8e5e0

Please sign in to comment.