Http2: Fix redirect-handling

The redirect handling for http2 was a little simple. E.g. not handling
relative URLs.
Fix this using the redirect response parsing function which the http1
protocol handler already uses.

Fixes: QTBUG-100651
Change-Id: Ic0cec4cacc92707e7a7fde1f4665f80995a6057e
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
(cherry picked from commit db5b8bbea3f3cf1675d2ddd449359b6fbedc523e)
This commit is contained in:
Mårten Nordheim 2022-02-09 17:40:48 +01:00
parent ae484cdbe1
commit e6e5ef9141
4 changed files with 100 additions and 7 deletions

View File

@ -1136,8 +1136,6 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
// moment and we are probably not done yet. So we extract url and set it
// here, if needed.
int statusCode = 0;
QUrl redirectUrl;
for (const auto &pair : headers) {
const auto &name = pair.name;
auto value = pair.value;
@ -1160,8 +1158,6 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
if (ok)
httpReply->setContentLength(length);
} else {
if (name == "location")
redirectUrl = QUrl::fromEncoded(value);
QByteArray binder(", ");
if (name == "set-cookie")
binder = "\n";
@ -1226,8 +1222,20 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
}
}
if (QHttpNetworkReply::isHttpRedirect(statusCode) && redirectUrl.isValid())
httpReply->setRedirectUrl(redirectUrl);
if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
QHttpNetworkConnectionPrivate::ParseRedirectResult result =
m_connection->d_func()->parseRedirectResponse(httpReply);
if (result.errorCode != QNetworkReply::NoError) {
auto errorString = m_connection->d_func()->errorDetail(result.errorCode, m_socket);
finishStreamWithError(stream, result.errorCode, errorString);
sendRST_STREAM(stream.streamID, INTERNAL_ERROR);
markAsReset(stream.streamID);
return;
}
if (result.redirectUrl.isValid())
httpReply->setRedirectUrl(result.redirectUrl);
}
if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress) {
httpReplyPrivate->removeAutoDecompressHeader();

View File

@ -130,6 +130,12 @@ void Http2Server::setAuthenticationHeader(const QByteArray &authentication)
authenticationHeader = authentication;
}
void Http2Server::setRedirect(const QByteArray &url, int count)
{
redirectUrl = url;
redirectCount = count;
}
void Http2Server::emulateGOAWAY(int timeout)
{
Q_ASSERT(timeout >= 0);
@ -860,7 +866,10 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
const QString url("%1://localhost:%2/");
header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"),
QString::number(targetPort)).toLatin1()});
} else if (redirectCount > 0) { // Not redirecting while reading, unlike above
--redirectCount;
header.push_back({":status", "308"});
header.push_back({"location", redirectUrl});
} else if (!authenticationHeader.isEmpty() && !hasAuth) {
header.push_back({ ":status", "401" });
header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader));

View File

@ -90,6 +90,9 @@ public:
void setContentEncoding(const QByteArray &contentEncoding);
// No authentication data is generated for the method, the full header value must be set
void setAuthenticationHeader(const QByteArray &authentication);
// Set the redirect URL and count. The server will return a redirect response with the url
// 'count' amount of times
void setRedirect(const QByteArray &redirectUrl, int count);
void emulateGOAWAY(int timeout);
void redirectOpenStream(quint16 targetPort);
@ -222,6 +225,9 @@ private:
QByteArray contentEncoding;
QByteArray authenticationHeader;
QByteArray redirectUrl;
int redirectCount = 0;
protected slots:
void ignoreErrorSlot();
};

View File

@ -112,6 +112,9 @@ private slots:
void authenticationRequired_data();
void authenticationRequired();
void redirect_data();
void redirect();
protected slots:
// Slots to listen to our in-process server:
void serverStarted(quint16 port);
@ -1073,6 +1076,73 @@ void tst_Http2::authenticationRequired()
QTRY_VERIFY(serverGotSettingsACK);
}
void tst_Http2::redirect_data()
{
QTest::addColumn<int>("maxRedirects");
QTest::addColumn<int>("redirectCount");
QTest::addColumn<bool>("success");
QTest::addRow("1-redirects-none-allowed-failure") << 0 << 1 << false;
QTest::addRow("1-redirects-success") << 1 << 1 << true;
QTest::addRow("2-redirects-1-allowed-failure") << 1 << 2 << false;
}
void tst_Http2::redirect()
{
QFETCH(const int, maxRedirects);
QFETCH(const int, redirectCount);
QFETCH(const bool, success);
const QByteArray redirectUrl = "/b.html"_qba;
clearHTTP2State();
serverPort = 0;
ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
targetServer->setRedirect(redirectUrl, redirectCount);
QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
runEventLoop();
QVERIFY(serverPort != 0);
nRequests = 1 + maxRedirects;
auto originalUrl = requestUrl(defaultConnectionType());
auto url = originalUrl;
url.setPath("/index.html");
QNetworkRequest request(url);
request.setMaximumRedirectsAllowed(maxRedirects);
// H2C might be used on macOS where SecureTransport doesn't support server-side ALPN
qputenv("QT_NETWORK_H2C_ALLOWED", "1");
auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); });
QScopedPointer<QNetworkReply> reply;
reply.reset(manager->get(request));
if (success) {
connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
} else {
connect(reply.get(), &QNetworkReply::errorOccurred, this,
&tst_Http2::replyFinishedWithError);
}
// Since we're using self-signed certificates,
// ignore SSL errors:
reply->ignoreSslErrors();
runEventLoop();
STOP_ON_FAILURE
if (success) {
QCOMPARE(reply->error(), QNetworkReply::NoError);
QCOMPARE(reply->url().toString(),
originalUrl.resolved(QString::fromLatin1(redirectUrl)).toString());
} else if (maxRedirects < redirectCount) {
QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError);
}
QTRY_VERIFY(serverGotSettingsACK);
}
void tst_Http2::serverStarted(quint16 port)
{
serverPort = port;