Http: Support unix+http: scheme in http backend

[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 <mate.barany@qt.io>
This commit is contained in:
Mårten Nordheim 2024-03-06 15:55:23 +01:00
parent 956795bda8
commit cb8e5e0dd9
6 changed files with 54 additions and 19 deletions

View File

@ -52,10 +52,11 @@ static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate( QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type) bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
: hostName(hostName), : hostName(hostName),
port(port), port(port),
encrypt(encrypt), encrypt(encrypt),
isLocalSocket(isLocalSocket),
activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)), activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
channelCount(connectionCount), channelCount(connectionCount),
channels(new QHttpNetworkConnectionChannel[channelCount]), channels(new QHttpNetworkConnectionChannel[channelCount]),
@ -64,6 +65,8 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
#endif #endif
connectionType(type) 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 // We allocate all 6 channels even if it's an HTTP/2-enabled
// connection: in case the protocol negotiation via NPN/ALPN fails, // connection: in case the protocol negotiation via NPN/ALPN fails,
// we will have normally working HTTP/1.1. // we will have normally working HTTP/1.1.
@ -533,7 +536,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
// Check redirect url protocol // Check redirect url protocol
const QUrl priorUrl(reply->request().url()); 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()) { switch (reply->request().redirectPolicy()) {
case QNetworkRequest::NoLessSafeRedirectPolicy: case QNetworkRequest::NoLessSafeRedirectPolicy:
// Here we could handle https->http redirects as InsecureProtocolError. // Here we could handle https->http redirects as InsecureProtocolError.
@ -544,7 +549,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
break; break;
case QNetworkRequest::SameOriginRedirectPolicy: case QNetworkRequest::SameOriginRedirectPolicy:
if (priorUrl.host() != redirectUrl.host() if (priorUrl.host() != redirectUrl.host()
|| priorUrl.scheme() != redirectUrl.scheme() || priorUrl.scheme() != targetUrlScheme
|| priorUrl.port() != redirectUrl.port()) { || priorUrl.port() != redirectUrl.port()) {
return {{}, QNetworkReply::InsecureRedirectError}; return {{}, QNetworkReply::InsecureRedirectError};
} }
@ -1346,9 +1351,9 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
} }
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, QObject *parent, quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType) QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
connectionType)), parent) connectionType)), parent)
{ {
Q_D(QHttpNetworkConnection); Q_D(QHttpNetworkConnection);

View File

@ -64,7 +64,8 @@ public:
}; };
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, 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); ConnectionType connectionType = ConnectionTypeHTTP);
~QHttpNetworkConnection(); ~QHttpNetworkConnection();
@ -155,7 +156,8 @@ public:
}; };
QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port, QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port,
bool encrypt, QHttpNetworkConnection::ConnectionType type); bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType type);
~QHttpNetworkConnectionPrivate(); ~QHttpNetworkConnectionPrivate();
void init(); void init();
@ -205,6 +207,7 @@ public:
QString hostName; QString hostName;
quint16 port; quint16 port;
bool encrypt; bool encrypt;
bool isLocalSocket;
bool delayIpv4 = true; bool delayIpv4 = true;
// Number of channels we are trying to use at the moment: // Number of channels we are trying to use at the moment:

View File

@ -79,6 +79,8 @@ void QHttpNetworkConnectionChannel::init()
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
if (connection->d_func()->encrypt) if (connection->d_func()->encrypt)
socket = new QSslSocket; socket = new QSslSocket;
else if (connection->d_func()->isLocalSocket)
socket = new QLocalSocket;
else else
socket = new QTcpSocket; socket = new QTcpSocket;
#else #else

View File

@ -95,7 +95,9 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &p
QUrl copy = url; QUrl copy = url;
QString scheme = copy.scheme(); QString scheme = copy.scheme();
bool isEncrypted = scheme == "https"_L1 || scheme == "preconnect-https"_L1; 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) if (scheme == "preconnect-http"_L1)
copy.setScheme("http"_L1); copy.setScheme("http"_L1);
else if (scheme == "preconnect-https"_L1) else if (scheme == "preconnect-https"_L1)
@ -145,9 +147,9 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
{ {
// Q_OBJECT // Q_OBJECT
public: 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::ConnectionType connectionType)
: QHttpNetworkConnection(connectionCount, hostName, port, encrypt, /*parent=*/nullptr, connectionType) : QHttpNetworkConnection(connectionCount, hostName, port, encrypt, isLocalSocket, /*parent=*/nullptr, connectionType)
{ {
setExpires(true); setExpires(true);
setShareable(true); setShareable(true);
@ -244,7 +246,9 @@ void QHttpThreadDelegate::startRequest()
// check if we have an open connection to this host // check if we have an open connection to this host
QUrl urlCopy = httpRequest.url(); 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 QHttpNetworkConnection::ConnectionType connectionType
= httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2 = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
@ -279,7 +283,10 @@ void QHttpThreadDelegate::startRequest()
} else } else
#endif // QT_CONFIG(ssl) #endif // QT_CONFIG(ssl)
{ {
urlCopy.setScheme(QStringLiteral("h2")); if (isLocalSocket)
urlCopy.setScheme(QStringLiteral("unix+h2"));
else
urlCopy.setScheme(QStringLiteral("h2"));
} }
} }
@ -297,8 +304,9 @@ void QHttpThreadDelegate::startRequest()
if (!httpConnection) { if (!httpConnection) {
// no entry in cache; create an object // no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection // the http object is actually a QHttpNetworkConnection
httpConnection = new QNetworkAccessCachedHttpConnection(http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl, httpConnection = new QNetworkAccessCachedHttpConnection(
connectionType); http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
isLocalSocket, connectionType);
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
httpConnection->setHttp2Parameters(http2Parameters); httpConnection->setHttp2Parameters(http2Parameters);

View File

@ -1220,6 +1220,13 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
bool isLocalFile = req.url().isLocalFile(); bool isLocalFile = req.url().isLocalFile();
QString scheme = req.url().scheme(); 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 // fast path for GET on file:// URLs
// The QNetworkAccessFileBackend will right now only be used for PUT // The QNetworkAccessFileBackend will right now only be used for PUT
@ -1296,11 +1303,15 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
u"https", u"https",
u"preconnect-https", u"preconnect-https",
#endif #endif
u"unix+http",
}; };
// Since Qt 5 we use the new QNetworkReplyHttpImpl // Since Qt 5 we use the new QNetworkReplyHttpImpl
if (std::find(std::begin(httpSchemes), std::end(httpSchemes), scheme) != std::end(httpSchemes)) { if (std::find(std::begin(httpSchemes), std::end(httpSchemes), scheme) != std::end(httpSchemes)) {
#ifndef QT_NO_SSL #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()); QUrl stsUrl(request.url());
// RFC6797, 8.3: // RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818], // The UA MUST replace the URI scheme with "https" [RFC2818],
@ -1391,6 +1402,8 @@ QStringList QNetworkAccessManager::supportedSchemesImplementation() const
// Those ones don't exist in backends // Those ones don't exist in backends
#if QT_CONFIG(http) #if QT_CONFIG(http)
schemes << QStringLiteral("http"); schemes << QStringLiteral("http");
schemes << QStringLiteral("unix+http");
schemes << QStringLiteral("local+http");
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
if (QSslSocket::supportsSsl()) if (QSslSocket::supportsSsl())
schemes << QStringLiteral("https"); schemes << QStringLiteral("https");

View File

@ -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 if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
url = redirectUrl; 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: // RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818], // The UA MUST replace the URI scheme with "https" [RFC2818],
// and if the URI contains an explicit port component of "80", // and if the URI contains an explicit port component of "80",
@ -1242,9 +1243,12 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
url.setPort(443); url.setPort(443);
} }
const bool isLessSafe = schemeBefore == "https"_L1 && url.scheme() == "http"_L1; // Just to be on the safe side for local sockets, any changes to the scheme
if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy // are considered less safe
&& isLessSafe) { 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, error(QNetworkReply::InsecureRedirectError,
QCoreApplication::translate("QHttp", "Insecure redirect")); QCoreApplication::translate("QHttp", "Insecure redirect"));
return; return;