Skip to content

Commit

Permalink
Http: Add support for full localsocket paths
Browse files Browse the repository at this point in the history
[ChangeLog][QtNetwork][QNetworkAccessManager] QNetworkAccessManager now
supports using full local server name, as in, named pipes on Windows or path to
socket objects on Unix.

Task-number: QTBUG-102855
Change-Id: Ifc743f5025b3d8d0b558ecffff437881897915d9
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
  • Loading branch information
Morten242 committed May 24, 2024
1 parent 72b8c7d commit 48aad48
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 2 deletions.
7 changes: 6 additions & 1 deletion src/network/access/qhttpnetworkconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,12 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
if (value.isEmpty()) {
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)) {
Expand Down
10 changes: 10 additions & 0 deletions src/network/access/qhttpnetworkrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,5 +381,15 @@ void QHttpNetworkRequest::setPeerVerifyName(const QString &peerName)
d->peerVerifyName = peerName;
}

QString QHttpNetworkRequest::fullLocalServerName() const
{
return d->fullLocalServerName;
}

void QHttpNetworkRequest::setFullLocalServerName(const QString &fullServerName)
{
d->fullLocalServerName = fullServerName;
}

QT_END_NAMESPACE

4 changes: 4 additions & 0 deletions src/network/access/qhttpnetworkrequest_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class Q_AUTOTEST_EXPORT QHttpNetworkRequest: public QHttpNetworkHeader
QString peerVerifyName() const;
void setPeerVerifyName(const QString &peerName);

QString fullLocalServerName() const;
void setFullLocalServerName(const QString &fullServerName);

private:
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
friend class QHttpNetworkRequestPrivate;
Expand All @@ -140,6 +143,7 @@ class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate

QHttpNetworkRequest::Operation operation;
QByteArray customVerb;
QString fullLocalServerName; // for local sockets
QHttpNetworkRequest::Priority priority;
mutable QNonContiguousByteDevice* uploadByteDevice;
bool autoDecompress;
Expand Down
16 changes: 15 additions & 1 deletion src/network/access/qhttpthreaddelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ void QHttpThreadDelegate::startRequest()
}
}

QString extraData = httpRequest.peerVerifyName();
if (isLocalSocket) {
if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
extraData = path;
}

#ifndef QT_NO_NETWORKPROXY
if (transparentProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
Expand All @@ -302,10 +308,18 @@ void QHttpThreadDelegate::startRequest()
// the http object is actually a QHttpNetworkConnection
httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
if (!httpConnection) {

QString host = urlCopy.host();
// Update the host if a unix socket path or named pipe is used:
if (isLocalSocket) {
if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
host = path;
}

// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
httpConnection = new QNetworkAccessCachedHttpConnection(
http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
http1Parameters.numberOfConnectionsPerHost(), host, urlCopy.port(), ssl,
isLocalSocket, connectionType);
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
Expand Down
7 changes: 7 additions & 0 deletions src/network/access/qnetworkreplyhttpimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,13 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq

httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());

if (scheme.startsWith(("unix"_L1))) {
if (QVariant path = newHttpRequest.attribute(QNetworkRequest::FullLocalServerNameAttribute);
path.isValid() && path.canConvert<QString>()) {
httpRequest.setFullLocalServerName(path.toString());
}
}

// Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
// Propagate Http/2 settings:
Expand Down
10 changes: 10 additions & 0 deletions src/network/access/qnetworkrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
same-origin requests. This only affects the WebAssembly platform.
(This value was introduced in 6.5.)
\value FullLocalServerNameAttribute
Requests only, type: QMetaType::String
Holds the full local server name to be used for the underlying
QLocalSocket. This attribute is used by the QNetworkAccessManager
to connect to a specific local server, when QLocalSocket's behavior for
a simple name isn't enough. The URL in the QNetworkRequest must still
use unix+http: or local+http: scheme. And the hostname in the URL will
be used for the Host header in the HTTP request.
(This value was introduced in 6.8.)
\value User
Special type. Additional information can be passed in
QVariants with types ranging from User to UserMax. The default
Expand Down
1 change: 1 addition & 0 deletions src/network/access/qnetworkrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class Q_NETWORK_EXPORT QNetworkRequest
ConnectionCacheExpiryTimeoutSecondsAttribute,
Http2CleartextAllowedAttribute,
UseCredentialsAttribute,
FullLocalServerNameAttribute,

User = 1000,
UserMax = 32767
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ private slots:

void get();
void post();

#if QT_CONFIG(localserver)
void fullServerName_data();
void fullServerName();
#endif
};

void tst_QNetworkReply_local::initTestCase_data()
Expand Down Expand Up @@ -108,6 +113,64 @@ void tst_QNetworkReply_local::post()
QCOMPARE(firstRequest.receivedData.last(payload.size() + 4), "\r\n\r\n" + payload);
}

#if QT_CONFIG(localserver)
void tst_QNetworkReply_local::fullServerName_data()
{
#if defined(Q_OS_ANDROID) || defined(QT_PLATFORM_UIKIT)
QSKIP("While partially supported, the test as-is doesn't make sense on this platform.");
#else

QTest::addColumn<QString>("hostAndPath");

QTest::newRow("dummy-host") << u"://irrelevant/test"_s;
QTest::newRow("no-host") << u":///test"_s;
#endif
}

void tst_QNetworkReply_local::fullServerName()
{
QFETCH_GLOBAL(QString, scheme);
if (!scheme.startsWith("unix"_L1) && !scheme.startsWith("local"_L1))
return; // only relevant for local sockets

MiniHttpServerV2 server;
QLocalServer localServer;

QString path;
#ifdef Q_OS_WIN
path = uR"(\\.\pipe\qt_networkreply_test_fullServerName)"_s
% QString::number(QCoreApplication::applicationPid());
#else
path = u"/tmp/qt_networkreply_test_fullServerName"_s
% QString::number(QCoreApplication::applicationPid()) % u".sock"_s;
#endif

QVERIFY(localServer.listen(path));
server.bind(&localServer);

QFETCH(QString, hostAndPath);
QUrl url(scheme % hostAndPath);
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::FullLocalServerNameAttribute, path);

QNetworkAccessManager manager;
std::unique_ptr<QNetworkReply> reply(manager.get(req));

const bool res = QTest::qWaitFor([reply = reply.get()] { return reply->isFinished(); });
QVERIFY(res);

QCOMPARE(reply->readAll(), QByteArray("Hello World!"));
QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);

const QByteArray receivedData = server.peerStates().at(0).receivedData;
const QByteArray expectedGet = "GET " % url.path().toUtf8() % " HTTP/1.1\r\n";
QVERIFY(receivedData.startsWith(expectedGet));

const QByteArray expectedHost = "host: " % url.host().toUtf8() % "\r\n";
QVERIFY(receivedData.contains(expectedHost));
}
#endif

QTEST_MAIN(tst_QNetworkReply_local)

#include "tst_qnetworkreply_local.moc"
Expand Down

0 comments on commit 48aad48

Please sign in to comment.