From 69de333e1fbb3b866ab77a9d6a4ad26d0796633b Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Thu, 22 Jun 2023 17:14:01 +0200 Subject: [PATCH] QNetworkReply: Propagate proxy errors properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map QNetworkError::HostNotFoundError and QNetworkError::ConnectionRefusedError to ProxyNotFoundError resp. ProxyConnectionRefusedError when it originated from the communication with the proxy server. Fixes: QTBUG-68821 Pick-to: 6.5 6.6 Change-Id: I21b91f2667ba0cd329d4ece1fe543472cdab2d22 Reviewed-by: Qt CI Bot Reviewed-by: MÃ¥rten Nordheim --- src/network/access/qhttpnetworkconnection.cpp | 12 ++++- .../access/qhttpnetworkconnectionchannel.cpp | 7 +++ .../qnetworkreply/tst_qnetworkreply.cpp | 46 ++++++++++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index dc9c9f610f4..55ac13d759f 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -1265,8 +1265,16 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info) 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, QNetworkReply::HostNotFoundError); + emitReplyError(channels[0].socket, channels[0].reply, lookupError); networkLayerState = QHttpNetworkConnectionPrivate::Unknown; } else if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { @@ -1274,7 +1282,7 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info) // emit error for all replies QHttpNetworkReply *currentReply = h2Pair.second; Q_ASSERT(currentReply); - emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError); + emitReplyError(channels[0].socket, currentReply, lookupError); } } else { // We can end up here if a request has been aborted or otherwise failed (e.g. timeout) diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 1e036bdc450..02ba8aa168e 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -949,6 +949,10 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket break; case QAbstractSocket::ConnectionRefusedError: errorCode = QNetworkReply::ConnectionRefusedError; +#ifndef QT_NO_NETWORKPROXY + if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) + errorCode = QNetworkReply::ProxyConnectionRefusedError; +#endif break; case QAbstractSocket::RemoteHostClosedError: // This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer. @@ -1030,6 +1034,9 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket } errorCode = QNetworkReply::TimeoutError; break; + case QAbstractSocket::ProxyConnectionRefusedError: + errorCode = QNetworkReply::ProxyConnectionRefusedError; + break; case QAbstractSocket::ProxyAuthenticationRequiredError: errorCode = QNetworkReply::ProxyAuthenticationRequiredError; break; diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 3d87f6cf960..14b104fdc8e 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -526,6 +526,9 @@ private Q_SLOTS: void notFoundWithCompression_data(); void notFoundWithCompression(); + void qtbug68821proxyError_data(); + void qtbug68821proxyError(); + // NOTE: This test must be last! void parentingRepliesToTheApp(); private: @@ -3852,7 +3855,6 @@ void tst_QNetworkReply::ioGetFromHttpWithSocksProxy() QVERIFY(reader.data.isEmpty()); QVERIFY(int(reply->error()) > 0); - QEXPECT_FAIL("", "QTcpSocket doesn't return enough information yet", Continue); QCOMPARE(int(reply->error()), int(QNetworkReply::ProxyConnectionRefusedError)); QCOMPARE(authspy.size(), 0); @@ -10094,6 +10096,48 @@ void tst_QNetworkReply::notFoundWithCompression() QCOMPARE(reply->readAll(), expected); } +void tst_QNetworkReply::qtbug68821proxyError_data() +{ + QTest::addColumn("proxyHost"); + QTest::addColumn("scheme"); + QTest::addColumn("error"); + + QTest::newRow("invalidhost+http") << "this-host-will-never-exist.qt-project.org" + << "http" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+http") << "localhost" + << "http" << QNetworkReply::ProxyConnectionRefusedError; +#ifndef QT_NO_SSL + QTest::newRow("invalidhost+https") << "this-host-will-never-exist.qt-project.org" + << "https" << QNetworkReply::ProxyNotFoundError; + QTest::newRow("localhost+https") << "localhost" + << "https" << QNetworkReply::ProxyConnectionRefusedError; +#endif +} + +void tst_QNetworkReply::qtbug68821proxyError() +{ + QTcpServer proxyServer; + QVERIFY(proxyServer.listen()); + quint16 proxyPort = proxyServer.serverPort(); + proxyServer.close(); + + QFETCH(QString, proxyHost); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, proxyHost, proxyPort); + + manager.setProxy(proxy); + + QFETCH(QString, scheme); + QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(scheme + "://example.com"))); + QSignalSpy spy(reply, &QNetworkReply::errorOccurred); + QVERIFY(spy.isValid()); + + QVERIFY(spy.wait(15000)); + + QFETCH(QNetworkReply::NetworkError, error); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0), error); +} + // NOTE: This test must be last testcase in tst_qnetworkreply! void tst_QNetworkReply::parentingRepliesToTheApp() {