forked from mirrors/gecko-dev
Bug 1804684 - Fragment navigation may change document URI scheme from https to http. r=ckerschb,nika,smaug
Differential Revision: https://phabricator.services.mozilla.com/D165282
This commit is contained in:
parent
06ac399c37
commit
c86335b0e3
10 changed files with 129 additions and 31 deletions
|
|
@ -8696,12 +8696,22 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
|||
// fact that the new URI is currently http), then set mSameExceptHashes to
|
||||
// true and only perform a fragment navigation.
|
||||
if (!aState.mSameExceptHashes) {
|
||||
nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel();
|
||||
if (docChannel) {
|
||||
if (nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel()) {
|
||||
nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo();
|
||||
if (!docLoadInfo->GetLoadErrorPage()) {
|
||||
if (nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(
|
||||
currentExposableURI, aLoadState->URI(), docLoadInfo)) {
|
||||
if (!docLoadInfo->GetLoadErrorPage() &&
|
||||
nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(
|
||||
currentExposableURI, aLoadState->URI(), docLoadInfo)) {
|
||||
uint32_t status = docLoadInfo->GetHttpsOnlyStatus();
|
||||
if (status & (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
|
||||
nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
|
||||
// At this point the requested URI is for sure a fragment
|
||||
// navigation via HTTP and HTTPS-Only mode or HTTPS-First is
|
||||
// enabled. Also it is not interfering the upgrade order of
|
||||
// https://searchfox.org/mozilla-central/source/netwerk/base/nsNetUtil.cpp#2948-2953.
|
||||
// Since we are on an HTTPS site the fragment
|
||||
// navigation should also be an HTTPS.
|
||||
// For that reason we should upgrade the URI to HTTPS.
|
||||
aState.mSecureUpgradeURI = true;
|
||||
aState.mSameExceptHashes = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -8788,6 +8798,22 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
|
||||
nsCOMPtr<nsIURI> currentURI = mCurrentURI;
|
||||
|
||||
// We need to upgrade the new URI from http: to https:
|
||||
nsCOMPtr<nsIURI> newURI = aLoadState->URI();
|
||||
if (aState.mSecureUpgradeURI) {
|
||||
MOZ_TRY(NS_GetSecureUpgradedURI(aLoadState->URI(), getter_AddRefs(newURI)));
|
||||
MOZ_LOG(gSHLog, LogLevel::Debug,
|
||||
("Upgraded URI to %s", newURI->GetSpecOrDefault().get()));
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (aState.mSameExceptHashes) {
|
||||
bool sameExceptHashes = false;
|
||||
currentURI->EqualsExceptRef(newURI, &sameExceptHashes);
|
||||
MOZ_ASSERT(sameExceptHashes);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Save the position of the scrollers.
|
||||
nsPoint scrollPos = GetCurScrollPos();
|
||||
|
||||
|
|
@ -8824,7 +8850,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
}
|
||||
|
||||
// Set the doc's URI according to the new history entry's URI.
|
||||
doc->SetDocumentURI(aLoadState->URI());
|
||||
doc->SetDocumentURI(newURI);
|
||||
|
||||
/* This is a anchor traversal within the same page.
|
||||
* call OnNewURI() so that, this traversal will be
|
||||
|
|
@ -8854,8 +8880,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
newCsp = doc->GetCsp();
|
||||
}
|
||||
|
||||
uint32_t locationChangeFlags =
|
||||
GetSameDocumentNavigationFlags(aLoadState->URI());
|
||||
uint32_t locationChangeFlags = GetSameDocumentNavigationFlags(newURI);
|
||||
|
||||
// Pass true for aCloneSHChildren, since we're not
|
||||
// changing documents here, so all of our subframes are
|
||||
|
|
@ -8869,10 +8894,9 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
// Note: we'll actually fire onLocationChange later, in order to preserve
|
||||
// ordering of HistoryCommit() in the parent vs onLocationChange (bug
|
||||
// 1668126)
|
||||
bool locationChangeNeeded =
|
||||
OnNewURI(aLoadState->URI(), nullptr, newURITriggeringPrincipal,
|
||||
newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit,
|
||||
newCsp, false, true, true);
|
||||
bool locationChangeNeeded = OnNewURI(
|
||||
newURI, nullptr, newURITriggeringPrincipal, newURIPrincipalToInherit,
|
||||
newURIPartitionedPrincipalToInherit, newCsp, false, true, true);
|
||||
|
||||
nsCOMPtr<nsIInputStream> postData;
|
||||
uint32_t cacheKey = 0;
|
||||
|
|
@ -9039,15 +9063,13 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
|
||||
MOZ_LOG(gSHLog, LogLevel::Debug,
|
||||
("Creating an active entry on nsDocShell %p to %s", this,
|
||||
aLoadState->URI()->GetSpecOrDefault().get()));
|
||||
newURI->GetSpecOrDefault().get()));
|
||||
if (mActiveEntry) {
|
||||
mActiveEntry =
|
||||
MakeUnique<SessionHistoryInfo>(*mActiveEntry, aLoadState->URI());
|
||||
mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, newURI);
|
||||
} else {
|
||||
mActiveEntry = MakeUnique<SessionHistoryInfo>(
|
||||
aLoadState->URI(), newURITriggeringPrincipal,
|
||||
newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit,
|
||||
newCsp, mContentTypeHint);
|
||||
newURI, newURITriggeringPrincipal, newURIPrincipalToInherit,
|
||||
newURIPartitionedPrincipalToInherit, newCsp, mContentTypeHint);
|
||||
}
|
||||
|
||||
// Save the postData obtained from the previous page in to the session
|
||||
|
|
@ -9086,7 +9108,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
}
|
||||
|
||||
if (locationChangeNeeded) {
|
||||
FireOnLocationChange(this, nullptr, aLoadState->URI(), locationChangeFlags);
|
||||
FireOnLocationChange(this, nullptr, newURI, locationChangeFlags);
|
||||
}
|
||||
|
||||
/* Restore the original LSHE if we were loading something
|
||||
|
|
@ -9097,13 +9119,13 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
|
||||
/* Set the title for the Global History entry for this anchor url.
|
||||
*/
|
||||
UpdateGlobalHistoryTitle(aLoadState->URI());
|
||||
UpdateGlobalHistoryTitle(newURI);
|
||||
|
||||
SetDocCurrentStateObj(mOSHE, mActiveEntry.get());
|
||||
|
||||
// Inform the favicon service that the favicon for oldURI also
|
||||
// applies to aLoadState->URI().
|
||||
CopyFavicon(currentURI, aLoadState->URI(), UsePrivateBrowsing());
|
||||
// applies to newURI.
|
||||
CopyFavicon(currentURI, newURI, UsePrivateBrowsing());
|
||||
|
||||
RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal;
|
||||
RefPtr<nsGlobalWindowInner> win =
|
||||
|
|
@ -9158,7 +9180,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
|
|||
if (doHashchange) {
|
||||
// Note that currentURI hasn't changed because it's on the
|
||||
// stack, so we can just use it directly as the old URI.
|
||||
win->DispatchAsyncHashchange(currentURI, aLoadState->URI());
|
||||
win->DispatchAsyncHashchange(currentURI, newURI);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1057,11 +1057,15 @@ class nsDocShell final : public nsDocLoader,
|
|||
bool mCurrentURIHasRef = false;
|
||||
bool mNewURIHasRef = false;
|
||||
bool mSameExceptHashes = false;
|
||||
bool mSecureUpgradeURI = false;
|
||||
bool mHistoryNavBetweenSameDoc = false;
|
||||
};
|
||||
|
||||
// Check to see if we're loading a prior history entry or doing a fragment
|
||||
// navigation in the same document.
|
||||
// NOTE: In case we are doing a fragment navigation, and HTTPS-Only/ -First
|
||||
// mode is enabled and upgraded the underlying document, we update the URI of
|
||||
// aLoadState from HTTP to HTTPS (if neccessary).
|
||||
bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
||||
SameDocumentNavigationState& aState);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ window.onload = function (){
|
|||
window.onscroll = function(){
|
||||
window.opener.postMessage({
|
||||
info: "scrolled-to-foo",
|
||||
result: window.location.hash,
|
||||
result: window.location.href,
|
||||
button: true,
|
||||
documentURI: document.documentURI,
|
||||
}, "*");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ async function receiveMessage(event) {
|
|||
|
||||
// Once the button was clicked we know the tast has finished
|
||||
ok(data.button, "button is clicked");
|
||||
ok(data.result == "#foo", "location (hash) is correct");
|
||||
is(data.result, EXPECT_URL + "#foo", "location (hash) is correct");
|
||||
ok(data.info == "scrolled-to-foo","Scrolled successfully without reloading!");
|
||||
is(data.documentURI, EXPECT_URL + "#foo", "Document URI is correct");
|
||||
window.removeEventListener("message",receiveMessage);
|
||||
winTest.close();
|
||||
SimpleTest.finish();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ support-files =
|
|||
[browser_hsts_host.js]
|
||||
support-files =
|
||||
hsts_headers.sjs
|
||||
file_fragment_noscript.html
|
||||
[browser_httpsonly_speculative_connect.js]
|
||||
support-files = file_httpsonly_speculative_connect.html
|
||||
[browser_websocket_exceptions.js]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ add_task(async function() {
|
|||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.security.https_only_mode", true]],
|
||||
});
|
||||
|
||||
Services.console.registerListener(onNewMessage);
|
||||
const RESOURCE_LINK =
|
||||
getRootDirectory(gTestPath).replace(
|
||||
|
|
@ -47,6 +48,64 @@ add_task(async function() {
|
|||
|
||||
// Clean up
|
||||
Services.console.unregisterListener(onNewMessage);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
// Test that when clicking on #fragment with a different scheme (http vs https)
|
||||
// DOES cause an actual navigation with HSTS, even though https-only mode is
|
||||
// enabled.
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.security.https_only_mode", true],
|
||||
[
|
||||
"dom.security.https_only_mode_break_upgrade_downgrade_endless_loop",
|
||||
false,
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
const TEST_PAGE =
|
||||
"http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html";
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: TEST_PAGE,
|
||||
waitForLoad: true,
|
||||
},
|
||||
async function(browser) {
|
||||
const UPGRADED_URL = TEST_PAGE.replace("http:", "https:");
|
||||
|
||||
await SpecialPowers.spawn(browser, [UPGRADED_URL], async function(url) {
|
||||
is(content.window.location.href, url);
|
||||
|
||||
content.window.addEventListener("scroll", () => {
|
||||
ok(false, "scroll event should not trigger");
|
||||
});
|
||||
|
||||
let beforeUnload = new Promise(resolve => {
|
||||
content.window.addEventListener("beforeunload", resolve, {
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
|
||||
content.window.document.querySelector("#clickMeButton").click();
|
||||
|
||||
// Wait for unload event.
|
||||
await beforeUnload;
|
||||
});
|
||||
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
await SpecialPowers.spawn(browser, [UPGRADED_URL], async function(url) {
|
||||
is(content.window.location.href, url + "#foo");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@ window.onload = function (){
|
|||
}, "*");
|
||||
// click button
|
||||
button.click();
|
||||
|
||||
}
|
||||
// after button clicked and paged scrolled sends URL of current window
|
||||
window.onscroll = function(){
|
||||
window.opener.postMessage({
|
||||
info: "scrolled-to-foo",
|
||||
result: window.location.hash,
|
||||
result: window.location.href,
|
||||
button: true,
|
||||
documentURI: document.documentURI,
|
||||
}, "*");
|
||||
}
|
||||
|
||||
|
|
|
|||
9
dom/security/test/https-only/file_fragment_noscript.html
Normal file
9
dom/security/test/https-only/file_fragment_noscript.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<a id="clickMeButton" href="http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html#foo">Click me</a>
|
||||
<div style="height: 1000px; border: 1px solid black;"> space</div>
|
||||
<a name="foo" href="http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html">foo</a>
|
||||
<div style="height: 1000px; border: 1px solid black;">space</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const REQUEST_URL = "http://example.com/tests/dom/security/test/https-only/file_fragment.html";
|
||||
const EXPECT_URL = REQUEST_URL.replace("http://", "https://");
|
||||
let winTest = null;
|
||||
let checkButtonClicked = false;
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ async function receiveMessage(event) {
|
|||
// checks if click was successful
|
||||
if (!checkButtonClicked){
|
||||
// expected window location ( expected URL)
|
||||
ok(data.result == "https://example.com/tests/dom/security/test/https-only/file_fragment.html", "location is correct");
|
||||
ok(data.result == EXPECT_URL, "location is correct");
|
||||
ok(data.button, "button is clicked");
|
||||
// checks if loading was successful
|
||||
ok(data.info == "onload", "Onloading worked");
|
||||
|
|
@ -46,9 +47,10 @@ async function receiveMessage(event) {
|
|||
// if Button was clicked once -> test finished
|
||||
// check if hash exist and if hash of location is correct
|
||||
ok(data.button, "button is clicked");
|
||||
ok(data.result == "#foo", "location (hash) is correct");
|
||||
ok(data.result == EXPECT_URL + "#foo", "location (hash) is correct");
|
||||
// check that page is scrolled not reloaded
|
||||
ok(data.info == "scrolled-to-foo","Scrolled successfully without reloading!");
|
||||
is(data.documentURI, EXPECT_URL + "#foo", "Document URI is correct");
|
||||
// complete test and close window
|
||||
window.removeEventListener("message",receiveMessage);
|
||||
winTest.close();
|
||||
|
|
|
|||
|
|
@ -495,8 +495,7 @@ interface nsILoadInfo : nsISupports
|
|||
const unsigned long HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE = (1 << 6);
|
||||
|
||||
/**
|
||||
* This flag indicates that the request should not be logged to the
|
||||
* console.
|
||||
* This flag indicates that the request was upgraded by https-first mode.
|
||||
*/
|
||||
const unsigned long HTTPS_ONLY_UPGRADED_HTTPS_FIRST = (1 << 7);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue