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
|
// moment and we are probably not done yet. So we extract url and set it
|
||||||
// here, if needed.
|
// here, if needed.
|
||||||
int statusCode = 0;
|
int statusCode = 0;
|
||||||
QUrl redirectUrl;
|
|
||||||
|
|
||||||
for (const auto &pair : headers) {
|
for (const auto &pair : headers) {
|
||||||
const auto &name = pair.name;
|
const auto &name = pair.name;
|
||||||
auto value = pair.value;
|
auto value = pair.value;
|
||||||
@ -1160,8 +1158,6 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
|
|||||||
if (ok)
|
if (ok)
|
||||||
httpReply->setContentLength(length);
|
httpReply->setContentLength(length);
|
||||||
} else {
|
} else {
|
||||||
if (name == "location")
|
|
||||||
redirectUrl = QUrl::fromEncoded(value);
|
|
||||||
QByteArray binder(", ");
|
QByteArray binder(", ");
|
||||||
if (name == "set-cookie")
|
if (name == "set-cookie")
|
||||||
binder = "\n";
|
binder = "\n";
|
||||||
@ -1226,8 +1222,20 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (QHttpNetworkReply::isHttpRedirect(statusCode) && redirectUrl.isValid())
|
if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
|
||||||
httpReply->setRedirectUrl(redirectUrl);
|
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) {
|
if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress) {
|
||||||
httpReplyPrivate->removeAutoDecompressHeader();
|
httpReplyPrivate->removeAutoDecompressHeader();
|
||||||
|
@ -130,6 +130,12 @@ void Http2Server::setAuthenticationHeader(const QByteArray &authentication)
|
|||||||
authenticationHeader = authentication;
|
authenticationHeader = authentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Http2Server::setRedirect(const QByteArray &url, int count)
|
||||||
|
{
|
||||||
|
redirectUrl = url;
|
||||||
|
redirectCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
void Http2Server::emulateGOAWAY(int timeout)
|
void Http2Server::emulateGOAWAY(int timeout)
|
||||||
{
|
{
|
||||||
Q_ASSERT(timeout >= 0);
|
Q_ASSERT(timeout >= 0);
|
||||||
@ -860,7 +866,10 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
|
|||||||
const QString url("%1://localhost:%2/");
|
const QString url("%1://localhost:%2/");
|
||||||
header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"),
|
header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"),
|
||||||
QString::number(targetPort)).toLatin1()});
|
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) {
|
} else if (!authenticationHeader.isEmpty() && !hasAuth) {
|
||||||
header.push_back({ ":status", "401" });
|
header.push_back({ ":status", "401" });
|
||||||
header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader));
|
header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader));
|
||||||
|
@ -90,6 +90,9 @@ public:
|
|||||||
void setContentEncoding(const QByteArray &contentEncoding);
|
void setContentEncoding(const QByteArray &contentEncoding);
|
||||||
// No authentication data is generated for the method, the full header value must be set
|
// No authentication data is generated for the method, the full header value must be set
|
||||||
void setAuthenticationHeader(const QByteArray &authentication);
|
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 emulateGOAWAY(int timeout);
|
||||||
void redirectOpenStream(quint16 targetPort);
|
void redirectOpenStream(quint16 targetPort);
|
||||||
|
|
||||||
@ -222,6 +225,9 @@ private:
|
|||||||
|
|
||||||
QByteArray contentEncoding;
|
QByteArray contentEncoding;
|
||||||
QByteArray authenticationHeader;
|
QByteArray authenticationHeader;
|
||||||
|
|
||||||
|
QByteArray redirectUrl;
|
||||||
|
int redirectCount = 0;
|
||||||
protected slots:
|
protected slots:
|
||||||
void ignoreErrorSlot();
|
void ignoreErrorSlot();
|
||||||
};
|
};
|
||||||
|
@ -112,6 +112,9 @@ private slots:
|
|||||||
void authenticationRequired_data();
|
void authenticationRequired_data();
|
||||||
void authenticationRequired();
|
void authenticationRequired();
|
||||||
|
|
||||||
|
void redirect_data();
|
||||||
|
void redirect();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
// Slots to listen to our in-process server:
|
// Slots to listen to our in-process server:
|
||||||
void serverStarted(quint16 port);
|
void serverStarted(quint16 port);
|
||||||
@ -1073,6 +1076,73 @@ void tst_Http2::authenticationRequired()
|
|||||||
QTRY_VERIFY(serverGotSettingsACK);
|
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)
|
void tst_Http2::serverStarted(quint16 port)
|
||||||
{
|
{
|
||||||
serverPort = port;
|
serverPort = port;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user