Bug 1747230 - Fix IsUpgradeDowngradeEndlessLoop blocking legitimate redirects when redirecting to different query parameters a=dmeehan

This changes where the IsUpgradeDowngradeEndlessLoop check triggers.
Before this patch, it triggered during the redirect caused by the https
upgrade. With this patch, it triggers during the downgrade for http
redirects. META and JS redirect are still detected during upgrade.
This should be fixed as a follow up (See Bug 1896691).
Downgrade in this context means same url, except with the scheme http
instead of https.

Different query parameters normally lead to different responses by web servers.
Don't consider the '#ref' part of the uri, because it doesn't get send to
the server and therefore can't change the server response.

We can't use the redirect chain anymore, because the query parameters
are trimmed since Bug 1715785.

This also removes the config option dom.security.https_only_check_path_upgrade_downgrade_endless_loop,
because it adds unnecessary complexity. Removing it for this patch is
easier.

https-only, https-first and httpssvc_https_upgrade tests had to be
modified, because they depended on the incorrect handling of query
strings in loop detection.

Original Revision: https://phabricator.services.mozilla.com/D193672

Differential Revision: https://phabricator.services.mozilla.com/D214977
This commit is contained in:
Manuel Bucher 2024-06-27 13:01:12 +00:00
parent bdb4402d5c
commit 0da5cdc8ad
17 changed files with 352 additions and 261 deletions

View file

@ -8537,8 +8537,9 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
if (nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel()) { if (nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel()) {
nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo(); nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo();
if (!docLoadInfo->GetLoadErrorPage() && if (!docLoadInfo->GetLoadErrorPage() &&
nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef( nsHTTPSOnlyUtils::ShouldUpgradeConnection(docLoadInfo) &&
currentExposableURI, aLoadState->URI(), docLoadInfo)) { nsHTTPSOnlyUtils::IsHttpDowngrade(currentExposableURI,
aLoadState->URI())) {
uint32_t status = docLoadInfo->GetHttpsOnlyStatus(); uint32_t status = docLoadInfo->GetHttpsOnlyStatus();
if (status & (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED | if (status & (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) { nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {

View file

@ -266,7 +266,7 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI* aURI,
/* static */ /* static */
bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop( bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIURI* aOldURI, nsIURI* aNewURI, nsILoadInfo* aLoadInfo,
const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions) { const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions) {
// 1. Check if the HTTPS-Only/HTTPS-First is even enabled, before doing // 1. Check if the HTTPS-Only/HTTPS-First is even enabled, before doing
// anything else // anything else
@ -307,84 +307,47 @@ bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
return false; return false;
} }
// 5. If the URI to be loaded is not http, then it's defnitely no endless // 5. Check HTTP 3xx redirects. If the Principal that kicked off the
// loop caused by https-only.
if (!aURI->SchemeIs("http")) {
return false;
}
nsAutoCString uriHost;
aURI->GetAsciiHost(uriHost);
auto uriAndPrincipalComparator = [&](nsIPrincipal* aPrincipal) {
nsAutoCString principalHost;
aPrincipal->GetAsciiHost(principalHost);
bool checkPath = mozilla::StaticPrefs::
dom_security_https_only_check_path_upgrade_downgrade_endless_loop();
if (!checkPath) {
return uriHost.Equals(principalHost);
}
nsAutoCString uriPath;
nsresult rv = aURI->GetFilePath(uriPath);
if (NS_FAILED(rv)) {
return false;
}
nsAutoCString principalPath;
aPrincipal->GetFilePath(principalPath);
return uriHost.Equals(principalHost) && uriPath.Equals(principalPath);
};
// We do dot return early when we find a loop, as we still need to set an
// exception at the end.
bool isLoop = false;
// 6. Check actual redirects. If the Principal that kicked off the
// load/redirect is not https, then it's definitely not a redirect cause by // load/redirect is not https, then it's definitely not a redirect cause by
// https-only. If the scheme of the principal however is https and the // https-only. If the scheme of the principal however is https and the
// asciiHost of the URI to be loaded and the asciiHost of the Principal are // asciiHost of the URI to be loaded and the asciiHost of the Principal are
// identical, then we are dealing with an upgrade downgrade scenario and we // identical, then we are dealing with an upgrade downgrade scenario and we
// have to break the cycle. // have to break the cycle.
if (!aLoadInfo->RedirectChain().IsEmpty()) { if (IsHttpDowngrade(aOldURI, aNewURI)) {
nsCOMPtr<nsIPrincipal> redirectPrincipal; return true;
for (nsIRedirectHistoryEntry* entry : aLoadInfo->RedirectChain()) {
entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
if (redirectPrincipal && redirectPrincipal->SchemeIs("https") &&
uriAndPrincipalComparator(redirectPrincipal)) {
isLoop = true;
} }
}
} else { // TODO(Bug 1896691): Don't depend on triggeringPrincipal for JS/Meta
// 6.1 We should only check if this load is triggered by a user gesture // redirects. Call this function at the correct places instead
// when the redirect chain is empty, since this information is only useful
// in our case here. When the redirect chain is not empty, this load is // 6. Bug 1725026: Disable JS/Meta loop detection when the load was triggered
// defnitely triggered by redirection, not a user gesture. // by a user gesture. This information is only when the redirect chain is
// empty. When the redirect chain is not empty, this load is definitely
// triggered by redirection, not a user gesture.
// TODO(1896685): Verify whether check is still necessary.
if (aLoadInfo->RedirectChain().IsEmpty()) {
if (aLoadInfo->GetHasValidUserGestureActivation()) { if (aLoadInfo->GetHasValidUserGestureActivation()) {
return false; return false;
} }
} }
if (!isLoop) { // 7. Meta redirects and JS based redirects (win.location). We detect them
// 7. Meta redirects and JS based redirects (win.location). If the security // during the https upgrade internal redirect.
// context that triggered the load is not https, then it's defnitely no nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
// endless loop caused by https-only. If the scheme is http however and the
// asciiHost of the URI to be loaded matches the asciiHost of the Principal,
// then we are dealing with an upgrade downgrade scenario and we have to
// break the cycle.
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
aLoadInfo->TriggeringPrincipal();
if (!triggeringPrincipal->SchemeIs("https")) { if (!triggeringPrincipal->SchemeIs("https")) {
return false; return false;
} }
isLoop = uriAndPrincipalComparator(triggeringPrincipal);
}
if (isLoop && enforceForHTTPSFirstMode && // We detect Meta and JS based redirects during the upgrade. Check whether
mozilla::StaticPrefs:: // we are currently in an upgrade situation here.
dom_security_https_first_add_exception_on_failiure()) { if (!IsHttpDowngrade(aNewURI, aOldURI)) {
AddHTTPSFirstExceptionForSession(aURI, aLoadInfo); return false;
} }
// If we upgrade to the same URI that the load is origining from we are
// creating a redirect loop.
bool isLoop = false;
nsresult rv = triggeringPrincipal->EqualsURI(aNewURI, &isLoop);
NS_ENSURE_SUCCESS(rv, false);
return isLoop; return isLoop;
} }
@ -436,7 +399,8 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
} }
// https-first needs to account for breaking upgrade-downgrade endless // https-first needs to account for breaking upgrade-downgrade endless
// loops at this point because this function is called before we // loops at this point for meta and js redirects because this function
// is called before we
// check the redirect limit in HttpBaseChannel. If we encounter // check the redirect limit in HttpBaseChannel. If we encounter
// a same-origin server side downgrade from e.g https://example.com // a same-origin server side downgrade from e.g https://example.com
// to http://example.com then we simply not annotating the loadinfo // to http://example.com then we simply not annotating the loadinfo
@ -444,12 +408,18 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
// the handling for https-only mode is different from https-first mode, // the handling for https-only mode is different from https-first mode,
// because https-only mode results in an exception page in case // because https-only mode results in an exception page in case
// we encounter and endless upgrade downgrade loop. // we encounter and endless upgrade downgrade loop.
/*
bool isUpgradeDowngradeEndlessLoop = IsUpgradeDowngradeEndlessLoop( bool isUpgradeDowngradeEndlessLoop = IsUpgradeDowngradeEndlessLoop(
aURI, aLoadInfo, aURI, aURI, aLoadInfo,
{UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode}); {UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode});
if (isUpgradeDowngradeEndlessLoop) { if (isUpgradeDowngradeEndlessLoop) {
if (mozilla::StaticPrefs::
dom_security_https_first_add_exception_on_failiure()) {
nsHTTPSOnlyUtils::AddHTTPSFirstExceptionForSession(aURI, aLoadInfo);
}
return false; return false;
} }
*/
// We can upgrade the request - let's log to the console and set the status // We can upgrade the request - let's log to the console and set the status
// so we know that we upgraded the request. // so we know that we upgraded the request.
@ -900,38 +870,51 @@ bool nsHTTPSOnlyUtils::LoopbackOrLocalException(nsIURI* aURI) {
} }
/* static */ /* static */
bool nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(nsIURI* aHTTPSSchemeURI, bool nsHTTPSOnlyUtils::ShouldUpgradeConnection(nsILoadInfo* aLoadInfo) {
nsIURI* aOtherURI, // Check if one of parameters is null then webpage can't be loaded yet
nsILoadInfo* aLoadInfo) {
// 1. Check if one of parameters is null then webpage can't be loaded yet
// and no further inspections are needed // and no further inspections are needed
if (!aHTTPSSchemeURI || !aOtherURI || !aLoadInfo) { if (!aLoadInfo) {
return false; return false;
} }
// 2. If the URI to be loaded is not http, then same origin will be detected // Check if the HTTPS-Only Mode is even enabled, before we do anything else
// already
if (!mozilla::net::SchemeIsHTTP(aOtherURI)) {
return false;
}
// 3. Check if the HTTPS-Only Mode is even enabled, before we do anything else
bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
if (!IsHttpsOnlyModeEnabled(isPrivateWin) && if (!IsHttpsOnlyModeEnabled(isPrivateWin) &&
!IsHttpsFirstModeEnabled(isPrivateWin)) { !IsHttpsFirstModeEnabled(isPrivateWin)) {
return false; return false;
} }
// 4. If the load is exempt, then it's defintely not related to https-only // If the load is exempt, then don't upgrade
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus(); uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) { if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
return false; return false;
} }
return true;
}
// 5. Create a new target URI with 'https' instead of 'http' and compare it /* static */
// to the current URI bool nsHTTPSOnlyUtils::IsHttpDowngrade(nsIURI* aFromURI, nsIURI* aToURI) {
MOZ_ASSERT(aFromURI);
MOZ_ASSERT(aToURI);
if (!aFromURI || !aToURI) {
return false;
}
// 2. If the target URI is not http, then it's not a http downgrade
if (!mozilla::net::SchemeIsHTTP(aToURI)) {
return false;
}
// 3. If the origin URI isn't https, then it's not a http downgrade either.
if (!mozilla::net::SchemeIsHTTPS(aFromURI)) {
return false;
}
// 4. Create a new target URI with 'https' instead of 'http' and compare it
// to the origin URI
int32_t port = 0; int32_t port = 0;
nsresult rv = aOtherURI->GetPort(&port); nsresult rv = aToURI->GetPort(&port);
NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_SUCCESS(rv, false);
// a port of -1 indicates the default port, hence we upgrade from port 80 to // a port of -1 indicates the default port, hence we upgrade from port 80 to
// port 443 // port 443
@ -940,15 +923,14 @@ bool nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(nsIURI* aHTTPSSchemeURI,
port = NS_GetDefaultPort("https"); port = NS_GetDefaultPort("https");
} }
nsCOMPtr<nsIURI> newHTTPSchemeURI; nsCOMPtr<nsIURI> newHTTPSchemeURI;
rv = NS_MutateURI(aOtherURI) rv = NS_MutateURI(aToURI)
.SetScheme("https"_ns) .SetScheme("https"_ns)
.SetPort(port) .SetPort(port)
.Finalize(newHTTPSchemeURI); .Finalize(newHTTPSchemeURI);
NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_SUCCESS(rv, false);
bool uriEquals = false; bool uriEquals = false;
if (NS_FAILED( if (NS_FAILED(aFromURI->EqualsExceptRef(newHTTPSchemeURI, &uriEquals))) {
aHTTPSSchemeURI->EqualsExceptRef(newHTTPSchemeURI, &uriEquals))) {
return false; return false;
} }

View file

@ -77,7 +77,7 @@ class nsHTTPSOnlyUtils {
EnforceForHTTPSRR, EnforceForHTTPSRR,
}; };
static bool IsUpgradeDowngradeEndlessLoop( static bool IsUpgradeDowngradeEndlessLoop(
nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIURI* aOldURI, nsIURI* aNewURI, nsILoadInfo* aLoadInfo,
const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions = const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions =
{}); {});
@ -154,16 +154,19 @@ class nsHTTPSOnlyUtils {
static bool IsSafeToAcceptCORSOrMixedContent(nsILoadInfo* aLoadInfo); static bool IsSafeToAcceptCORSOrMixedContent(nsILoadInfo* aLoadInfo);
/** /**
* Checks if two URIs are same origin modulo the difference that * Checks if https only or https first mode is enabled for this load
* aHTTPSchemeURI uses an http scheme.
* @param aHTTPSSchemeURI nsIURI using scheme of https
* @param aOtherURI nsIURI using scheme of http
* @param aLoadInfo nsILoadInfo of the request * @param aLoadInfo nsILoadInfo of the request
*/
static bool ShouldUpgradeConnection(nsILoadInfo* aLoadInfo);
/**
* Checks if two URIs are same origin modulo the difference that
* aToURI scheme is downgraded to http from https aFromURI.
* @param aFromURI nsIURI using scheme of https
* @param aToURI nsIURI using scheme of http
* @return true, if URIs are equal except scheme and ref * @return true, if URIs are equal except scheme and ref
*/ */
static bool IsEqualURIExceptSchemeAndRef(nsIURI* aHTTPSSchemeURI, static bool IsHttpDowngrade(nsIURI* aFromURI, nsIURI* aToURI);
nsIURI* aOtherURI,
nsILoadInfo* aLoadInfo);
/** /**
* Will add a special temporary HTTPS-Only exception that only applies to * Will add a special temporary HTTPS-Only exception that only applies to

View file

@ -1,61 +1,96 @@
"use strict"; "use strict";
const REDIRECT_URI = // DOWNGRADE_REDIRECT_*: http instead of https, otherwise same path
"http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?verify"; const DOWNGRADE_REDIRECT_META = `
const DOWNGRADE_URI = <html>
"http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs"; <head>
const RESPONSE_ERROR = "unexpected-query"; <meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?downgrade_redirect_meta'">
</head>
<body>
META REDIRECT
</body>
</html>`;
// An onload postmessage to window opener const DOWNGRADE_REDIRECT_JS = `
const RESPONSE_HTTPS_SCHEME = `
<html> <html>
<body> <body>
<script type="application/javascript"> JS REDIRECT
window.opener.postMessage({result: 'scheme-https'}, '*'); <script>
let url= "http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?downgrade_redirect_js";
window.location = url;
</script> </script>
</body> </body>
</html>`; </html>`;
// REDIRECT_*: different path and http instead of https
const REDIRECT_META = `
<html>
<head>
<meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs?redirect_meta'">
</head>
<body>
META REDIRECT
</body>
</html>`;
const REDIRECT_JS = `
<html>
<body>
JS REDIRECT
<script>
let url= "http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs?redirect_js";
window.location = url;
</script>
</body>
</html>`;
// An onload postmessage to window opener
const RESPONSE_HTTP_SCHEME = ` const RESPONSE_HTTP_SCHEME = `
<html> <html>
<body> <body>
<script type="application/javascript"> <script type="application/javascript">
window.opener.postMessage({result: 'scheme-http'}, '*'); window.opener.postMessage({result: 'scheme-http-'+window.location}, '*');
</script> </script>
</body> </body>
</html>`; </html>`;
function handleRequest(request, response) { function handleRequest(request, response) {
response.setHeader("Cache-Control", "no-cache", false); response.setHeader("Cache-Control", "no-cache", false);
const query = request.queryString;
if (query == "downgrade") { if (request.scheme == "https") {
// send same-origin downgrade from https: to http: with a different path. // allow http status code as parameter
// we don't consider it's an endless upgrade downgrade loop in this case. const query = request.queryString.split("=");
response.setStatusLine(request.httpVersion, 302, "Found"); if (query[0] == "downgrade_redirect_http") {
response.setHeader("Location", DOWNGRADE_URI, false); let location = `http://${request.host}${request.path}?${request.queryString}`;
return; response.setStatusLine(request.httpVersion, query[1], "Found");
} response.setHeader("Location", location, false);
} else if (query[0] == "redirect_http") {
// handle the redirect case response.setStatusLine(request.httpVersion, query[1], "Found");
if ((query >= 301 && query <= 303) || query == 307) { let location =
// send same-origin downgrade from https: to http: again simluating "http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs?" +
// and endless upgrade downgrade loop. request.queryString;
response.setStatusLine(request.httpVersion, query, "Found"); response.setHeader("Location", location, false);
response.setHeader("Location", REDIRECT_URI, false); } else if (query[0] == "downgrade_redirect_js") {
return;
}
// Check if scheme is http:// or https://
if (query == "verify") {
let response_content =
request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME;
response.setStatusLine(request.httpVersion, 200, "OK"); response.setStatusLine(request.httpVersion, 200, "OK");
response.write(response_content); response.write(DOWNGRADE_REDIRECT_JS);
return; } else if (query[0] == "redirect_js") {
} response.setStatusLine(request.httpVersion, 200, "OK");
response.write(REDIRECT_JS);
} else if (query[0] == "downgrade_redirect_meta") {
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(DOWNGRADE_REDIRECT_META);
} else if (query[0] == "redirect_meta") {
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(REDIRECT_META);
} else {
// We should never get here, but just in case ... // We should never get here, but just in case ...
response.setStatusLine(request.httpVersion, 500, "OK"); response.setStatusLine(request.httpVersion, 500, "OK");
response.write("unexepcted query"); response.write("unexepcted query");
} }
return;
}
// return http response
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(RESPONSE_HTTP_SCHEME);
}

View file

@ -5,7 +5,7 @@ const RESPONSE_HTTPS_SCHEME = `
<html> <html>
<body> <body>
<script type="application/javascript"> <script type="application/javascript">
window.opener.postMessage({result: 'scheme-https'}, '*'); window.opener.postMessage({result: 'scheme-https-'+window.location}, '*');
</script> </script>
</body> </body>
</html>`; </html>`;
@ -14,7 +14,7 @@ const RESPONSE_HTTP_SCHEME = `
<html> <html>
<body> <body>
<script type="application/javascript"> <script type="application/javascript">
window.opener.postMessage({result: 'scheme-http'}, '*'); window.opener.postMessage({result: 'scheme-http-'+window.location}'}, '*');
</script> </script>
</body> </body>
</html>`; </html>`;

View file

@ -9,6 +9,10 @@ const OTHERHOST_REDIRECT_URI_HTTP =
"http://example.org/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; "http://example.org/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify";
const REDIRECT_URI_HTTPS = const REDIRECT_URI_HTTPS =
"https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify";
const REDIRECT_DOWNGRADE_URI =
"https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?downgrade";
const REDIRECT_DOWNGRADE_URI_HTTP =
"http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?downgrade";
const RESPONSE_ERROR = "unexpected-query"; const RESPONSE_ERROR = "unexpected-query";
@ -52,6 +56,10 @@ function sendRedirection(query, response) {
if (query.includes("test4")) { if (query.includes("test4")) {
response.setHeader("Location", OTHERHOST_REDIRECT_URI_HTTP, false); response.setHeader("Location", OTHERHOST_REDIRECT_URI_HTTP, false);
} }
// send a redirection http downgrade uri
if (query.includes("test5")) {
response.setHeader("Location", REDIRECT_DOWNGRADE_URI, false);
}
} }
function handleRequest(request, response) { function handleRequest(request, response) {
@ -64,6 +72,7 @@ function handleRequest(request, response) {
if (request.scheme !== "https") { if (request.scheme !== "https") {
response.setStatusLine(request.httpVersion, 500, "OK"); response.setStatusLine(request.httpVersion, 500, "OK");
response.write("Request should have been HTTPS."); response.write("Request should have been HTTPS.");
return;
} }
// send a 302 redirection // send a 302 redirection
response.setStatusLine(request.httpVersion, 302, "Found"); response.setStatusLine(request.httpVersion, 302, "Found");
@ -75,11 +84,20 @@ function handleRequest(request, response) {
if (request.scheme !== "https") { if (request.scheme !== "https") {
response.setStatusLine(request.httpVersion, 500, "OK"); response.setStatusLine(request.httpVersion, 500, "OK");
response.write("Request should have been HTTPS."); response.write("Request should have been HTTPS.");
return;
} }
response.setStatusLine(request.httpVersion, 302, "Found"); response.setStatusLine(request.httpVersion, 302, "Found");
sendRedirection(query, response); sendRedirection(query, response);
return; return;
} }
// Send a http redirect downgrade
if (query.includes("downgrade")) {
response.setStatusLine(request.httpVersion, 302, "Found");
let redirect_uri =
request.scheme === "https" ? REDIRECT_DOWNGRADE_URI : REDIRECT_URI_HTTP;
response.setHeader("Location", redirect_uri, false);
return;
}
// Reset the HSTS policy, prevent influencing other tests // Reset the HSTS policy, prevent influencing other tests
if (request.queryString === "reset") { if (request.queryString === "reset") {
response.setHeader("Strict-Transport-Security", "max-age=0"); response.setHeader("Strict-Transport-Security", "max-age=0");

View file

@ -20,55 +20,64 @@ Test that same origin redirect does not cause endless loop with https-first enab
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
const redirectCodes = ["301", "302","303","307"]; let testQueries = [
// Those are clear downgrades. Need to load http site
{ query: "downgrade_redirect_meta", result: "http" },
{ query: "downgrade_redirect_js", result: "http" },
{ query: "downgrade_redirect_http=301", result: "http" },
{ query: "downgrade_redirect_http=302", result: "http" },
{ query: "downgrade_redirect_http=303", result: "http" },
{ query: "downgrade_redirect_http=307", result: "http" },
// from here it isn't required to downgrade. Could be upgraded again
{ query: "redirect_meta", result: "https" },
{ query: "redirect_js", result: "https" },
{ query: "redirect_http=301", result: "https" },
{ query: "redirect_http=302", result: "https" },
{ query: "redirect_http=303", result: "https" },
{ query: "redirect_http=307", result: "https" },
];
let currentTest = 0; let currentTest = 0;
// do each test two time. One time starting with https:// one time with http://
let currentTestStartWithHttps = false;
let testWin; let testWin;
window.addEventListener("message", receiveMessage); window.addEventListener("message", receiveMessage);
// receive message from loaded site verifying the scheme of // receive message from loaded site verifying the scheme of
// the loaded document. // the loaded document.
async function receiveMessage(event) { async function receiveMessage(event) {
let currentRedirectCode = redirectCodes[currentTest]; let currentTestParams = testQueries[Math.floor(currentTest / 2)];
is(event.data.result, let expectedURI;
"scheme-http", if(currentTestParams.result == "https") {
"same-origin redirect results in 'http' for " + currentRedirectCode expectedURI = "https://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs?" + currentTestParams.query;
} else {
expectedURI = "http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?" + currentTestParams.query;
}
is(`scheme-${currentTestParams.result}-${expectedURI}`,
event.data.result,
`${currentTest}: redirect results in '${currentTestParams.result}' for ${expectedURI}`
); );
testWin.close(); testWin.close();
await SpecialPowers.removePermission( await SpecialPowers.removePermission(
"https-only-load-insecure", "https-only-load-insecure",
"http://example.com" "http://example.com"
); );
if (++currentTest < redirectCodes.length) { // each test gets run starting with http:// and https://. Therefore *2
if (++currentTest < 2 * testQueries.length) {
// start next case
startTest(); startTest();
return; return;
} }
// cleanup
window.removeEventListener("message", receiveMessage); window.removeEventListener("message", receiveMessage);
window.addEventListener("message", receiveMessageForDifferentPathTest);
testDifferentPath();
}
async function receiveMessageForDifferentPathTest(event) {
is(event.data.result,
"scheme-https",
"scheme should be https when the path is different"
);
testWin.close();
window.removeEventListener("message", receiveMessageForDifferentPathTest);
SimpleTest.finish(); SimpleTest.finish();
} }
async function startTest() { async function startTest() {
const currentCode = redirectCodes[currentTest]; const currentTestParams = testQueries[Math.floor(currentTest / 2)];
const scheme = currentTest % 2 == 0 ? "https" : "http";
// Load an http:// window which gets upgraded to https:// // Load an http:// window which gets upgraded to https://
let uri = let uri =
`http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?${currentCode}`; `${scheme}://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?${currentTestParams.query}`;
testWin = window.open(uri);
}
async function testDifferentPath() {
// Load an https:// window which gets downgraded to http://
let uri =
`https://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?downgrade`;
testWin = window.open(uri); testWin = window.open(uri);
} }
@ -77,7 +86,6 @@ Test that same origin redirect does not cause endless loop with https-first enab
["dom.security.https_first", true], ["dom.security.https_first", true],
["security.mixed_content.block_active_content", false], ["security.mixed_content.block_active_content", false],
["security.mixed_content.block_display_content", false], ["security.mixed_content.block_display_content", false],
["dom.security.https_only_check_path_upgrade_downgrade_endless_loop", true],
]}, startTest); ]}, startTest);
</script> </script>
</body> </body>

View file

@ -21,10 +21,8 @@ Test multiple redirects using https-first and ensure the entire redirect chain i
const testCase = [ const testCase = [
// test 1: https-first upgrades http://example.com/test1 -> https://example.com/test1 // test 1: https-first upgrades http://example.com/test1 -> https://example.com/test1
// that's redirect to https://example.com/.../redirect which then redirects // that's redirect to https://example.com/.../redirect which then redirects
// to http://example.com/../verify. Since the last redirect is http, and the // to http://example.com/../verify.
// the redirection chain contains already example.com we expect https-first {name: "test last redirect HTTP", result: "scheme-https", query: "test1" },
// to downgrade the request.
{name: "test last redirect HTTP", result: "scheme-http", query: "test1" },
// test 2: https-first upgrades http://example.com/test2 -> https://example.com/test2 // test 2: https-first upgrades http://example.com/test2 -> https://example.com/test2
// that's redirect to https://example.com/.../redirect which then redirects // that's redirect to https://example.com/.../redirect which then redirects
// to https://example.com/../verify. Since the last redirect is https, we // to https://example.com/../verify. Since the last redirect is https, we
@ -43,6 +41,13 @@ Test multiple redirects using https-first and ensure the entire redirect chain i
// http://example.org/.../verify -upgrade-> httpS://example.ORG/.../verify // http://example.org/.../verify -upgrade-> httpS://example.ORG/.../verify
// Everything should be upgraded and accessed only via HTTPS! // Everything should be upgraded and accessed only via HTTPS!
{name: "test last redirect other HTTP origin gets upgraded", result: "scheme-https", query: "test4" }, {name: "test last redirect other HTTP origin gets upgraded", result: "scheme-https", query: "test4" },
// test 5: https-first upgrades http://example.com/test5 -> https://example.com/test5
// that's redirect to https://example.com/.../downgrade which then redirects
// https-first upgrades http://example.com/.../downgrade -> https://example.com/.../downgrade
// that's redirect to http://example.com/.../downgrade which which is detected as http downgrade
// to http://example.com/../verify. Since the last redirect is http, and we
// had a downgrade in the redirect chain. We load the http version
{name: "test downgrade HTTP", result: "scheme-http", query: "test5" },
] ]
let currentTest = 0; let currentTest = 0;
let testWin; let testWin;

View file

@ -4,7 +4,7 @@
const REDIRECT_META = ` const REDIRECT_META = `
<html> <html>
<head> <head>
<meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test1b'"> <meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test1'">
</head> </head>
<body> <body>
META REDIRECT META REDIRECT
@ -16,17 +16,17 @@ const REDIRECT_JS = `
<body> <body>
JS REDIRECT JS REDIRECT
<script> <script>
let url= "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test2b"; let url= "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test2";
window.location = url; window.location = url;
</script> </script>
</body> </body>
</html>`; </html>`;
const REDIRECT_302 = const REDIRECT_302 =
"http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test3b"; "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test3";
const REDIRECT_302_DIFFERENT_PATH = const REDIRECT_302_DIFFERENT_PATH =
"http://example.com/tests/dom/security/test/https-only/file_user_gesture.html"; "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?verify";
function handleRequest(request, response) { function handleRequest(request, response) {
// avoid confusing cache behaviour // avoid confusing cache behaviour
@ -35,30 +35,35 @@ function handleRequest(request, response) {
// if the scheme is not https, meaning that the initial request did not // if the scheme is not https, meaning that the initial request did not
// get upgraded, then we rather fall through and display unexpected content. // get upgraded, then we rather fall through and display unexpected content.
if (request.scheme === "https") { if (request.scheme == "https") {
let query = request.queryString; let query = request.queryString;
if (query === "test1a") { if (query == "test1") {
response.write(REDIRECT_META); response.write(REDIRECT_META);
return; return;
} }
if (query === "test2a") { if (query == "test2") {
response.write(REDIRECT_JS); response.write(REDIRECT_JS);
return; return;
} }
if (query === "test3a") { if (query == "test3") {
response.setStatusLine("1.1", 302, "Found"); response.setStatusLine("1.1", 302, "Found");
response.setHeader("Location", REDIRECT_302, false); response.setHeader("Location", REDIRECT_302, false);
return; return;
} }
if (query === "test4a") { if (query == "test4") {
response.setStatusLine("1.1", 302, "Found"); response.setStatusLine("1.1", 302, "Found");
response.setHeader("Location", REDIRECT_302_DIFFERENT_PATH, false); response.setHeader("Location", REDIRECT_302_DIFFERENT_PATH, false);
return; return;
} }
if (query == "verify") {
response.write("<html><body>OK :)</body></html>");
return;
}
} }
// we should never get here, just in case, // we should never get here, just in case,

View file

@ -16,73 +16,85 @@
* Test 1: Meta Refresh * Test 1: Meta Refresh
* Test 2: JS Redirect * Test 2: JS Redirect
* Test 3: 302 redirect * Test 3: 302 redirect
* Test 4: Redirect to different origin. No redirect loop should be detected
*/ */
SimpleTest.requestFlakyTimeout("We need to wait for the HTTPS-Only error page to appear");
SimpleTest.requestLongerTimeout(10);
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
const REQUEST_URL = const HTTP_REQUEST_URL =
"http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs"; "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs";
const HTTPS_REQUEST_URL =
"https://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs";
function resolveAfter6Seconds() { const testQueries = [
return new Promise(resolve => { // Test 1: Meta Refresh Redirect
setTimeout(() => { { scheme: "http", query: "test1", error: true },
resolve(); { scheme: "https", query: "test1", error: true },
}, 6000); // Test 2: JS win.location Redirect
}); { scheme: "http", query: "test2", error: true },
} { scheme: "https", query: "test2", error: true },
// Test 3: 302 Redirect
{ scheme: "http", query: "test3", error: true },
{ scheme: "https", query: "test3", error: true },
// Test 4: 302 Redirect with a different path
{ scheme: "http", query: "test4", error: false },
{ scheme: "https", query: "test4", error: false },
];
async function verifyResult(aTestName) { let currentTest = 0;
let errorPageL10nId = "about-httpsonly-title-alert"; // do each test two time. One time starting with https:// one time with http://
let testWin;
window.addEventListener("message", receiveMessageWhenLoaded);
function postMessageWhenLoaded() {
SimpleTest.waitForCondition(async () => {
return await SpecialPowers.spawn(testWin, [], () => {
let innerHTML = content.document.body.innerHTML; let innerHTML = content.document.body.innerHTML;
ok(innerHTML.includes(errorPageL10nId), "the error page should be shown for " + aTestName); return innerHTML == "OK :)"
} || innerHTML == "DO NOT DISPLAY THIS"
|| innerHTML.includes("about-httpsonly-title-alert");
async function verifyTest4Result() { }).catch(() => false);
let pathname = content.document.location.pathname; },
ok( () => window.postMessage("https-only-page-loaded", "*"),
pathname === "/tests/dom/security/test/https-only/file_user_gesture.html", "waiting for page load to complete"
"the http:// page should be loaded"
); );
} }
async function runTests() { async function receiveMessageWhenLoaded() {
await SpecialPowers.pushPrefEnv({ set: [ const currentTestParams = testQueries[currentTest];
["dom.security.https_only_mode", true], let testName = currentTestParams.scheme + ":" + currentTestParams.query
["dom.security.https_only_mode_break_upgrade_downgrade_endless_loop", true],
["dom.security.https_only_check_path_upgrade_downgrade_endless_loop", true],
]});
// Test 1: Meta Refresh Redirect let innerHTML = await SpecialPowers.spawn(testWin, [], () => {
let winTest1 = window.open(REQUEST_URL + "?test1a", "_blank"); return content.document.body.innerHTML;
// Test 2: JS win.location Redirect });
let winTest2 = window.open(REQUEST_URL + "?test2a", "_blank"); if(currentTestParams.error) {
// Test 3: 302 Redirect ok(innerHTML.includes("about-httpsonly-title-alert"), testName + ": the error page should be shown");
let winTest3 = window.open(REQUEST_URL + "?test3a", "_blank"); } else {
// Test 4: 302 Redirect with a different path is(innerHTML, "OK :)", testName + ": different path with https loaded ");
let winTest4 = window.open(REQUEST_URL + "?test4a", "_blank"); }
testWin.close();
// provide enough time for:
// the redirects to occur, and the error page to be displayed
await resolveAfter6Seconds();
await SpecialPowers.spawn(winTest1, ["test1"], verifyResult);
winTest1.close();
await SpecialPowers.spawn(winTest2, ["test2"], verifyResult);
winTest2.close();
await SpecialPowers.spawn(winTest3, ["test3"], verifyResult);
winTest3.close();
await SpecialPowers.spawn(winTest4, ["test4"], verifyTest4Result);
winTest4.close();
if (++currentTest < testQueries.length) {
runNextTest();
return;
}
// no more tests to run -> cleanup
window.removeEventListener("https-only-page-load", receiveMessageWhenLoaded);
SimpleTest.finish(); SimpleTest.finish();
} }
runTests(); async function runNextTest() {
const currentTestParams = testQueries[currentTest];
let uri = `${currentTestParams.scheme}://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?${currentTestParams.query}`;
testWin = window.open(uri, "_blank");
postMessageWhenLoaded();
}
SpecialPowers.pushPrefEnv({ set: [
["dom.security.https_only_mode", true],
["dom.security.https_only_mode_break_upgrade_downgrade_endless_loop", true],
["dom.security.https_only_mode_ever_enabled", true], // clear this pref at the end
]}, runNextTest);
</script> </script>
</body> </body>

View file

@ -3751,13 +3751,6 @@
value: true value: true
mirror: always mirror: always
# If true, when checking if it's upgrade downgrade cycles, the URI path will be
# also checked.
- name: dom.security.https_only_check_path_upgrade_downgrade_endless_loop
type: RelaxedAtomicBool
value: true
mirror: always
# If true and HTTPS-only mode is enabled, requests # If true and HTTPS-only mode is enabled, requests
# to local IP addresses are also upgraded # to local IP addresses are also upgraded
- name: dom.security.https_only_mode.upgrade_local - name: dom.security.https_only_mode.upgrade_local

View file

@ -5969,7 +5969,8 @@ HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const { nsresult HttpBaseChannel::CheckRedirectLimit(nsIURI* aNewURI,
uint32_t aRedirectFlags) const {
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
// for internal redirect due to auth retry we do not have any limit // for internal redirect due to auth retry we do not have any limit
// as we might restrict the number of times a user might retry // as we might restrict the number of times a user might retry
@ -6003,15 +6004,39 @@ nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
// https and the page answers with a redirect (meta, 302, win.location, ...) // https and the page answers with a redirect (meta, 302, win.location, ...)
// then this method can break the cycle which causes the https-only exception // then this method can break the cycle which causes the https-only exception
// page to appear. Note that https-first mode breaks upgrade downgrade endless // page to appear. Note that https-first mode breaks upgrade downgrade endless
// loops within ShouldUpgradeHTTPSFirstRequest because https-first does not // loops within ShouldUpgradeHttpsFirstRequest because https-first does not
// display an exception page but needs a soft fallback/downgrade. // display an exception page but needs a soft fallback/downgrade.
if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop( if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
mURI, mLoadInfo, mURI, aNewURI, mLoadInfo,
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions:: {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
EnforceForHTTPSOnlyMode})) { EnforceForHTTPSOnlyMode})) {
// Mark that we didn't upgrade to https due to loop detection in https-only
// mode to show https-only error page. We know that we are in https-only
// mode, because we passed `EnforceForHTTPSOnlyMode` to
// `IsUpgradeDowngradeEndlessLoop`. In other words we upgrade the request
// with https-only mode, but then immediately cancel the request.
uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
httpsOnlyStatus ^= nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
httpsOnlyStatus |=
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
mLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
}
LOG(("upgrade downgrade redirect loop!\n")); LOG(("upgrade downgrade redirect loop!\n"));
return NS_ERROR_REDIRECT_LOOP; return NS_ERROR_REDIRECT_LOOP;
} }
// in case of http-first mode we want to add an exception to disable the
// upgrade behavior if we have upgrade-downgrade loop to break the loop and
// load the http request next
if (mozilla::StaticPrefs::
dom_security_https_first_add_exception_on_failiure() &&
nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
mURI, aNewURI, mLoadInfo,
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
EnforceForHTTPSFirstMode})) {
nsHTTPSOnlyUtils::AddHTTPSFirstExceptionForSession(mURI, mLoadInfo);
}
return NS_OK; return NS_OK;
} }

View file

@ -651,7 +651,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
static void CallTypeSniffers(void* aClosure, const uint8_t* aData, static void CallTypeSniffers(void* aClosure, const uint8_t* aData,
uint32_t aCount); uint32_t aCount);
nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const; nsresult CheckRedirectLimit(nsIURI* aNewURI, uint32_t aRedirectFlags) const;
bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener, bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener,
nsISupports* aContext); nsISupports* aContext);

View file

@ -80,7 +80,7 @@ nsresult InterceptedHttpChannel::SetupReplacementChannel(
return rv; return rv;
} }
rv = CheckRedirectLimit(aRedirectFlags); rv = CheckRedirectLimit(aURI, aRedirectFlags);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// While we can't resume an synthetic response, we can still propagate // While we can't resume an synthetic response, we can still propagate

View file

@ -1165,7 +1165,7 @@ nsresult TRRServiceChannel::SetupReplacementChannel(nsIURI* aNewURI,
return rv; return rv;
} }
rv = CheckRedirectLimit(aRedirectFlags); rv = CheckRedirectLimit(aNewURI, aRedirectFlags);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);

View file

@ -703,23 +703,7 @@ nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade,
nsAutoCString uriHost; nsAutoCString uriHost;
mURI->GetAsciiHost(uriHost); mURI->GetAsciiHost(uriHost);
if (gHttpHandler->IsHostExcludedForHTTPSRR(uriHost)) { return gHttpHandler->IsHostExcludedForHTTPSRR(uriHost);
return true;
}
if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
mURI, mLoadInfo,
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
EnforceForHTTPSRR})) {
// Add the host to a excluded list because:
// 1. We don't need to do the same check again.
// 2. Other subresources in the same host will be also excluded.
gHttpHandler->ExcludeHTTPSRRHost(uriHost);
LOG(("[%p] skip HTTPS upgrade for host [%s]", this, uriHost.get()));
return true;
}
return false;
}; };
if (shouldSkipUpgradeWithHTTPSRR()) { if (shouldSkipUpgradeWithHTTPSRR()) {
@ -5413,7 +5397,27 @@ nsresult nsHttpChannel::SetupReplacementChannel(nsIURI* newURI,
newURI, newChannel, preserveMethod, redirectFlags); newURI, newChannel, preserveMethod, redirectFlags);
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
rv = CheckRedirectLimit(redirectFlags); nsAutoCString uriHost;
mURI->GetAsciiHost(uriHost);
// disable https-rr when encountering a downgrade from https to http.
// If the host would have https-rr dns-entries, it would be misconfigured
// due to giving us mixed signals:
// 1. the signal to upgrade all http requests to https,
// 2. but also downgrading to http on https via redirects.
// Add to exclude list for that reason
if (!gHttpHandler->IsHostExcludedForHTTPSRR(uriHost) &&
nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
mURI, newURI, mLoadInfo,
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
EnforceForHTTPSRR})) {
// Add the host to a excluded list because:
// 1. We don't need to do the same check again.
// 2. Other subresources in the same host will be also excluded.
gHttpHandler->ExcludeHTTPSRRHost(uriHost);
LOG(("[%p] skip HTTPS upgrade for host [%s]", this, uriHost.get()));
}
rv = CheckRedirectLimit(newURI, redirectFlags);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// pass on the early hint observer to be able to process `103 Early Hints` // pass on the early hint observer to be able to process `103 Early Hints`

View file

@ -1693,7 +1693,7 @@ function handleRequest(req, res) {
} else if (u.pathname === "/redirect_to_http") { } else if (u.pathname === "/redirect_to_http") {
res.setHeader( res.setHeader(
"Location", "Location",
`http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http` `http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http?port=${u.query.port}`
); );
res.writeHead(307); res.writeHead(307);
res.end(""); res.end("");