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:
parent
ae484cdbe1
commit
e6e5ef9141
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user