import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1174323 - Disable screenClientXYConst subtest of pointerlock test on OS X. rs=KWierso (2d0db6d1b)
- Bug 992096 - Implement Sub Resource Integrity [1/2]. r=baku,r=ckerschb (c30671ac0)
- Bug 992096 - Implement Sub Resource Integrity [2/2]. r=ckerschb (0afc64d88)
- Bug 1091883 - Added test, this is fixed by a fix to bug 1113438. r=sstamm CLOSED TREE (fd9a64b43)
- Bug 1196740 - Consider redirects when looking for SRI-eligibility. r=ckerschb (5c749cdc9)
- Bug 1202015 - Better document the SRI strings for translators. r=ckerschb (a7860e0fb)
- Bug 1202027 - Make SRI require CORS loads for cross-origin resources. r=ckerschb (ea451323d)
- bit of Bug 1202902 - Mass replace toplevel 'let' with 'var' (a6e8a587d)
- Bug 1208629 - Properly support data: and blob: URIs with an integrity atribute. r=ckerschb (6b2018fe4)
- Bug 1140129 - Don't clear tab title when location changes (r=Mossop) (ca1945ba8)
- Bug 1073462: Send synthetic property with Content:LocationChange message. r=felipe (1aa418acf)
- bug 1165017 - annotate content process URL on location change. r=mconley (cdca4fa75)
- Bug 1157561 - Add webRequest-like API to Firefox (r=Mossop) (546a57822)
- Bug 1163861 - Include windowID in all WebRequest notifications (r=Mossop) (c140af560)
- Bug 1171248 - Add MatchPattern support to WebRequest module (r=Mossop) (b09a05658)
This commit is contained in:
2021-08-17 10:04:53 +08:00
parent 36ba256882
commit ebd6e6dc19
99 changed files with 3558 additions and 67 deletions
+1
View File
@@ -364,6 +364,7 @@ Florian Scholz <elchi3@elchi3.de>
<flying@dom.natm.ru>
France Telecom Research and Development
Franck
Francois Marier <francois@fmarier.org>
Frank Tang <ftang@netscape.com>
Frank Yan <fyan@mozilla.com>
Franky Braem
+1
View File
@@ -429,6 +429,7 @@ LOCAL_INCLUDES += [
'/layout/svg',
'/layout/xul',
'/netwerk/base',
'/security/manager/ssl',
'/widget',
'/xpcom/ds',
]
+22 -1
View File
@@ -51,6 +51,16 @@
#include "nsParserConstants.h"
#include "nsSandboxFlags.h"
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
using namespace mozilla;
PRLogModuleInfo* gContentSinkLogModuleInfo;
@@ -755,12 +765,23 @@ nsContentSink::ProcessStyleLink(nsIContent* aElement,
aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
"We only expect processing instructions here");
nsAutoString integrity;
if (aElement) {
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
}
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsContentSink::ProcessStyleLink, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
}
// If this is a fragment parser, we don't want to observe.
// We don't support CORS for processing instructions
bool isAlternate;
rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
CORS_NONE, mDocument->GetReferrerPolicy(),
mRunsToCompletion ? nullptr : this, &isAlternate);
integrity, mRunsToCompletion ? nullptr : this,
&isAlternate);
NS_ENSURE_SUCCESS(rv, rv);
if (!isAlternate && !mRunsToCompletion) {
+3 -2
View File
@@ -9912,7 +9912,8 @@ NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
void
nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
const nsAString& aCrossOriginAttr,
const ReferrerPolicy aReferrerPolicy)
const ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
// The CSSLoader will retain this object after we return.
nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
@@ -9922,7 +9923,7 @@ nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
NS_LossyConvertUTF16toASCII(charset),
obs,
Element::StringToCORSMode(aCrossOriginAttr),
aReferrerPolicy);
aReferrerPolicy, aIntegrity);
}
nsresult
+2 -1
View File
@@ -1126,7 +1126,8 @@ public:
virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
const nsAString& aCrossOriginAttr,
ReferrerPolicy aReferrerPolicy) override;
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity) override;
virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
mozilla::CSSStyleSheet** sheet) override;
+1
View File
@@ -488,6 +488,7 @@ GK_ATOM(instanceOf, "instanceOf")
GK_ATOM(int32, "int32")
GK_ATOM(int64, "int64")
GK_ATOM(integer, "integer")
GK_ATOM(integrity, "integrity")
GK_ATOM(intersection, "intersection")
GK_ATOM(is, "is")
GK_ATOM(iscontainer, "iscontainer")
+4 -3
View File
@@ -158,8 +158,8 @@ struct FullScreenOptions {
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0x21bbd52a, 0xc2d2, 0x4b2f, \
{ 0xbc, 0x6c, 0xc9, 0x52, 0xbe, 0x23, 0x6b, 0x19 } }
{ 0x6d18ec0b, 0x1f68, 0x4ae6, \
{ 0x8b, 0x3d, 0x8d, 0x7d, 0x8b, 0x8e, 0x28, 0xd4 } }
// Enum for requesting a particular type of document when creating a doc
enum DocumentFlavor {
@@ -2065,7 +2065,8 @@ public:
*/
virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy) = 0;
ReferrerPolicyEnum aReferrerPolicy,
const nsAString& aIntegrity) = 0;
/**
* Called by the chrome registry to load style sheets. Can be put
+49 -5
View File
@@ -53,9 +53,21 @@
#include "mozilla/Attributes.h"
#include "mozilla/unused.h"
#include "mozilla/dom/SRICheck.h"
#include "nsIScriptError.h"
static PRLogModuleInfo* gCspPRLog;
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
using namespace mozilla;
using namespace mozilla::dom;
@@ -626,7 +638,22 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
if (!request) {
// no usable preload
request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
SRIMetadata sriMetadata;
{
nsAutoString integrity;
scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
integrity);
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsScriptLoader::ProcessScriptElement, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
}
}
request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
sriMetadata);
request->mURI = scriptURI;
request->mIsInline = false;
request->mLoading = true;
@@ -740,7 +767,8 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
}
// Inline scripts ignore ther CORS mode and are always CORS_NONE
request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
request = new nsScriptLoadRequest(aElement, version, CORS_NONE,
SRIMetadata()); // SRI doesn't apply
request->mJSVersion = version;
request->mLoading = false;
request->mIsInline = true;
@@ -1436,8 +1464,14 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
NS_ASSERTION(request, "null request in stream complete handler");
NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
aString);
nsresult rv = NS_ERROR_SRI_CORRUPT;
if (request->mIntegrity.IsEmpty() ||
NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity, aLoader,
request->mCORSMode, aStringLen,
aString, mDocument))) {
rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
}
if (NS_FAILED(rv)) {
/*
* Handle script not loading error because source was a tracking URL.
@@ -1634,6 +1668,7 @@ void
nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
const nsAString &aType,
const nsAString &aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
const mozilla::net::ReferrerPolicy aReferrerPolicy)
{
@@ -1642,9 +1677,18 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
return;
}
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsScriptLoader::PreloadURI, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
}
nsRefPtr<nsScriptLoadRequest> request =
new nsScriptLoadRequest(nullptr, 0,
Element::StringToCORSMode(aCrossOrigin));
Element::StringToCORSMode(aCrossOrigin),
sriMetadata);
request->mURI = aURI;
request->mIsInline = false;
request->mLoading = true;
+7 -1
View File
@@ -19,6 +19,7 @@
#include "nsIDocument.h"
#include "nsIStreamLoader.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/LinkedList.h"
#include "mozilla/net/ReferrerPolicy.h"
@@ -56,7 +57,8 @@ class nsScriptLoadRequest final : public nsISupports,
public:
nsScriptLoadRequest(nsIScriptElement* aElement,
uint32_t aVersion,
mozilla::CORSMode aCORSMode)
mozilla::CORSMode aCORSMode,
const mozilla::dom::SRIMetadata &aIntegrity)
: mElement(aElement),
mLoading(true),
mIsInline(true),
@@ -72,6 +74,7 @@ public:
mJSVersion(aVersion),
mLineNo(1),
mCORSMode(aCORSMode),
mIntegrity(aIntegrity),
mReferrerPolicy(mozilla::net::RP_Default)
{
}
@@ -129,6 +132,7 @@ public:
nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
int32_t mLineNo;
const mozilla::CORSMode mCORSMode;
const mozilla::dom::SRIMetadata mIntegrity;
mozilla::net::ReferrerPolicy mReferrerPolicy;
};
@@ -374,11 +378,13 @@ public:
* @param aType The type parameter for the script.
* @param aCrossOrigin The crossorigin attribute for the script.
* Void if not present.
* @param aIntegrity The expect hash url, if avail, of the request
* @param aScriptFromHead Whether or not the script was a child of head
*/
virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
const nsAString &aType,
const nsAString &aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
const mozilla::net::ReferrerPolicy aReferrerPolicy);
+9 -1
View File
@@ -421,13 +421,21 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
scopeElement, aObserver, &doneLoading, &isAlternate);
}
else {
nsAutoString integrity;
thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
}
// XXXbz clone the URI here to work around content policies modifying URIs.
nsCOMPtr<nsIURI> clonedURI;
uri->Clone(getter_AddRefs(clonedURI));
NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
rv = doc->CSSLoader()->
LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
GetCORSMode(), doc->GetReferrerPolicy(),
GetCORSMode(), doc->GetReferrerPolicy(), integrity,
aObserver, &isAlternate);
if (NS_FAILED(rv)) {
// Don't propagate LoadStyleLink() errors further than this, since some
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<body>
<h2>Frame I am.</h2>
<script>
var subframeOrigin = location.hash.substr(1); // e.g., "http://example.com"
var subframe = document.createElement("iframe");
subframe.src = subframeOrigin +
"/tests/dom/base/test/file_bug1091883_subframe.html";
document.body.appendChild(subframe);
</script>
</body>
</html>
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<h3>Subframe I am.</h3>
</body>
</html>
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<h3>Target I am.</h3>
<script>
var testRun = location.hash.substr(1);
parent.parent.postMessage(document.referrer + " " + testRun,
"http://mochi.test:8888");
</script>
</head>
<body>
</body>
</html>
+4
View File
@@ -92,6 +92,9 @@ support-files =
file_XHR_system_redirect.html^headers^
file_XHR_timeout.sjs
file_base_xbl.xml
file_bug1091883_frame.html
file_bug1091883_subframe.html
file_bug1091883_target.html
file_bug28293.sjs
file_bug326337.xml
file_bug326337_inner.html
@@ -247,6 +250,7 @@ support-files =
[test_audioWindowUtils.html]
[test_audioNotification.html]
skip-if = buildapp == 'mulet'
[test_bug1091883.html]
[test_bug116083.html]
[test_bug793311.html]
[test_bug913761.html]
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1091883
-->
<head>
<meta charset="utf-8">
<meta name="referrer" content="origin-when-crossorigin">
<title>Test for Bug 1091883</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1091883">Mozilla Bug 1091883</a></p>
<h2>Results</h2>
<pre id="results">Running...</pre>
<script>
SimpleTest.waitForExplicitFinish();
var origins = [
"http://mochi.test:8888", "http://example.com", "http://example.org"];
var numOrigins = origins.length;
// For each combination of (frame, subframe, target) origins, this test
// includes a "frame" that includes a "subframe"; and then this test
// navigates this "subframe" to the "target". Both the referrer and
// the triggering principal are this test, i.e., "http://mochi.test:8888".
// Since the referrer policy is origin-when-crossorigin, we expect to have
// a full referrer if and only if the target is also "http://mochi.test:8888";
// in all other cases, the referrer needs to be the origin alone.
var numTests = numOrigins * numOrigins * numOrigins;
// Helpers to look up the approriate origins for a given test number.
function getFrameOrigin(i) {
return origins[(i / (numOrigins * numOrigins)) | 0];
}
function getSubframeOrigin(i) {
return origins[((i / numOrigins) | 0) % 3];
}
function getTargetOrigin(i) {
return origins[i % 3];
}
// Create the frames, and tell them which subframes to load.
for (var i = 0; i < numTests; i++) {
var frame = document.createElement("iframe");
frame.src = getFrameOrigin(i) +
"/tests/dom/base/test/file_bug1091883_frame.html#" +
getSubframeOrigin(i);
document.body.appendChild(frame);
}
// Navigate all subframes to the target.
window.onload = function() {
for (var i = 0; i < numTests; i++) {
frames[i].frames[0].location = getTargetOrigin(i) +
"/tests/dom/base/test/file_bug1091883_target.html#" + i;
}
};
// Check referrer messages from the target.
var results = {};
function makeResultsKey(i) {
return i + ": " + getFrameOrigin(i) + " | " + getSubframeOrigin(i) + " -> " +
getTargetOrigin(i);
}
window.addEventListener("message", function(event) {
var out = event.data.split(" ");
var referrer = out[0];
var testRun = +out[1];
results[makeResultsKey(testRun)] = referrer;
if (event.origin == "http://mochi.test:8888") {
is(referrer,
"http://mochi.test:8888/tests/dom/base/test/test_bug1091883.html",
"must be full referrer");
} else {
is(referrer, "http://mochi.test:8888", "must be origin referrer");
}
if (Object.keys(results).length == numTests) {
document.getElementById("results").textContent =
JSON.stringify(results, null, 4);
SimpleTest.finish();
}
});
</script>
</body>
</html>
+5
View File
@@ -214,6 +214,11 @@ HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
aResult.ParseAtomArray(aValue);
return true;
}
if (aAttribute == nsGkAtoms::integrity) {
aResult.ParseStringOrAtom(aValue);
return true;
}
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+8
View File
@@ -136,6 +136,14 @@ public:
{
SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
}
void GetIntegrity(nsAString& aIntegrity) const
{
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
}
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
{
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
}
already_AddRefed<nsIDocument> GetImport();
already_AddRefed<ImportLoader> GetImportLoader()
+10 -4
View File
@@ -75,10 +75,16 @@ HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None &&
aAttribute == nsGkAtoms::crossorigin) {
ParseCORSValue(aValue, aResult);
return true;
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::crossorigin) {
ParseCORSValue(aValue, aResult);
return true;
}
if (aAttribute == nsGkAtoms::integrity) {
aResult.ParseStringOrAtom(aValue);
return true;
}
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+8
View File
@@ -79,6 +79,14 @@ public:
{
SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
}
void GetIntegrity(nsAString& aIntegrity)
{
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
}
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
{
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
}
bool Async();
void SetAsync(bool aValue, ErrorResult& rv);
@@ -32,6 +32,22 @@ LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a
# LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
# Sub-Resource Integrity
# LOCALIZATION NOTE: Do not translate "script" or "integrity". "%1$S" is the invalid token found in the attribute.
MalformedIntegrityHash=The script element has a malformed hash in its integrity attribute: "%1$S". The correct format is "<hash algorithm>-<hash value>".
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
# LOCALIZATION NOTE: Do not translate "integrity". "%1$S" is the type of hash algorigthm in use (e.g. "sha256").
IntegrityMismatch=None of the "%1$S" hashes in the integrity attribute match the content of the subresource.
# LOCALIZATION NOTE: "%1$S" is the URI of the sub-resource that cannot be protected using SRI.
IneligibleResource="%1$S" is not eligible for integrity checks since it's neither CORS-enabled nor same-origin.
# LOCALIZATION NOTE: Do not translate "integrity". "%1$S" is the invalid hash algorithm found in the attribute.
UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: "%1$S"
# LOCALIZATION NOTE: Do not translate "integrity"
NoValidMetadata=The integrity attribute does not contain any valid metadata.
# LOCALIZATION NOTE: Do not translate "SSL 3.0".
WeakProtocolVersionWarning=This site uses the protocol SSL 3.0 for encryption, which is deprecated and insecure.
# LOCALIZATION NOTE: Do not translate "RC4".
+349
View File
@@ -0,0 +1,349 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SRICheck.h"
#include "mozilla/Base64.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsICryptoHash.h"
#include "nsIDocument.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIStreamLoader.h"
#include "nsIUnicharStreamLoader.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsWhitespaceTokenizer.h"
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
namespace mozilla {
namespace dom {
/**
* Returns whether or not the sub-resource about to be loaded is eligible
* for integrity checks. If it's not, the checks will be skipped and the
* sub-resource will be loaded.
*/
static nsresult
IsEligible(nsIChannel* aChannel, const CORSMode aCORSMode,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aDocument);
if (!aChannel) {
SRILOG(("SRICheck::IsEligible, null channel"));
return NS_ERROR_SRI_NOT_ELIGIBLE;
}
// Was the sub-resource loaded via CORS?
if (aCORSMode != CORS_NONE) {
SRILOG(("SRICheck::IsEligible, CORS mode"));
return NS_OK;
}
nsCOMPtr<nsIURI> finalURI;
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> originalURI;
rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString requestSpec;
rv = originalURI->GetSpec(requestSpec);
NS_ENSURE_SUCCESS(rv, rv);
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
nsAutoCString documentSpec, finalSpec;
aDocument->GetDocumentURI()->GetAsciiSpec(documentSpec);
if (finalURI) {
finalURI->GetSpec(finalSpec);
}
SRILOG(("SRICheck::IsEligible, documentURI=%s; requestURI=%s; finalURI=%s",
documentSpec.get(), requestSpec.get(), finalSpec.get()));
}
// Is the sub-resource same-origin?
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(),
finalURI, false))) {
SRILOG(("SRICheck::IsEligible, same-origin"));
return NS_OK;
}
SRILOG(("SRICheck::IsEligible, NOT same origin"));
NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
const char16_t* params[] = { requestSpecUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"IneligibleResource",
params, ArrayLength(params));
return NS_ERROR_SRI_NOT_ELIGIBLE;
}
/**
* Compute the hash of a sub-resource and compare it with the expected
* value.
*/
static nsresult
VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
uint32_t aStringLen, const uint8_t* aString,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aString);
NS_ENSURE_ARG_POINTER(aDocument);
nsAutoCString base64Hash;
aMetadata.GetHash(aHashIndex, &base64Hash);
SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
nsAutoCString binaryHash;
if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"InvalidIntegrityBase64");
return NS_ERROR_SRI_CORRUPT;
}
uint32_t hashLength;
int8_t hashType;
aMetadata.GetHashType(&hashType, &hashLength);
if (binaryHash.Length() != hashLength) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"InvalidIntegrityLength");
return NS_ERROR_SRI_CORRUPT;
}
nsresult rv;
nsCOMPtr<nsICryptoHash> cryptoHash =
do_CreateInstance("@mozilla.org/security/hash;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Init(hashType);
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Update(aString, aStringLen);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString computedHash;
rv = cryptoHash->Finish(false, computedHash);
NS_ENSURE_SUCCESS(rv, rv);
if (!binaryHash.Equals(computedHash)) {
SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
return NS_ERROR_SRI_CORRUPT;
}
SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
return NS_OK;
}
/* static */ nsresult
SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
const nsIDocument* aDocument,
SRIMetadata* outMetadata)
{
NS_ENSURE_ARG_POINTER(outMetadata);
NS_ENSURE_ARG_POINTER(aDocument);
MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
if (!Preferences::GetBool("security.sri.enable", false)) {
SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
return NS_ERROR_SRI_DISABLED;
}
// put a reasonable bound on the length of the metadata
NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
}
MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
// the integrity attribute is a list of whitespace-separated hashes
// and options so we need to look at them one by one and pick the
// strongest (valid) one
nsCWhitespaceTokenizer tokenizer(metadataList);
nsAutoCString token;
for (uint32_t i=0; tokenizer.hasMoreTokens() &&
i < SRICheck::MAX_METADATA_TOKENS; ++i) {
token = tokenizer.nextToken();
SRIMetadata metadata(token);
if (metadata.IsMalformed()) {
NS_ConvertUTF8toUTF16 tokenUTF16(token);
const char16_t* params[] = { tokenUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"MalformedIntegrityHash",
params, ArrayLength(params));
} else if (!metadata.IsAlgorithmSupported()) {
nsAutoCString alg;
metadata.GetAlgorithm(&alg);
NS_ConvertUTF8toUTF16 algUTF16(alg);
const char16_t* params[] = { algUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"UnsupportedHashAlg",
params, ArrayLength(params));
}
nsAutoCString alg1, alg2;
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
outMetadata->GetAlgorithm(&alg1);
metadata.GetAlgorithm(&alg2);
}
if (*outMetadata == metadata) {
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
alg1.get(), alg2.get()));
*outMetadata += metadata; // add new hash to strongest metadata
} else if (*outMetadata < metadata) {
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
alg1.get(), alg2.get()));
*outMetadata = metadata; // replace strongest metadata with current
}
}
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
if (outMetadata->IsValid()) {
nsAutoCString alg;
outMetadata->GetAlgorithm(&alg);
SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
} else if (outMetadata->IsEmpty()) {
SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
} else {
SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
}
}
return NS_OK;
}
static nsresult
VerifyIntegrityInternal(const SRIMetadata& aMetadata,
nsIChannel* aChannel,
const CORSMode aCORSMode,
uint32_t aStringLen,
const uint8_t* aString,
const nsIDocument* aDocument)
{
MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
// IntegrityMetadata() checks this and returns "no metadata" if
// it's disabled so we should never make it this far
MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
if (NS_FAILED(IsEligible(aChannel, aCORSMode, aDocument))) {
return NS_ERROR_SRI_NOT_ELIGIBLE;
}
if (!aMetadata.IsValid()) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"NoValidMetadata");
return NS_OK; // ignore invalid metadata for forward-compatibility
}
for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
aString, aDocument))) {
return NS_OK; // stop at the first valid hash
}
}
nsAutoCString alg;
aMetadata.GetAlgorithm(&alg);
NS_ConvertUTF8toUTF16 algUTF16(alg);
const char16_t* params[] = { algUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"IntegrityMismatch",
params, ArrayLength(params));
return NS_ERROR_SRI_CORRUPT;
}
/* static */ nsresult
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
nsIUnicharStreamLoader* aLoader,
const CORSMode aCORSMode,
const nsAString& aString,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aLoader);
NS_ConvertUTF16toUTF8 utf8Hash(aString);
nsCOMPtr<nsIChannel> channel;
aLoader->GetChannel(getter_AddRefs(channel));
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
nsAutoCString requestURL;
nsCOMPtr<nsIURI> originalURI;
if (channel &&
NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
originalURI) {
originalURI->GetAsciiSpec(requestURL);
}
SRILOG(("SRICheck::VerifyIntegrity (unichar stream), url=%s (length=%u)",
requestURL.get(), utf8Hash.Length()));
}
return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
utf8Hash.Length(), (uint8_t*)utf8Hash.get(),
aDocument);
}
/* static */ nsresult
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
nsIStreamLoader* aLoader,
const CORSMode aCORSMode,
uint32_t aStringLen,
const uint8_t* aString,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aLoader);
nsCOMPtr<nsIRequest> request;
aLoader->GetRequest(getter_AddRefs(request));
NS_ENSURE_ARG_POINTER(request);
nsCOMPtr<nsIChannel> channel;
channel = do_QueryInterface(request);
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
nsAutoCString requestURL;
request->GetName(requestURL);
SRILOG(("SRICheck::VerifyIntegrity (stream), url=%s (length=%u)",
requestURL.get(), aStringLen));
}
return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
aStringLen, aString, aDocument);
}
} // namespace dom
} // namespace mozilla
+60
View File
@@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SRICheck_h
#define mozilla_dom_SRICheck_h
#include "mozilla/CORSMode.h"
#include "nsCOMPtr.h"
#include "SRIMetadata.h"
class nsIDocument;
class nsIStreamLoader;
class nsIUnicharStreamLoader;
namespace mozilla {
namespace dom {
class SRICheck final
{
public:
static const uint32_t MAX_METADATA_LENGTH = 24*1024;
static const uint32_t MAX_METADATA_TOKENS = 512;
/**
* Parse the multiple hashes specified in the integrity attribute and
* return the strongest supported hash.
*/
static nsresult IntegrityMetadata(const nsAString& aMetadataList,
const nsIDocument* aDocument,
SRIMetadata* outMetadata);
/**
* Process the integrity attribute of the element. A result of false
* must prevent the resource from loading.
*/
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
nsIUnicharStreamLoader* aLoader,
const CORSMode aCORSMode,
const nsAString& aString,
const nsIDocument* aDocument);
/**
* Process the integrity attribute of the element. A result of false
* must prevent the resource from loading.
*/
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
nsIStreamLoader* aLoader,
const CORSMode aCORSMode,
uint32_t aStringLen,
const uint8_t* aString,
const nsIDocument* aDocument);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SRICheck_h
+172
View File
@@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SRIMetadata.h"
#include "hasht.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/Logging.h"
#include "nsICryptoHash.h"
static PRLogModuleInfo*
GetSriMetadataLog()
{
static PRLogModuleInfo *gSriMetadataPRLog;
if (!gSriMetadataPRLog) {
gSriMetadataPRLog = PR_NewLogModule("SRIMetadata");
}
return gSriMetadataPRLog;
}
#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
namespace mozilla {
namespace dom {
SRIMetadata::SRIMetadata(const nsACString& aToken)
: mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
{
MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
PromiseFlatCString(aToken).get()));
int32_t hyphen = aToken.FindChar('-');
if (hyphen == -1) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
return; // invalid metadata
}
// split the token into its components
mAlgorithm = Substring(aToken, 0, hyphen);
uint32_t hashStart = hyphen + 1;
if (hashStart >= aToken.Length()) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
return; // invalid metadata
}
int32_t question = aToken.FindChar('?');
if (question == -1) {
mHashes.AppendElement(Substring(aToken, hashStart,
aToken.Length() - hashStart));
} else {
MOZ_ASSERT(question > 0);
if (static_cast<uint32_t>(question) <= hashStart) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
return; // invalid metadata
}
mHashes.AppendElement(Substring(aToken, hashStart,
question - hashStart));
}
if (mAlgorithm.EqualsLiteral("sha256")) {
mAlgorithmType = nsICryptoHash::SHA256;
} else if (mAlgorithm.EqualsLiteral("sha384")) {
mAlgorithmType = nsICryptoHash::SHA384;
} else if (mAlgorithm.EqualsLiteral("sha512")) {
mAlgorithmType = nsICryptoHash::SHA512;
}
SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
mHashes[0].get(), mAlgorithm.get()));
}
bool
SRIMetadata::operator<(const SRIMetadata& aOther) const
{
static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
"We rely on the order indicating relative alg strength");
static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
"We rely on the order indicating relative alg strength");
MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
mAlgorithmType == nsICryptoHash::SHA256 ||
mAlgorithmType == nsICryptoHash::SHA384 ||
mAlgorithmType == nsICryptoHash::SHA512);
MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
aOther.mAlgorithmType == nsICryptoHash::SHA512);
if (mEmpty) {
SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
return true; // anything beats the empty metadata (incl. invalid ones)
}
SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
mAlgorithmType, aOther.mAlgorithmType));
return (mAlgorithmType < aOther.mAlgorithmType);
}
bool
SRIMetadata::operator>(const SRIMetadata& aOther) const
{
MOZ_ASSERT(false);
return false;
}
SRIMetadata&
SRIMetadata::operator+=(const SRIMetadata& aOther)
{
MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
MOZ_ASSERT(aOther.IsValid() && IsValid());
MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
// We only pull in the first element of the other metadata
MOZ_ASSERT(aOther.mHashes.Length() == 1);
if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
mAlgorithm.get(), mHashes.Length()));
mHashes.AppendElement(aOther.mHashes[0]);
}
MOZ_ASSERT(mHashes.Length() > 1);
MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
return *this;
}
bool
SRIMetadata::operator==(const SRIMetadata& aOther) const
{
if (IsEmpty() || !IsValid()) {
return false;
}
return mAlgorithmType == aOther.mAlgorithmType;
}
void
SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
{
MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
if (NS_WARN_IF(aIndex >= mHashes.Length())) {
*outHash = nullptr;
return;
}
*outHash = mHashes[aIndex];
}
void
SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
{
// these constants are defined in security/nss/lib/util/hasht.h and
// netwerk/base/public/nsICryptoHash.idl
switch (mAlgorithmType) {
case nsICryptoHash::SHA256:
*outLength = SHA256_LENGTH;
break;
case nsICryptoHash::SHA384:
*outLength = SHA384_LENGTH;
break;
case nsICryptoHash::SHA512:
*outLength = SHA512_LENGTH;
break;
default:
*outLength = 0;
}
*outType = mAlgorithmType;
}
} // namespace dom
} // namespace mozilla
+74
View File
@@ -0,0 +1,74 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SRIMetadata_h
#define mozilla_dom_SRIMetadata_h
#include "nsTArray.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
class SRIMetadata final
{
public:
static const uint32_t MAX_ALTERNATE_HASHES = 256;
static const int8_t UNKNOWN_ALGORITHM = -1;
/**
* Create an empty metadata object.
*/
SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
/**
* Split a string token into the components of an SRI metadata
* attribute.
*/
explicit SRIMetadata(const nsACString& aToken);
/**
* Returns true when this object's hash algorithm is weaker than the
* other object's hash algorithm.
*/
bool operator<(const SRIMetadata& aOther) const;
/**
* Not implemented. Should not be used.
*/
bool operator>(const SRIMetadata& aOther) const;
/**
* Add another metadata's hash to this one.
*/
SRIMetadata& operator+=(const SRIMetadata& aOther);
/**
* Returns true when the two metadata use the same hash algorithm.
*/
bool operator==(const SRIMetadata& aOther) const;
bool IsEmpty() const { return mEmpty; }
bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
uint32_t HashCount() const { return mHashes.Length(); }
void GetHash(uint32_t aIndex, nsCString* outHash) const;
void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
void GetHashType(int8_t* outType, uint32_t* outLength) const;
private:
nsTArray<nsCString> mHashes;
nsCString mAlgorithm;
int8_t mAlgorithmType;
bool mEmpty;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SRIMetadata_h
+4
View File
@@ -11,6 +11,8 @@ EXPORTS.mozilla.dom += [
'nsCSPService.h',
'nsCSPUtils.h',
'nsMixedContentBlocker.h',
'SRICheck.h',
'SRIMetadata.h',
]
EXPORTS += [
@@ -24,6 +26,8 @@ UNIFIED_SOURCES += [
'nsCSPService.cpp',
'nsCSPUtils.cpp',
'nsMixedContentBlocker.cpp',
'SRICheck.cpp',
'SRIMetadata.cpp',
]
FAIL_ON_WARNINGS = True
+1
View File
@@ -16,6 +16,7 @@ MOCHITEST_MANIFESTS += [
'cors/mochitest.ini',
'csp/mochitest.ini',
'mixedcontentblocker/mochitest.ini',
'sri/mochitest.ini',
]
MOCHITEST_CHROME_MANIFESTS += [
@@ -0,0 +1,135 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
window.hasCORSLoaded = false;
window.hasNonCORSLoaded = false;
function good_nonsriLoaded() {
ok(true, "Non-eligible non-SRI resource was loaded correctly.");
}
function bad_nonsriBlocked() {
ok(false, "Non-eligible non-SRI resources should be loaded!");
}
function good_nonCORSInvalidBlocked() {
ok(true, "A non-CORS resource with invalid metadata was correctly blocked.");
}
function bad_nonCORSInvalidLoaded() {
ok(false, "Non-CORS resources with invalid metadata should be blocked!");
}
window.onerrorCalled = false;
window.onloadCalled = false;
function bad_onloadCalled() {
window.onloadCalled = true;
}
function good_onerrorCalled() {
window.onerrorCalled = true;
}
function good_incorrect301Blocked() {
ok(true, "A non-CORS load with incorrect hash redirected to a different origin was blocked correctly.");
}
function bad_incorrect301Loaded() {
ok(false, "Non-CORS loads with incorrect hashes redirecting to a different origin should be blocked!");
}
function good_correct301Blocked() {
ok(true, "A non-CORS load with correct hash redirected to a different origin was blocked correctly.");
}
function bad_correct301Loaded() {
ok(false, "Non-CORS loads with correct hashes redirecting to a different origin should be blocked!");
}
function good_correctDataBlocked() {
ok(true, "A data: URL was blocked correctly.");
}
function bad_correctDataLoaded() {
ok(false, "Since data: URLs are neither same-origin nor CORS, they should be blocked!");
}
function good_correctDataCORSBlocked() {
ok(true, "A data: URL was blocked correctly even though it was a CORS load.");
}
function bad_correctDataCORSLoaded() {
todo(false, "We should not load scripts in data: URIs regardless of CORS mode!");
}
window.onload = function() {
SimpleTest.finish()
}
</script>
<!-- cors-enabled. should be loaded -->
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
crossorigin=""
integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q=="></script>
<!-- not cors-enabled. should be blocked -->
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain2.js"
crossorigin="anonymous"
integrity="sha256-ntgU2U1xv7HfK1XWMTSWz6vJkyVtGzMrIAxQkux1I94="
onload="bad_onloadCalled()"
onerror="good_onerrorCalled()"></script>
<!-- non-cors but not actually using SRI. should trigger onload -->
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain3.js"
integrity=" "
onload="good_nonsriLoaded()"
onerror="bad_nonsriBlocked()"></script>
<!-- non-cors with invalid metadata -->
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain4.js"
integrity="sha256-bogus"
onload="bad_nonCORSInvalidLoaded()"
onerror="good_nonCORSInvalidBlocked()"></script>
<!-- non-cors that's same-origin initially but redirected to another origin -->
<script src="script_301.js"
integrity="sha384-invalid"
onerror="good_incorrect301Blocked()"
onload="bad_incorrect301Loaded()"></script>
<!-- non-cors that's same-origin initially but redirected to another origin -->
<script src="script_301.js"
integrity="sha384-1NpiDI6decClMaTWSCAfUjTdx1BiOffsCPgH4lW5hCLwmHk0VyV/g6B9Sw2kD2K3"
onerror="good_correct301Blocked()"
onload="bad_correct301Loaded()"></script>
<!-- data: URLs are not same-origin -->
<script src="data:,console.log('data:valid');"
integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
onerror="good_correctDataBlocked()"
onload="bad_correctDataLoaded()"></script>
<!-- data: URLs should always be opaque -->
<script src="data:,console.log('data:valid');"
crossorigin="anonymous"
integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
onerror="good_correctDataCORSBlocked()"
onload="bad_correctDataCORSLoaded()"></script>
<script>
ok(window.hasCORSLoaded, "CORS-enabled resource with a correct hash");
ok(!window.hasNonCORSLoaded, "Correct hash, but non-CORS, should be blocked");
ok(!window.onloadCalled, "Failed loads should not call onload when they're cross-domain");
ok(window.onerrorCalled, "Failed loads should call onerror when they're cross-domain");
</script>
</body>
</html>
@@ -0,0 +1,249 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
window.onload = function() {
SimpleTest.finish();
}
</script>
<script>
function good_correctHashLoaded() {
ok(true, "A script was correctly loaded when integrity matched")
}
function bad_correctHashBlocked() {
ok(false, "We should load scripts with hashes that match!");
}
function good_correctHashArrayLoaded() {
ok(true, "A script was correctly loaded when one of the hashes in the integrity attribute matched")
}
function bad_correctHashArrayBlocked() {
ok(false, "We should load scripts with at least one hash that match!");
}
function good_emptyIntegrityLoaded() {
ok(true, "A script was correctly loaded when the integrity attribute was empty")
}
function bad_emptyIntegrityBlocked() {
ok(false, "We should load scripts with empty integrity attributes!");
}
function good_whitespaceIntegrityLoaded() {
ok(true, "A script was correctly loaded when the integrity attribute only contained whitespace")
}
function bad_whitespaceIntegrityBlocked() {
ok(false, "We should load scripts with integrity attributes containing only whitespace!");
}
function good_incorrectHashBlocked() {
ok(true, "A script was correctly blocked, because the hash digest was wrong");
}
function bad_incorrectHashLoaded() {
ok(false, "We should not load scripts with hashes that do not match the content!");
}
function good_incorrectHashArrayBlocked() {
ok(true, "A script was correctly blocked, because all the hashes were wrong");
}
function bad_incorrectHashArrayLoaded() {
ok(false, "We should not load scripts when none of the hashes match the content!");
}
function good_incorrectHashLengthBlocked() {
ok(true, "A script was correctly blocked, because the hash length was wrong");
}
function bad_incorrectHashLengthLoaded() {
ok(false, "We should not load scripts with hashes that don't have the right length!");
}
function bad_incorrectHashFunctionBlocked() {
ok(false, "We should load scripts with invalid/unsupported hash functions!");
}
function good_incorrectHashFunctionLoaded() {
ok(true, "A script was correctly loaded, despite the hash function being invalid/unsupported.");
}
function bad_missingHashFunctionBlocked() {
ok(false, "We should load scripts with missing hash functions!");
}
function good_missingHashFunctionLoaded() {
ok(true, "A script was correctly loaded, despite a missing hash function.");
}
function bad_missingHashValueBlocked() {
ok(false, "We should load scripts with missing hash digests!");
}
function good_missingHashValueLoaded() {
ok(true, "A script was correctly loaded, despite the missing hash digest.");
}
function good_401Blocked() {
ok(true, "A script was not loaded because of 401 response.");
}
function bad_401Loaded() {
ok(false, "We should nt load scripts with a 401 response!");
}
function good_valid302Loaded() {
ok(true, "A script was loaded successfully despite a 302 response.");
}
function bad_valid302Blocked() {
ok(false, "We should load scripts with a 302 response and the right hash!");
}
function good_invalid302Blocked() {
ok(true, "A script was blocked successfully after a 302 response.");
}
function bad_invalid302Loaded() {
ok(false, "We should not load scripts with a 302 response and the wrong hash!");
}
function good_validBlobLoaded() {
ok(true, "A script was loaded successfully from a blob: URL.");
}
function bad_validBlobBlocked() {
ok(false, "We should load scripts using blob: URLs with the right hash!");
}
function good_invalidBlobBlocked() {
ok(true, "A script was blocked successfully from a blob: URL.");
}
function bad_invalidBlobLoaded() {
ok(false, "We should not load scripts using blob: URLs with the wrong hash!");
}
</script>
</head>
<body>
<!-- valid hash. should trigger onload -->
<!-- the hash value comes from running this command:
cat script.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
-->
<script src="script.js"
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_correctHashBlocked()"
onload="good_correctHashLoaded()"></script>
<!-- valid sha512 hash. should trigger onload -->
<script src="script.js"
integrity="sha512-mzSqH+vC6qrXX46JX2WEZ0FtY/lGj/5+5yYCBlk0jfYHLm0vP6XgsURbq83mwMApsnwbDLXdgjp5J8E93GT6Mw==?ignore=this"
onerror="bad_correctHashBlocked()"
onload="good_correctHashLoaded()"></script>
<!-- one valid sha256 hash. should trigger onload -->
<script src="script.js"
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_correctHashArrayBlocked()"
onload="good_correctHashArrayLoaded()"></script>
<!-- empty integrity. should trigger onload -->
<script src="script.js"
integrity=""
onerror="bad_emptyIntegrityBlocked()"
onload="good_emptyIntegrityLoaded()"></script>
<!-- whitespace integrity. should trigger onload -->
<script src="script.js"
integrity="
"
onerror="bad_whitespaceIntegrityBlocked()"
onload="good_whitespaceIntegrityLoaded()"></script>
<!-- invalid sha256 hash but valid sha384 hash. should trigger onload -->
<script src="script.js"
integrity="sha256-bogus sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo?"
onerror="bad_correctHashBlocked()"
onload="good_correctHashLoaded()"></script>
<!-- valid sha256 and invalid sha384. should trigger onerror -->
<script src="script.js"
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha384-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="good_incorrectHashLengthBlocked()"
onload="bad_incorrectHashLengthLoaded()"></script>
<!-- invalid hash. should trigger onerror -->
<script src="script.js"
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="good_incorrectHashBlocked()"
onload="bad_incorrectHashLoaded()"></script>
<!-- invalid hashes. should trigger onerror -->
<script src="script.js"
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-ZkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-zkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="good_incorrectHashBlocked()"
onload="bad_incorrectHashLoaded()"></script>
<!-- invalid hash function. should trigger onload -->
<script src="script.js"
integrity="rot13-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_incorrectHashFunctionBlocked()"
onload="good_incorrectHashFunctionLoaded()"></script>
<!-- missing hash function. should trigger onload -->
<script src="script.js"
integrity="RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_missingHashFunctionBlocked()"
onload="good_missingHashFunctionLoaded()"></script>
<!-- missing hash value. should trigger onload -->
<script src="script.js"
integrity="sha512-"
onerror="bad_missingHashValueBlocked()"
onload="good_missingHashValueLoaded()"></script>
<!-- 401 response. should trigger onerror -->
<script src="script_401.js"
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="good_401Blocked()"
onload="bad_401Loaded()"></script>
<!-- valid sha256 after a redirection. should trigger onload -->
<script src="script_302.js"
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_valid302Blocked()"
onload="good_valid302Loaded()"></script>
<!-- invalid sha256 after a redirection. should trigger onerror -->
<script src="script_302.js"
integrity="sha256-JSi74NSN8WQNr9syBGmNg2APJp9PnHUO5ioZo5hmIiQ="
onerror="good_invalid302Blocked()"
onload="bad_invalid302Loaded()"></script>
<!-- valid sha256 for a blob: URL -->
<script>
var blob = new Blob(["console.log('blob:valid');"],
{type:"application/javascript"});
var script = document.createElement('script');
script.setAttribute('src', URL.createObjectURL(blob));
script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
script.onerror = bad_validBlobBlocked;
script.onload = good_validBlobLoaded;
var head = document.getElementsByTagName('head').item(0);
head.appendChild(script);
</script>
<!-- invalid sha256 for a blob: URL -->
<script>
var blob = new Blob(["console.log('blob:invalid');"],
{type:"application/javascript"});
var script = document.createElement('script');
script.setAttribute('src', URL.createObjectURL(blob));
script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
script.onerror = good_invalidBlobBlocked;
script.onload = bad_invalidBlobLoaded;
var head = document.getElementsByTagName('head').item(0);
head.appendChild(script);
</script>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>
@@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
window.onload = function() {
SimpleTest.finish();
}
</script>
<script>
function good_correctHashLoaded() {
ok(true, "A script was correctly loaded when integrity matched")
}
function bad_correctHashBlocked() {
ok(false, "We should load scripts with hashes that match!");
}
function good_incorrectHashLoaded() {
ok(true, "A script was correctly loaded despite the incorrect hash because SRI is disabled.");
}
function bad_incorrectHashBlocked() {
ok(false, "We should load scripts with hashes that do not match the content when SRI is disabled!");
}
function good_correctStyleHashLoaded() {
ok(true, "A stylesheet was correctly loaded when integrity matched")
}
function bad_correctStyleHashBlocked() {
ok(false, "We should load stylesheets with hashes that match!");
}
function good_incorrectStyleHashLoaded() {
ok(true, "A stylesheet was correctly loaded despite the incorrect hash because SRI is disabled.");
}
function bad_incorrectStyleHashBlocked() {
ok(false, "We should load stylesheets with hashes that do not match the content when SRI is disabled!");
}
</script>
<!-- valid sha256 hash. should trigger onload -->
<link rel="stylesheet" href="style1.css?disabled"
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
onerror="bad_correctStyleHashBlocked()"
onload="good_correctStyleHashLoaded()">
<!-- invalid sha256 hash. should trigger onerror -->
<link rel="stylesheet" href="style2.css?disabled"
integrity="sha256-bogus"
onerror="bad_incorrectStyleHashBlocked()"
onload="good_incorrectStyleHashLoaded()">
</head>
<body>
<!-- valid hash. should trigger onload -->
<script src="script.js"
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_correctHashBlocked()"
onload="good_correctHashLoaded()"></script>
<!-- invalid hash. should trigger onerror -->
<script src="script.js"
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
onerror="bad_incorrectHashBlocked()"
onload="good_incorrectHashLoaded()"></script>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>
@@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
function check_styles() {
var redText = document.getElementById('red-text');
var blackText = document.getElementById('black-text');
var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
var blackTextColor = window.getComputedStyle(blackText, null).getPropertyValue('color');
ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
todo(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
}
SimpleTest.waitForExplicitFinish();
window.onload = function() {
check_styles();
SimpleTest.finish();
}
</script>
<script>
function good_correctHashCORSLoaded() {
ok(true, "A CORS cross-domain stylesheet with correct hash was correctly loaded.");
}
function bad_correctHashCORSBlocked() {
ok(false, "We should load CORS cross-domain stylesheets with hashes that match!");
}
function good_correctHashBlocked() {
ok(true, "A non-CORS cross-domain stylesheet with correct hash was correctly blocked.");
}
function bad_correctHashLoaded() {
ok(false, "We should block non-CORS cross-domain stylesheets with hashes that match!");
}
function good_incorrectHashBlocked() {
ok(true, "A non-CORS cross-domain stylesheet with incorrect hash was correctly blocked.");
}
function bad_incorrectHashLoaded() {
ok(false, "We should load non-CORS cross-domain stylesheets with incorrect hashes!");
}
function good_correctDataBlocked() {
ok(true, "A stylesheet was correctly blocked, because it came from a data: URI.");
}
function bad_correctDataLoaded() {
ok(false, "We should not load stylesheets in data: URIs!");
}
function good_correctDataCORSBlocked() {
ok(true, "A stylesheet was correctly blocked, because it came from a data: URI even though it was a CORS load.");
}
function bad_correctDataCORSLoaded() {
todo(false, "We should not load stylesheets in data: URIs regardless of CORS mode!");
}
</script>
<!-- valid CORS sha256 hash -->
<link rel="stylesheet" href="http://example.com/tests/dom/security/test/sri/style1.css"
crossorigin="anonymous"
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
onerror="bad_correctHashCORSBlocked()"
onload="good_correctHashCORSLoaded()">
<!-- valid non-CORS sha256 hash -->
<link rel="stylesheet" href="style_301.css"
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
onerror="good_correctHashBlocked()"
onload="bad_correctHashLoaded()">
<!-- invalid non-CORS sha256 hash -->
<link rel="stylesheet" href="style_301.css?again"
integrity="sha256-bogus"
onerror="good_incorrectHashBlocked()"
onload="bad_incorrectHashLoaded()">
<!-- valid non-CORS sha256 hash in a data: URL -->
<link rel="stylesheet" href="data:text/css,.red-text{color:red}"
integrity="sha256-ewUcnAs4+XY5k2JpfUQGFdG5YMZkq80/nIKW67kd7vE="
onerror="good_correctDataBlocked()"
onload="bad_correctDataLoaded()">
<!-- valid CORS sha256 hash in a data: URL -->
<link rel="stylesheet" href="data:text/css,.red-text{color:red}"
crossorigin="anonymous"
integrity="sha256-ewUcnAs4+XY5k2JpfUQGFdG5YMZkq80/nIKW67kd7vE="
onerror="good_correctDataCORSBlocked()"
onload="bad_correctDataCORSLoaded()">
</head>
<body>
<p><span id="red-text">This should be red</span> but
<span id="black-text" class="red-text">this should remain black.</span></p>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>
@@ -0,0 +1,124 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
function check_styles() {
var redText = document.getElementById('red-text');
var blueText = document.getElementById('blue-text-element');
var blackText1 = document.getElementById('black-text');
var blackText2 = document.getElementById('black-text-2');
var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
var blueTextColor = window.getComputedStyle(blueText, null).getPropertyValue('color');
var blackTextColor1 = window.getComputedStyle(blackText1, null).getPropertyValue('color');
var blackTextColor2 = window.getComputedStyle(blackText2, null).getPropertyValue('color');
ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
ok(blueTextColor == 'rgb(0, 0, 255)', "The second part should be blue.");
ok(blackTextColor1 == 'rgb(0, 0, 0)', "The second last part should still be black.");
ok(blackTextColor2 == 'rgb(0, 0, 0)', "The last part should still be black.");
}
SimpleTest.waitForExplicitFinish();
window.onload = function() {
check_styles();
SimpleTest.finish();
}
</script>
<script>
function good_correctHashLoaded() {
ok(true, "A stylesheet was correctly loaded when integrity matched");
}
function bad_correctHashBlocked() {
ok(false, "We should load stylesheets with hashes that match!");
}
function good_emptyIntegrityLoaded() {
ok(true, "A stylesheet was correctly loaded when the integrity attribute was empty");
}
function bad_emptyIntegrityBlocked() {
ok(false, "We should load stylesheets with empty integrity attributes!");
}
function good_incorrectHashBlocked() {
ok(true, "A stylesheet was correctly blocked, because the hash digest was wrong");
}
function bad_incorrectHashLoaded() {
ok(false, "We should not load stylesheets with hashes that do not match the content!");
}
function good_validBlobLoaded() {
ok(true, "A stylesheet was loaded successfully from a blob: URL with the right hash.");
}
function bad_validBlobBlocked() {
ok(false, "We should load stylesheets using blob: URLs with the right hash!");
}
function good_invalidBlobBlocked() {
ok(true, "A stylesheet was blocked successfully from a blob: URL with an invalid hash.");
}
function bad_invalidBlobLoaded() {
ok(false, "We should not load stylesheets using blob: URLs when they have the wrong hash!");
}
</script>
<!-- valid sha256 hash. should trigger onload -->
<link rel="stylesheet" href="style1.css"
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
onerror="bad_correctHashBlocked()"
onload="good_correctHashLoaded()">
<!-- empty metadata. should trigger onload -->
<link rel="stylesheet" href="style2.css"
integrity=""
onerror="bad_emptyIntegrityBlocked()"
onload="good_emptyIntegrityLoaded()">
<!-- invalid sha256 hash. should trigger onerror -->
<link rel="stylesheet" href="style3.css"
integrity="sha256-bogus"
onerror="good_incorrectHashBlocked()"
onload="bad_incorrectHashLoaded()">
</head>
<body>
<!-- valid sha256 for a blob: URL -->
<script>
var blob = new Blob(['.blue-text{color:blue}'],
{type: 'text/css'});
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = window.URL.createObjectURL(blob);
link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
link.onerror = bad_validBlobBlocked;
link.onload = good_validBlobLoaded;
document.body.appendChild(link);
</script>
<!-- invalid sha256 for a blob: URL -->
<script>
var blob = new Blob(['.black-text{color:blue}'],
{type: 'text/css'});
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = window.URL.createObjectURL(blob);
link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
link.onerror = good_invalidBlobBlocked;
link.onload = bad_invalidBlobLoaded;
document.body.appendChild(link);
</script>
<p><span id="red-text">This should be red </span> and
<span class="blue-text" id="blue-text-element">this should be blue.</span>
However, <span id="black-text">this should stay black</span> and
<span class="black-text" id="black-text-2">this should also stay black.</span>
</p>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
[DEFAULT]
support-files =
iframe_script_crossdomain.html
iframe_script_sameorigin.html
iframe_sri_disabled.html
iframe_style_crossdomain.html
iframe_style_sameorigin.html
script_crossdomain1.js
script_crossdomain1.js^headers^
script_crossdomain2.js
script_crossdomain3.js
script_crossdomain3.js^headers^
script_crossdomain4.js
script_crossdomain4.js^headers^
script_crossdomain5.js
script_crossdomain5.js^headers^
script.js
script.js^headers^
script_301.js
script_301.js^headers^
script_302.js
script_302.js^headers^
script_401.js
script_401.js^headers^
style1.css
style1.css^headers^
style2.css
style3.css
style_301.css
style_301.css^headers^
[test_script_sameorigin.html]
[test_script_crossdomain.html]
[test_sri_disabled.html]
[test_style_crossdomain.html]
[test_style_sameorigin.html]
+1
View File
@@ -0,0 +1 @@
var load=true;
+1
View File
@@ -0,0 +1 @@
Cache-control: public
+1
View File
@@ -0,0 +1 @@
var load=false;
@@ -0,0 +1,2 @@
HTTP 301 Moved Permanently
Location: http://example.com/tests/dom/security/test/sri/script_crossdomain5.js
+1
View File
@@ -0,0 +1 @@
var load=false;
@@ -0,0 +1,2 @@
HTTP 302 Found
Location: /tests/dom/security/test/sri/script.js
+1
View File
@@ -0,0 +1 @@
var load=true;
@@ -0,0 +1,2 @@
HTTP 401 Authorization Required
Cache-control: public
@@ -0,0 +1,4 @@
/*
* this file should be loaded, because it has CORS enabled.
*/
window.hasCORSLoaded = true;
@@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888
@@ -0,0 +1,5 @@
/*
* this file should not be loaded, because it does not have CORS
* enabled.
*/
window.hasNonCORSLoaded = true;
@@ -0,0 +1 @@
// This script intentionally left blank
@@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888
@@ -0,0 +1 @@
// This script intentionally left blank
@@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888
@@ -0,0 +1 @@
// This script intentionally left blank
@@ -0,0 +1 @@
Access-Control-Allow-Origin: *
+3
View File
@@ -0,0 +1,3 @@
#red-text {
color: red;
}
@@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888
+1
View File
@@ -0,0 +1 @@
; A valid but somewhat uninteresting stylesheet
+3
View File
@@ -0,0 +1,3 @@
#black-text {
color: green;
}
+3
View File
@@ -0,0 +1,3 @@
#red-text {
color: red;
}
@@ -0,0 +1,2 @@
HTTP 301 Moved Permanently
Location: http://example.com/tests/dom/security/test/sri/style1.css
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Cross-domain script tests for Bug 992096</title>
<script>
SpecialPowers.setBoolPref("security.sri.enable", true);
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
<div>
<iframe src="iframe_script_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Same-origin script tests for Bug 992096</title>
<script>
SpecialPowers.setBoolPref("security.sri.enable", true);
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
<div>
<iframe src="iframe_script_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>security.sri.enable tests for Bug 992096</title>
<script>
SpecialPowers.setBoolPref("security.sri.enable", false);
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
<div>
<iframe src="iframe_sri_disabled.html" height="100%" width="90%" frameborder="0"></iframe>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Cross-domain stylesheet tests for Bug 1196740</title>
<script>
SpecialPowers.setBoolPref("security.sri.enable", true);
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196740">Mozilla Bug 1196740</a>
<div>
<iframe src="iframe_style_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Same-origin stylesheet tests for Bug 992096</title>
<script>
SpecialPowers.setBoolPref("security.sri.enable", true);
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
<div>
<iframe src="iframe_style_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
</div>
</body>
</html>
@@ -62,6 +62,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
"file_allowPointerLockSandboxFlag.html"
];
var gDisableList = [
// Bug 1174323
{ file: "file_screenClientXYConst.html",
platform: "MacIntel" }
];
var gTestWindow = null;
var gTestIndex = 0;
@@ -104,8 +110,22 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
function runNextTest() {
if (gTestIndex < gTestFiles.length) {
gTestWindow = window.open(gTestFiles[gTestIndex], "", "width=500,height=500");
var file = gTestFiles[gTestIndex];
gTestIndex++;
var skipTest = false;
for (var item of gDisableList) {
if (item.file == file && navigator.platform == item.platform) {
skipTest = true;
break;
}
}
if (!skipTest) {
info(`Testing ${file}`);
gTestWindow = window.open(file, "", "width=500,height=500");
} else {
nextTest();
}
} else {
finish();
}
+5
View File
@@ -48,3 +48,8 @@ partial interface HTMLLinkElement {
readonly attribute Document? import;
};
// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
partial interface HTMLLinkElement {
[SetterThrows]
attribute DOMString integrity;
};
+5
View File
@@ -33,3 +33,8 @@ partial interface HTMLScriptElement {
attribute DOMString htmlFor;
};
// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmlscriptelement-1
partial interface HTMLScriptElement {
[SetterThrows]
attribute DOMString integrity;
};
+24 -2
View File
@@ -815,10 +815,12 @@ namespace mozilla {
CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const SRIMetadata& aIntegrity)
: mSheets()
, mCORSMode(aCORSMode)
, mReferrerPolicy (aReferrerPolicy)
, mIntegrity(aIntegrity)
, mComplete(false)
#ifdef DEBUG
, mPrincipalSet(false)
@@ -940,6 +942,7 @@ CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
mPrincipal(aCopy.mPrincipal),
mCORSMode(aCopy.mCORSMode),
mReferrerPolicy(aCopy.mReferrerPolicy),
mIntegrity(aCopy.mIntegrity),
mComplete(aCopy.mComplete)
#ifdef DEBUG
, mPrincipalSet(aCopy.mPrincipalSet)
@@ -1080,7 +1083,26 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy)
mScopeElement(nullptr),
mRuleProcessors(nullptr)
{
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy);
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
SRIMetadata());
}
CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const SRIMetadata& aIntegrity)
: mTitle(),
mParent(nullptr),
mOwnerRule(nullptr),
mDocument(nullptr),
mOwningNode(nullptr),
mDisabled(false),
mDirty(false),
mInRuleProcessorCache(false),
mScopeElement(nullptr),
mRuleProcessors(nullptr)
{
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
aIntegrity);
}
CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy,
+9 -1
View File
@@ -26,6 +26,7 @@
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "mozilla/dom/SRIMetadata.h"
class CSSRuleListImpl;
class nsCSSRuleProcessor;
@@ -63,7 +64,8 @@ public:
private:
CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy);
ReferrerPolicy aReferrerPolicy,
const dom::SRIMetadata& aIntegrity);
CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
CSSStyleSheet* aPrimarySheet);
~CSSStyleSheetInner();
@@ -96,6 +98,7 @@ private:
// The Referrer Policy of a stylesheet is used for its child sheets, so it is
// stored here.
ReferrerPolicy mReferrerPolicy;
dom::SRIMetadata mIntegrity;
bool mComplete;
#ifdef DEBUG
@@ -123,6 +126,8 @@ class CSSStyleSheet final : public nsIStyleSheet,
public:
typedef net::ReferrerPolicy ReferrerPolicy;
CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy);
CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy,
const dom::SRIMetadata& aIntegrity);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CSSStyleSheet,
@@ -259,6 +264,9 @@ public:
// Get this style sheet's Referrer Policy
ReferrerPolicy GetReferrerPolicy() const { return mInner->mReferrerPolicy; }
// Get this style sheet's integrity metadata
dom::SRIMetadata GetIntegrity() const { return mInner->mIntegrity; }
dom::Element* GetScopeElement() const { return mScopeElement; }
void SetScopeElement(dom::Element* aScopeElement)
{
+51 -9
View File
@@ -62,6 +62,7 @@
#include "nsError.h"
#include "nsIContentSecurityPolicy.h"
#include "mozilla/dom/SRICheck.h"
#include "mozilla/dom/EncodingUtils.h"
using mozilla::dom::EncodingUtils;
@@ -265,6 +266,16 @@ GetLoaderLog()
return sLog;
}
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
#define LOG_ERROR(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Debug, args)
@@ -928,6 +939,18 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
}
}
SRIMetadata sriMetadata = mSheet->GetIntegrity();
if (!sriMetadata.IsEmpty() &&
NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, aLoader,
mSheet->GetCORSMode(), aBuffer,
mLoader->mDocument))) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("css::Loader::OnStreamComplete, bad metadata"));
mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
return NS_OK;
}
// Enough to set the URIs on mSheet, since any sibling datas we have share
// the same mInner as mSheet and will thus get the same URI.
mSheet->SetURIs(channelURI, originalURI, channelURI);
@@ -1057,6 +1080,7 @@ Loader::CreateSheet(nsIURI* aURI,
nsIPrincipal* aLoaderPrincipal,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
bool aSyncLoad,
bool aHasAlternateRel,
const nsAString& aTitle,
@@ -1204,7 +1228,17 @@ Loader::CreateSheet(nsIURI* aURI,
originalURI = aURI;
}
nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode, aReferrerPolicy);
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("css::Loader::CreateSheet, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
}
nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode,
aReferrerPolicy,
sriMetadata);
sheet->SetURIs(sheetURI, originalURI, baseURI);
sheet.forget(aSheet);
}
@@ -1415,6 +1449,8 @@ Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
}
SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity();
if (aLoadData->mSyncLoad) {
LOG((" Synchronous load"));
NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
@@ -1599,7 +1635,7 @@ Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
// send a minimal Accept header for text/css
// Send a minimal Accept header for text/css
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
false);
@@ -1932,8 +1968,10 @@ Loader::LoadInlineStyle(nsIContent* aElement,
StyleSheetState state;
nsRefPtr<CSSStyleSheet> sheet;
nsresult rv = CreateSheet(nullptr, aElement, nullptr, CORS_NONE,
mDocument->GetReferrerPolicy(), false, false,
aTitle, state, aIsAlternate, getter_AddRefs(sheet));
mDocument->GetReferrerPolicy(),
EmptyString(), // no inline integrity checks
false, false, aTitle, state, aIsAlternate,
getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(state == eSheetNeedsParser,
"Inline sheets should not be cached");
@@ -1979,6 +2017,7 @@ Loader::LoadStyleLink(nsIContent* aElement,
bool aHasAlternateRel,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
nsICSSLoaderObserver* aObserver,
bool* aIsAlternate)
{
@@ -2013,7 +2052,7 @@ Loader::LoadStyleLink(nsIContent* aElement,
StyleSheetState state;
nsRefPtr<CSSStyleSheet> sheet;
rv = CreateSheet(aURL, aElement, principal, aCORSMode,
aReferrerPolicy, false,
aReferrerPolicy, aIntegrity, false,
aHasAlternateRel, aTitle, state, aIsAlternate,
getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
@@ -2177,6 +2216,7 @@ Loader::LoadChildSheet(CSSStyleSheet* aParentSheet,
// For now, use CORS_NONE for child sheets
rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
aParentSheet->GetReferrerPolicy(),
EmptyString(), // integrity is only checked on main sheet
parentData ? parentData->mSyncLoad : false,
false, empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
@@ -2243,13 +2283,14 @@ Loader::LoadSheet(nsIURI* aURL,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
return InternalLoadNonDocumentSheet(aURL, false, false,
aOriginPrincipal, aCharset,
nullptr, aObserver, aCORSMode,
aReferrerPolicy);
aReferrerPolicy, aIntegrity);
}
nsresult
@@ -2261,7 +2302,8 @@ Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
CSSStyleSheet** aSheet,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
NS_PRECONDITION(aURL, "Must have a URI to load");
NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
@@ -2292,7 +2334,7 @@ Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
const nsSubstring& empty = EmptyString();
rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aCORSMode,
aReferrerPolicy, syncLoad, false,
aReferrerPolicy, aIntegrity, syncLoad, false,
empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
+6 -2
View File
@@ -223,6 +223,7 @@ public:
bool aHasAlternateRel,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
nsICSSLoaderObserver* aObserver,
bool* aIsAlternate);
@@ -320,7 +321,8 @@ public:
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode = CORS_NONE,
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
const nsAString& aIntegrity = EmptyString());
/**
* Stop loading all sheets. All nsICSSLoaderObservers involved will be
@@ -417,6 +419,7 @@ private:
nsIPrincipal* aLoaderPrincipal,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
bool aSyncLoad,
bool aHasAlternateRel,
const nsAString& aTitle,
@@ -450,7 +453,8 @@ private:
CSSStyleSheet** aSheet,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode = CORS_NONE,
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
const nsAString& aIntegrity = EmptyString());
// Post a load event for aObserver to be notified about aSheet. The
// notification will be sent with status NS_OK unless the load event is
+3
View File
@@ -2206,6 +2206,9 @@ pref("security.apps.privileged.CSP.default", "default-src * data: blob:; script-
pref("security.mixed_content.block_active_content", false);
pref("security.mixed_content.block_display_content", false);
// Sub-resource integrity
pref("security.sri.enable", false);
// Enable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 2);
+3 -3
View File
@@ -45,14 +45,14 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
break;
case eSpeculativeLoadScript:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
mCrossOrigin, false);
mCrossOrigin, mIntegrity, false);
break;
case eSpeculativeLoadScriptFromHead:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
mCrossOrigin, true);
mCrossOrigin, mIntegrity, true);
break;
case eSpeculativeLoadStyle:
aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin);
aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);
break;
case eSpeculativeLoadManifest:
aExecutor->ProcessOfflineManifest(mUrl);
+11 -1
View File
@@ -105,6 +105,7 @@ class nsHtml5SpeculativeLoad {
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aParserInHead)
{
NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
@@ -115,10 +116,12 @@ class nsHtml5SpeculativeLoad {
mCharset.Assign(aCharset);
mTypeOrCharsetSource.Assign(aType);
mCrossOrigin.Assign(aCrossOrigin);
mIntegrity.Assign(aIntegrity);
}
inline void InitStyle(const nsAString& aUrl, const nsAString& aCharset,
const nsAString& aCrossOrigin)
const nsAString& aCrossOrigin,
const nsAString& aIntegrity)
{
NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
@@ -126,6 +129,7 @@ class nsHtml5SpeculativeLoad {
mUrl.Assign(aUrl);
mCharset.Assign(aCharset);
mCrossOrigin.Assign(aCrossOrigin);
mIntegrity.Assign(aIntegrity);
}
/**
@@ -219,6 +223,12 @@ class nsHtml5SpeculativeLoad {
* attribute. If the attribute is not set, this will be a void string.
*/
nsString mMedia;
/**
* If mOpCode is eSpeculativeLoadScript[FromHead], this is the value of the
* "integrity" attribute. If the attribute is not set, this will be a void
* string.
*/
nsString mIntegrity;
};
#endif // nsHtml5SpeculativeLoad_h
+14 -2
View File
@@ -165,11 +165,14 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitScript(*url,
(charset) ? *charset : EmptyString(),
(type) ? *type : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString(),
mode == NS_HTML5TREE_BUILDER_IN_HEAD);
mCurrentHtmlScriptIsAsyncOrDefer =
aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
@@ -186,10 +189,13 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitStyle(*url,
(charset) ? *charset : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString());
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString());
}
} else if (rel->LowerCaseEqualsASCII("preconnect")) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
@@ -256,11 +262,14 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitScript(*url,
EmptyString(),
(type) ? *type : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString(),
mode == NS_HTML5TREE_BUILDER_IN_HEAD);
}
} else if (nsHtml5Atoms::style == aName) {
@@ -272,9 +281,12 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
if (url) {
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitStyle(*url, EmptyString(),
(crossOrigin) ? *crossOrigin : NullString());
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString());
}
}
break;
+5 -3
View File
@@ -919,6 +919,7 @@ nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
@@ -926,21 +927,22 @@ nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
return;
}
mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
aScriptFromHead,
aIntegrity, aScriptFromHead,
mSpeculationReferrerPolicy);
}
void
nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aCrossOrigin)
const nsAString& aCrossOrigin,
const nsAString& aIntegrity)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
mSpeculationReferrerPolicy);
mSpeculationReferrerPolicy, aIntegrity);
}
void
+3 -1
View File
@@ -248,10 +248,12 @@ class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead);
void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
const nsAString& aCrossOrigin);
const nsAString& aCrossOrigin,
const nsAString& aIntegrity);
void PreloadImage(const nsAString& aURL,
const nsAString& aCrossOrigin,
+2 -2
View File
@@ -23,8 +23,8 @@ if (typeof Components != "undefined") {
throw new Error("Please load this module with Component.utils.import or with require()");
}
let libxul = new SharedAll.Library("libxul", SharedAll.Constants.Path.libxul);
let Type = SharedAll.Type;
var libxul = new SharedAll.Library("libxul", SharedAll.Constants.Path.libxul);
var Type = SharedAll.Type;
libxul.declareLazyFFI(Primitives, "compress",
"workerlz4_compress",
+22 -16
View File
@@ -146,9 +146,20 @@ let WebProgressListener = {
if (aWebProgress && aWebProgress.isTopLevel) {
json.documentURI = content.document.documentURIObject.spec;
json.title = content.document.title;
json.charset = content.document.characterSet;
json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
json.principal = content.document.nodePrincipal;
json.synthetic = content.document.mozSyntheticDocument;
if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
let uri = aLocationURI.clone();
try {
// If the current URI contains a username/password, remove it.
uri.userPass = "";
} catch (ex) { /* Ignore failures on about: URIs. */ }
CrashReporter.annotateCrashReport("URL", uri.spec);
}
}
sendAsyncMessage("Content:LocationChange", json, objects);
@@ -255,6 +266,17 @@ let WebNavigation = {
},
loadURI: function(uri, flags, referrer, referrerPolicy, baseURI) {
if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
let annotation = uri;
try {
let url = Services.io.newURI(uri, null, null);
// If the current URI contains a username/password, remove it.
url.userPass = "";
annotation = url.spec;
} catch (ex) { /* Ignore failures to parse and failures
on about: URIs. */ }
CrashReporter.annotateCrashReport("URL", annotation);
}
if (referrer)
referrer = Services.io.newURI(referrer, null, null);
if (baseURI)
@@ -336,22 +358,6 @@ addEventListener("ImageContentLoaded", function (aEvent) {
}
}, false);
let DocumentObserver = {
init: function() {
Services.obs.addObserver(this, "document-element-inserted", false);
addEventListener("unload", () => {
Services.obs.removeObserver(this, "document-element-inserted");
});
},
observe: function(aSubject, aTopic, aData) {
if (aSubject == content.document) {
sendAsyncMessage("DocumentInserted", {synthetic: aSubject.mozSyntheticDocument});
}
},
};
DocumentObserver.init();
const ZoomManager = {
get fullZoom() {
return this._cache.fullZoom;
+12 -1
View File
@@ -1,5 +1,7 @@
[DEFAULT]
support-files = head.js
support-files =
head.js
file_contentTitle.html
[browser_autoscroll_disabled.js]
skip-if = e10s # Bug ?????? - test touches content (getElementById on the content document)
[browser_browserDrop.js]
@@ -10,6 +12,7 @@ skip-if = e10s # Bug 921935 - focusmanager issues with e10s
skip-if = e10s # Bug ?????? - intermittent crash of child process reported when run under e10s
[browser_bug982298.js]
skip-if = e10s # Bug 1064580
[browser_contentTitle.js]
[browser_default_image_filename.js]
skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_f7_caret_browsing.js]
@@ -18,6 +21,9 @@ skip-if = e10s
skip-if = e10s # Disabled for e10s: Bug ?????? - seems to be a timing issue with RemoteFinder.jsm messages coming later than the tests expect.
[browser_input_file_tooltips.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: doc.createElement is not a function)
[browser_isSynthetic.js]
support-files =
empty.png
[browser_keyevents_during_autoscrolling.js]
skip-if = e10s # Bug 921935 - focusmanager issues with e10s
[browser_save_resend_postdata.js]
@@ -26,4 +32,9 @@ support-files =
data/post_form_inner.sjs
data/post_form_outer.sjs
skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.contentDocument.getElementById("postForm").submit();)
[browser_content_url_annotation.js]
skip-if = !e10s || !crashreporter
support-files =
file_redirect.html
file_redirect_to.html
[browser_bug1170531.js]
@@ -0,0 +1,16 @@
let url = "https://example.com/browser/toolkit/content/tests/browser/file_contentTitle.html";
add_task(function*() {
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
yield new Promise((resolve) => {
addEventListener("TestLocationChange", function listener() {
removeEventListener("TestLocationChange", listener);
resolve();
}, true, true);
});
is(gBrowser.contentTitle, "Test Page", "Should have the right title.");
gBrowser.removeTab(tab);
});
@@ -0,0 +1,139 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*global Services, requestLongerTimeout, TestUtils, BrowserTestUtils,
ok, info, dump, is, Ci, Cu, Components, ctypes, privateNoteIntentionalCrash,
gBrowser, add_task, addEventListener, removeEventListener, ContentTask */
"use strict";
// Running this test in ASAN is slow.
requestLongerTimeout(2);
/**
* Returns a Promise that resolves once a remote <xul:browser> has experienced
* a crash. Resolves with the data from the .extra file (the crash annotations).
*
* @param browser
* The <xul:browser> that will crash
* @return Promise
*/
function crashBrowser(browser) {
let kv = {};
Cu.import("resource://gre/modules/KeyValueParser.jsm", kv);
// This frame script is injected into the remote browser, and used to
// intentionally crash the tab. We crash by using js-ctypes and dereferencing
// a bad pointer. The crash should happen immediately upon loading this
// frame script.
let frame_script = () => {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
let dies = function() {
privateNoteIntentionalCrash();
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
let crash = badptr.contents;
};
dump("Et tu, Brute?");
dies();
};
function checkSubject(subject, data) {
return subject instanceof Ci.nsIPropertyBag2 &&
subject.hasKey("abnormal");
};
let crashPromise = TestUtils.topicObserved('ipc:content-shutdown',
checkSubject);
let crashDataPromise = crashPromise.then(([subject, data]) => {
ok(subject instanceof Ci.nsIPropertyBag2);
let dumpID;
if ('nsICrashReporter' in Ci) {
dumpID = subject.getPropertyAsAString('dumpID');
ok(dumpID, "dumpID is present and not an empty string");
}
let extra = null;
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
let extrafile = minidumpDirectory.clone();
extrafile.append(dumpID + '.extra');
ok(extrafile.exists(), 'found .extra file');
extra = kv.parseKeyValuePairsFromFile(extrafile);
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
}
return extra;
});
// This frame script will crash the remote browser as soon as it is
// evaluated.
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
return crashDataPromise;
}
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
*
* @param directory
* The nsIFile representing the directory to remove from.
* @param filename
* A string for the file to remove from the directory.
*/
function removeFile(directory, filename) {
let file = directory.clone();
file.append(filename);
if (file.exists()) {
file.remove(false);
}
}
/**
* Returns the directory where crash dumps are stored.
*
* @return nsIFile
*/
function getMinidumpDirectory() {
let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
dir.append("minidumps");
return dir;
}
/**
* Checks that the URL is correctly annotated on a content process crash.
*/
add_task(function* test_content_url_annotation() {
let url = "https://example.com/browser/toolkit/content/tests/browser/file_redirect.html";
let redirect_url = "https://example.com/browser/toolkit/content/tests/browser/file_redirect_to.html";
yield BrowserTestUtils.withNewTab({
gBrowser: gBrowser
}, function* (browser) {
ok(browser.isRemoteBrowser, "Should be a remote browser");
// file_redirect.html should send us to file_redirect_to.html
let promise = ContentTask.spawn(browser, {}, function* () {
dump('ContentTask starting...\n');
yield new Promise((resolve) => {
addEventListener("RedirectDone", function listener() {
dump('Got RedirectDone\n');
removeEventListener("RedirectDone", listener);
resolve();
}, true, true);
});
});
browser.loadURI(url);
yield promise;
// Crash the tab
let annotations = yield crashBrowser(browser);
ok("URL" in annotations, "annotated a URL");
is(annotations.URL, redirect_url,
"Should have annotated the URL after redirect");
});
});
@@ -0,0 +1,72 @@
function LocationChangeListener(browser) {
this.browser = browser;
browser.addProgressListener(this);
}
LocationChangeListener.prototype = {
wasSynthetic: false,
browser: null,
destroy: function() {
this.browser.removeProgressListener(this);
},
onLocationChange: function(webProgress, request, location, flags) {
this.wasSynthetic = this.browser.isSyntheticDocument;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
}
const FILES = gTestPath.replace("browser_isSynthetic.js", "")
.replace("chrome://mochitests/content/", "http://example.com/");
function waitForPageShow(browser) {
return ContentTask.spawn(browser, null, function*() {
Cu.import("resource://gre/modules/PromiseUtils.jsm");
yield new Promise(resolve => {
let listener = () => {
removeEventListener("pageshow", listener, true);
resolve();
}
addEventListener("pageshow", listener, true);
});
});
}
add_task(function*() {
let tab = gBrowser.addTab("about:blank");
let browser = tab.linkedBrowser;
yield BrowserTestUtils.browserLoaded(browser);
let listener = new LocationChangeListener(browser);
is(browser.isSyntheticDocument, false, "Should not be synthetic");
let loadPromise = waitForPageShow(browser);
browser.loadURI("data:text/html;charset=utf-8,<html/>");
yield loadPromise;
is(listener.wasSynthetic, false, "Should not be synthetic");
is(browser.isSyntheticDocument, false, "Should not be synthetic");
loadPromise = waitForPageShow(browser);
browser.loadURI(FILES + "empty.png");
yield loadPromise;
is(listener.wasSynthetic, true, "Should be synthetic");
is(browser.isSyntheticDocument, true, "Should be synthetic");
loadPromise = waitForPageShow(browser);
browser.goBack();
yield loadPromise;
is(listener.wasSynthetic, false, "Should not be synthetic");
is(browser.isSyntheticDocument, false, "Should not be synthetic");
loadPromise = waitForPageShow(browser);
browser.goForward();
yield loadPromise;
is(listener.wasSynthetic, true, "Should be synthetic");
is(browser.isSyntheticDocument, true, "Should be synthetic");
listener.destroy();
gBrowser.removeTab(tab);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,14 @@
<html>
<head><title>Test Page</title></head>
<body>
<script type="text/javascript">
dump("Script!\n");
addEventListener("load", () => {
// Trigger an onLocationChange event. We want to make sure the title is still correct afterwards.
location.hash = "#x2";
var event = new Event("TestLocationChange");
document.dispatchEvent(event);
}, false);
</script>
</body>
</html>
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>redirecting...</title>
<script>
window.addEventListener("load",
() => window.location = "file_redirect_to.html");
</script>
<body>
redirectin u bro
</body>
</html>
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>redirected!</title>
<script>
window.addEventListener("load", () => {
var event = new Event("RedirectDone");
document.dispatchEvent(event);
});
</script>
<body>
u got redirected, bro
</body>
</html>
@@ -329,10 +329,6 @@
break;
}
case "DocumentInserted":
this._isSyntheticDocument = data.synthetic;
break;
case "FullZoomChange": {
this._fullZoom = data.value;
let event = document.createEvent("Events");
@@ -4770,6 +4770,7 @@ var Utils = {
case "CORS":
case "Iframe Sandbox":
case "Tracking Protection":
case "Sub-resource Integrity":
return CATEGORY_SECURITY;
default:
+2 -1
View File
@@ -180,10 +180,11 @@ RemoteWebProgressManager.prototype = {
this._browser.webNavigation._currentURI = location;
this._browser._characterSet = json.charset;
this._browser._documentURI = newURI(json.documentURI);
this._browser._contentTitle = "";
this._browser._contentTitle = json.title;
this._browser._imageDocument = null;
this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu;
this._browser._contentPrincipal = json.principal;
this._browser._isSyntheticDocument = json.synthetic;
this._browser._innerWindowID = json.innerWindowID;
}
+120
View File
@@ -0,0 +1,120 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const EXPORTED_SYMBOLS = ["MatchPattern"];
const PERMITTED_SCHEMES = ["http", "https", "file", "ftp"];
// This function converts a glob pattern (containing * and possibly ?
// as wildcards) to a regular expression.
function globToRegexp(pat, allowQuestion)
{
// Escape everything except ? and *.
pat = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
if (allowQuestion) {
pat = pat.replace(/\?/g, ".");
} else {
pat = pat.replace(/\?/g, "\\?");
}
pat = pat.replace(/\*/g, ".*");
return new RegExp("^" + pat + "$");
}
// These patterns follow the syntax in
// https://developer.chrome.com/extensions/match_patterns
function SingleMatchPattern(pat)
{
if (pat == "<all_urls>") {
this.scheme = PERMITTED_SCHEMES;
this.host = "*";
this.path = new RegExp('.*');
} else if (!pat) {
this.scheme = [];
} else {
let re = new RegExp("^(http|https|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$");
let match = re.exec(pat);
if (!match) {
Cu.reportError(`Invalid match pattern: '${pat}'`);
this.scheme = [];
return;
}
if (match[1] == '*') {
this.scheme = ["http", "https"];
} else {
this.scheme = [match[1]];
}
this.host = match[2];
this.path = globToRegexp(match[3], false);
// We allow the host to be empty for file URLs.
if (this.host == "" && this.scheme[0] != "file") {
Cu.reportError(`Invalid match pattern: '${pat}'`);
this.scheme = [];
return;
}
}
}
SingleMatchPattern.prototype = {
matches(uri) {
if (this.scheme.indexOf(uri.scheme) == -1) {
return false;
}
// This code ignores the port, as Chrome does.
if (this.host == '*') {
// Don't check anything.
} else if (this.host[0] == '*') {
// It must be *.foo. We also need to allow foo by itself.
let suffix = this.host.substr(2);
if (uri.host != suffix && !uri.host.endsWith("." + suffix)) {
return false;
}
} else {
if (this.host != uri.host) {
return false;
}
}
if (!this.path.test(uri.path)) {
return false;
}
return true;
}
};
function MatchPattern(pat)
{
this.pat = pat;
if (!pat) {
this.matchers = [];
} else if (pat instanceof String || typeof(pat) == "string") {
this.matchers = [new SingleMatchPattern(pat)];
} else {
this.matchers = [for (p of pat) new SingleMatchPattern(p)];
}
}
MatchPattern.prototype = {
// |uri| should be an nsIURI.
matches(uri) {
for (let matcher of this.matchers) {
if (matcher.matches(uri)) {
return true;
}
}
return false;
},
serialize() {
return this.pat;
},
};
+450
View File
@@ -0,0 +1,450 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["WebRequest"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
"resource://gre/modules/WebRequestCommon.jsm");
// TODO
// Figure out how to handle requestId. Gecko seems to have no such thing. (Bug 1163862)
// We also don't know the method for content policy. (Bug 1163862)
// We don't even have a window ID for HTTP observer stuff. (Bug 1163861)
function parseFilter(filter) {
if (!filter) {
filter = {};
}
// FIXME: Support windowId filtering.
return {urls: filter.urls || null, types: filter.types || null};
}
function parseExtra(extra, allowed) {
if (extra) {
for (let ex of extra) {
if (allowed.indexOf(ex) == -1) {
throw `Invalid option ${ex}`;
}
}
}
let result = {};
for (let al of allowed) {
if (extra && extra.indexOf(al) != -1) {
result[al] = true;
}
}
return result;
}
let ContentPolicyManager = {
policyData: new Map(),
policies: new Map(),
idMap: new Map(),
nextId: 0,
init() {
Services.ppmm.initialProcessData.webRequestContentPolicies = this.policyData;
Services.ppmm.addMessageListener("WebRequest:ShouldLoad", this);
Services.mm.addMessageListener("WebRequest:ShouldLoad", this);
},
receiveMessage(msg) {
let browser = msg.target instanceof Ci.nsIDOMXULElement ? msg.target : null;
for (let id of msg.data.ids) {
let callback = this.policies.get(id);
if (!callback) {
// It's possible that this listener has been removed and the
// child hasn't learned yet.
continue;
}
let response = null;
try {
response = callback({
url: msg.data.url,
windowId: msg.data.windowId,
parentWindowId: msg.data.parentWindowId,
type: msg.data.type,
browser: browser
});
} catch (e) {
Cu.reportError(e);
}
if (response && response.cancel) {
return {cancel: true};
}
// FIXME: Need to handle redirection here. (Bug 1163862)
}
return {};
},
addListener(callback, opts) {
let id = this.nextId++;
opts.id = id;
if (opts.filter.urls) {
opts.filter.urls = opts.filter.urls.serialize();
}
Services.ppmm.broadcastAsyncMessage("WebRequest:AddContentPolicy", opts);
this.policyData.set(id, opts);
this.policies.set(id, callback);
this.idMap.set(callback, id);
},
removeListener(callback) {
let id = this.idMap.get(callback);
Services.ppmm.broadcastAsyncMessage("WebRequest:RemoveContentPolicy", {id});
this.policyData.delete(id);
this.idMap.delete(callback);
this.policies.delete(id);
},
};
ContentPolicyManager.init();
function StartStopListener(manager)
{
this.manager = manager;
this.orig = null;
}
StartStopListener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
Ci.nsIStreamListener,
Ci.nsISupports]),
onStartRequest: function(request, context) {
this.manager.onStartRequest(request);
return this.orig.onStartRequest(request, context);
},
onStopRequest(request, context, statusCode) {
let result = this.orig.onStopRequest(request, context, statusCode);
this.manager.onStopRequest(request);
return result;
},
onDataAvailable(...args) {
return this.orig.onDataAvailable(...args);
}
};
let HttpObserverManager = {
modifyInitialized: false,
examineInitialized: false,
listeners: {
modify: new Map(),
afterModify: new Map(),
headersReceived: new Map(),
onStart: new Map(),
onStop: new Map(),
},
addOrRemove() {
let needModify = this.listeners.modify.size || this.listeners.afterModify.size;
if (needModify && !this.modifyInitialized) {
this.modifyInitialized = true;
Services.obs.addObserver(this, "http-on-modify-request", false);
} else if (!needModify && this.modifyInitialized) {
this.modifyInitialized = false;
Services.obs.removeObserver(this, "http-on-modify-request");
}
let needExamine = this.listeners.headersReceived.size ||
this.listeners.onStart.size ||
this.listeners.onStop.size;
if (needExamine && !this.examineInitialized) {
this.examineInitialized = true;
Services.obs.addObserver(this, "http-on-examine-response", false);
Services.obs.addObserver(this, "http-on-examine-cached-response", false);
Services.obs.addObserver(this, "http-on-examine-merged-response", false);
} else if (!needExamine && this.examineInitialized) {
this.examineInitialized = false;
Services.obs.removeObserver(this, "http-on-examine-response");
Services.obs.removeObserver(this, "http-on-examine-cached-response");
Services.obs.removeObserver(this, "http-on-examine-merged-response");
}
},
addListener(kind, callback, opts) {
this.listeners[kind].set(callback, opts);
this.addOrRemove();
},
removeListener(kind, callback) {
this.listeners[kind].delete(callback);
this.addOrRemove();
},
getLoadContext(channel) {
try {
return channel.QueryInterface(Ci.nsIChannel)
.notificationCallbacks
.getInterface(Components.interfaces.nsILoadContext);
} catch (e) {
try {
return channel.loadGroup
.notificationCallbacks
.getInterface(Components.interfaces.nsILoadContext);
} catch (e) {
return null;
}
}
},
getHeaders(channel, method) {
let headers = [];
let visitor = {
visitHeader(name, value) {
headers.push({name, value});
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIHttpHeaderVisitor,
Ci.nsISupports]),
};
channel[method](visitor);
return headers;
},
observe(subject, topic, data) {
if (topic == "http-on-modify-request") {
this.modify(subject, topic, data);
} else if (topic == "http-on-examine-response" ||
topic == "http-on-examine-cached-response" ||
topic == "http-on-examine-merged-response") {
this.examine(subject, topic, data);
}
},
shouldRunListener(policyType, uri, filter) {
return WebRequestCommon.typeMatches(policyType, filter.types) &&
WebRequestCommon.urlMatches(uri, filter.urls);
},
runChannelListener(request, kind) {
let listeners = this.listeners[kind];
let channel = request.QueryInterface(Ci.nsIHttpChannel);
let loadContext = this.getLoadContext(channel);
let browser = loadContext ? loadContext.topFrameElement : null;
let loadInfo = channel.loadInfo;
let policyType = loadInfo.contentPolicyType;
let requestHeaders;
let responseHeaders;
let includeStatus = kind == "headersReceived" || kind == "onStart" || kind == "onStop";
for (let [callback, opts] of listeners.entries()) {
if (!this.shouldRunListener(policyType, channel.URI, opts.filter)) {
continue;
}
let data = {
url: channel.URI.spec,
method: channel.requestMethod,
browser: browser,
type: WebRequestCommon.typeForPolicyType(policyType),
windowId: loadInfo.outerWindowID,
parentWindowId: loadInfo.parentOuterWindowID,
};
if (opts.requestHeaders) {
if (!requestHeaders) {
requestHeaders = this.getHeaders(channel, "visitRequestHeaders");
}
data.requestHeaders = requestHeaders;
}
if (opts.responseHeaders) {
if (!responseHeaders) {
responseHeaders = this.getHeaders(channel, "visitResponseHeaders");
}
data.responseHeaders = responseHeaders;
}
if (includeStatus) {
data.statusCode = channel.responseStatus;
}
let result = null;
try {
result = callback(data);
} catch (e) {
Cu.reportError(e);
}
if (!result || !opts.blocking) {
return true;
}
if (result.cancel) {
channel.cancel();
return false;
}
if (result.redirectUrl) {
channel.redirectTo(BrowserUtils.makeURI(result.redirectUrl));
return false;
}
if (opts.requestHeaders && result.requestHeaders) {
// Start by clearing everything.
for (let {name, value} of requestHeaders) {
channel.setRequestHeader(name, "", false);
}
for (let {name, value} of result.requestHeaders) {
channel.setRequestHeader(name, value, false);
}
}
if (opts.responseHeaders && result.responseHeaders) {
// Start by clearing everything.
for (let {name, value} of responseHeaders) {
channel.setResponseHeader(name, "", false);
}
for (let {name, value} of result.responseHeaders) {
channel.setResponseHeader(name, value, false);
}
}
}
},
modify(subject, topic, data) {
if (this.runChannelListener(subject, "modify")) {
this.runChannelListener(subject, "afterModify");
}
},
examine(subject, topic, data) {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
if (this.listeners.onStart.size || this.listeners.onStop.size) {
if (channel instanceof Components.interfaces.nsITraceableChannel) {
let listener = new StartStopListener(this);
let orig = subject.setNewListener(listener);
listener.orig = orig;
}
}
this.runChannelListener(subject, "headersReceived");
},
onStartRequest(request) {
this.runChannelListener(request, "onStart");
},
onStopRequest(request) {
this.runChannelListener(request, "onStop");
},
};
let onBeforeRequest = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
// FIXME: Add requestBody support.
let opts = parseExtra(opt_extraInfoSpec, ["blocking"]);
opts.filter = parseFilter(filter);
ContentPolicyManager.addListener(callback, opts);
},
removeListener(callback) {
ContentPolicyManager.removeListener(callback);
}
};
let onBeforeSendHeaders = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
let opts = parseExtra(opt_extraInfoSpec, ["requestHeaders", "blocking"]);
opts.filter = parseFilter(filter);
HttpObserverManager.addListener("modify", callback, opts);
},
removeListener(callback) {
HttpObserverManager.removeListener("modify", callback);
}
};
let onSendHeaders = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
let opts = parseExtra(opt_extraInfoSpec, ["requestHeaders"]);
opts.filter = parseFilter(filter);
HttpObserverManager.addListener("afterModify", callback, opts);
},
removeListener(callback) {
HttpObserverManager.removeListener("afterModify", callback);
}
};
let onHeadersReceived = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
let opts = parseExtra(opt_extraInfoSpec, ["blocking", "responseHeaders"]);
opts.filter = parseFilter(filter);
HttpObserverManager.addListener("headersReceived", callback, opts);
},
removeListener(callback) {
HttpObserverManager.removeListener("headersReceived", callback);
}
};
let onResponseStarted = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
let opts = parseExtra(opt_extraInfoSpec, ["responseHeaders"]);
opts.filter = parseFilter(filter);
HttpObserverManager.addListener("onStart", callback, opts);
},
removeListener(callback) {
HttpObserverManager.removeListener("onStart", callback);
}
};
let onCompleted = {
addListener(callback, filter = null, opt_extraInfoSpec = null) {
let opts = parseExtra(opt_extraInfoSpec, ["responseHeaders"]);
opts.filter = parseFilter(filter);
HttpObserverManager.addListener("onStop", callback, opts);
},
removeListener(callback) {
HttpObserverManager.removeListener("onStop", callback);
}
};
let WebRequest = {
// Handled via content policy.
onBeforeRequest: onBeforeRequest,
// http-on-modify observer.
onBeforeSendHeaders: onBeforeSendHeaders,
// http-on-modify observer.
onSendHeaders: onSendHeaders,
// http-on-examine-*observer.
onHeadersReceived: onHeadersReceived,
// OnStartRequest channel listener.
onResponseStarted: onResponseStarted,
// OnStopRequest channel listener.
onCompleted: onCompleted,
};
Services.ppmm.loadProcessScript("resource://gre/modules/WebRequestContent.js", true);
@@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const EXPORTED_SYMBOLS = ["WebRequestCommon"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
let WebRequestCommon = {
typeForPolicyType(type) {
switch (type) {
case Ci.nsIContentPolicy.TYPE_DOCUMENT: return "main_frame";
case Ci.nsIContentPolicy.TYPE_SUBDOCUMENT: return "sub_frame";
case Ci.nsIContentPolicy.TYPE_STYLESHEET: return "stylesheet";
case Ci.nsIContentPolicy.TYPE_SCRIPT: return "script";
case Ci.nsIContentPolicy.TYPE_IMAGE: return "image";
case Ci.nsIContentPolicy.TYPE_OBJECT: return "object";
case Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST: return "object";
case Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST: return "xmlhttprequest";
default: return "other";
}
},
typeMatches(policyType, filterTypes) {
if (filterTypes === null) {
return true;
}
return filterTypes.indexOf(this.typeForPolicyType(policyType)) != -1;
},
urlMatches(uri, urlFilter) {
if (urlFilter === null) {
return true;
}
return urlFilter.matches(uri);
}
};
+176
View File
@@ -0,0 +1,176 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
"resource://gre/modules/WebRequestCommon.jsm");
let ContentPolicy = {
_classDescription: "WebRequest content policy",
_classID: Components.ID("938e5d24-9ccc-4b55-883e-c252a41f7ce9"),
_contractID: "@mozilla.org/webrequest/policy;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy,
Ci.nsIFactory,
Ci.nsISupportsWeakReference]),
init() {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
this.contentPolicies = new Map();
Services.cpmm.addMessageListener("WebRequest:AddContentPolicy", this);
Services.cpmm.addMessageListener("WebRequest:RemoveContentPolicy", this);
if (initialProcessData && initialProcessData.webRequestContentPolicies) {
for (let data of initialProcessData.webRequestContentPolicies.values()) {
this.addContentPolicy(data);
}
}
},
addContentPolicy({id, blocking, filter}) {
if (this.contentPolicies.size == 0) {
this.register();
}
if (filter.urls) {
filter.urls = new MatchPattern(filter.urls);
}
this.contentPolicies.set(id, {blocking, filter});
},
receiveMessage(msg) {
switch (msg.name) {
case "WebRequest:AddContentPolicy":
this.addContentPolicy(msg.data);
break;
case "WebRequest:RemoveContentPolicy":
this.contentPolicies.delete(msg.data.id);
if (this.contentPolicies.size == 0) {
this.unregister();
}
break;
}
},
register() {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
},
unregister() {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
},
shouldLoad(policyType, contentLocation, requestOrigin,
node, mimeTypeGuess, extra, requestPrincipal) {
let block = false;
let ids = [];
for (let [id, {blocking, filter}] of this.contentPolicies.entries()) {
if (WebRequestCommon.typeMatches(policyType, filter.types) &&
WebRequestCommon.urlMatches(contentLocation, filter.urls))
{
if (blocking) {
block = true;
}
ids.push(id);
}
}
if (!ids.length) {
return Ci.nsIContentPolicy.ACCEPT;
}
let windowId = 0;
let parentWindowId = -1;
let mm = Services.cpmm;
function getWindowId(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
if (policyType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT ||
(node instanceof Ci.nsIDOMXULElement && node.localName == "browser"))
{
// Chrome sets frameId to the ID of the sub-window. But when
// Firefox loads an iframe, it sets |node| to the <iframe>
// element, whose window is the parent window. We adopt the
// Chrome behavior here.
node = node.contentWindow;
}
if (node) {
let window;
if (node instanceof Ci.nsIDOMWindow) {
window = node;
} else {
let doc;
if (node.ownerDocument) {
doc = node.ownerDocument;
} else {
doc = node;
}
window = doc.defaultView;
}
windowId = getWindowId(window);
if (window.parent !== window) {
parentWindowId = getWindowId(window.parent);
}
let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor);
try {
// If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
mm = ir.getInterface(Ci.nsIContentFrameMessageManager);
} catch (e if e.result == Cr.NS_NOINTERFACE) {}
}
let data = {ids,
url: contentLocation.spec,
type: WebRequestCommon.typeForPolicyType(policyType),
windowId,
parentWindowId};
if (block) {
let rval = mm.sendSyncMessage("WebRequest:ShouldLoad", data);
if (rval.length == 1 && rval[0].cancel) {
return Ci.nsIContentPolicy.REJECT;
}
} else {
mm.sendAsyncMessage("WebRequest:ShouldLoad", data);
}
return Ci.nsIContentPolicy.ACCEPT;
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
return Ci.nsIContentPolicy.ACCEPT;
},
createInstance: function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
};
ContentPolicy.init();
+4
View File
@@ -12,6 +12,10 @@ MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
SPHINX_TREES['toolkit_modules'] = 'docs'
EXTRA_JS_MODULES += [
'addons/MatchPattern.jsm',
'addons/WebRequest.jsm',
'addons/WebRequestCommon.jsm',
'addons/WebRequestContent.js',
'Battery.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
@@ -9,6 +9,9 @@ support-files =
skip-if = e10s # Bug ?????? - test already uses content scripts, but still fails only under e10s.
[browser_Geometry.js]
[browser_InlineSpellChecker.js]
[browser_WebRequest.js]
[browser_WebRequest_cookies.js]
[browser_WebRequest_filtering.js]
[browser_PageMetadata.js]
[browser_RemoteWebNavigation.js]
[browser_Troubleshoot.js]
@@ -0,0 +1,188 @@
"use strict";
const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
let {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
const BASE = "http://example.com/browser/toolkit/modules/tests/browser";
const URL = BASE + "/file_WebRequest_page1.html";
let expected_browser;
function checkType(details)
{
let expected_type = "???";
if (details.url.indexOf("style") != -1) {
expected_type = "stylesheet";
} else if (details.url.indexOf("image") != -1) {
expected_type = "image";
} else if (details.url.indexOf("script") != -1) {
expected_type = "script";
} else if (details.url.indexOf("page1") != -1) {
expected_type = "main_frame";
} else if (details.url.indexOf("page2") != -1) {
expected_type = "sub_frame";
} else if (details.url.indexOf("xhr") != -1) {
expected_type = "xmlhttprequest";
}
is(details.type, expected_type, "resource type is correct");
}
let windowIDs = new Map();
let requested = [];
function onBeforeRequest(details)
{
info(`onBeforeRequest ${details.url}`);
if (details.url.startsWith(BASE)) {
requested.push(details.url);
is(details.browser, expected_browser, "correct <browser> element");
checkType(details);
windowIDs.set(details.url, details.windowId);
if (details.url.indexOf("page2") != -1) {
let page1id = windowIDs.get(URL);
ok(details.windowId != page1id, "sub-frame gets its own window ID");
is(details.parentWindowId, page1id, "parent window id is correct");
}
}
if (details.url.indexOf("_bad.") != -1) {
return {cancel: true};
}
}
let sendHeaders = [];
function onBeforeSendHeaders(details)
{
info(`onBeforeSendHeaders ${details.url}`);
if (details.url.startsWith(BASE)) {
sendHeaders.push(details.url);
is(details.browser, expected_browser, "correct <browser> element");
checkType(details);
let id = windowIDs.get(details.url);
is(id, details.windowId, "window ID same in onBeforeSendHeaders as onBeforeRequest");
}
if (details.url.indexOf("_redirect.") != -1) {
return {redirectUrl: details.url.replace("_redirect.", "_good.")};
}
}
let headersReceived = [];
function onResponseStarted(details)
{
if (details.url.startsWith(BASE)) {
headersReceived.push(details.url);
}
}
const expected_requested = [BASE + "/file_WebRequest_page1.html",
BASE + "/file_style_good.css",
BASE + "/file_style_bad.css",
BASE + "/file_style_redirect.css",
BASE + "/file_image_good.png",
BASE + "/file_image_bad.png",
BASE + "/file_image_redirect.png",
BASE + "/file_script_good.js",
BASE + "/file_script_bad.js",
BASE + "/file_script_redirect.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/xhr_resource"];
const expected_sendHeaders = [BASE + "/file_WebRequest_page1.html",
BASE + "/file_style_good.css",
BASE + "/file_style_redirect.css",
BASE + "/file_image_good.png",
BASE + "/file_image_redirect.png",
BASE + "/file_script_good.js",
BASE + "/file_script_redirect.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/xhr_resource"];
const expected_headersReceived = [BASE + "/file_WebRequest_page1.html",
BASE + "/file_style_good.css",
BASE + "/file_image_good.png",
BASE + "/file_script_good.js",
BASE + "/file_script_xhr.js",
BASE + "/file_WebRequest_page2.html",
BASE + "/nonexistent_script_url.js",
BASE + "/xhr_resource"];
function removeDupes(list)
{
let j = 0;
for (let i = 1; i < list.length; i++) {
if (list[i] != list[j]) {
j++;
if (i != j) {
list[j] = list[i];
}
}
}
list.length = j + 1;
}
function compareLists(list1, list2, kind)
{
list1.sort();
removeDupes(list1);
list2.sort();
removeDupes(list2);
is(String(list1), String(list2), `${kind} URLs correct`);
}
function* test_once()
{
WebRequest.onBeforeRequest.addListener(onBeforeRequest, null, ["blocking"]);
WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, null, ["blocking"]);
WebRequest.onResponseStarted.addListener(onResponseStarted);
gBrowser.selectedTab = gBrowser.addTab();
let browser = gBrowser.selectedBrowser;
expected_browser = browser;
yield waitForLoad();
browser.messageManager.loadFrameScript(`data:,content.location = "${URL}";`, false);
yield waitForLoad();
let win = browser.contentWindow.wrappedJSObject;
is(win.success, 2, "Good script ran");
is(win.failure, undefined, "Failure script didn't run");
let style = browser.contentWindow.getComputedStyle(browser.contentDocument.getElementById("test"), null);
is(style.getPropertyValue("color"), "rgb(255, 0, 0)", "Good CSS loaded");
gBrowser.removeCurrentTab();
compareLists(requested, expected_requested, "requested");
compareLists(sendHeaders, expected_sendHeaders, "sendHeaders");
compareLists(headersReceived, expected_headersReceived, "headersReceived");
WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
WebRequest.onResponseStarted.removeListener(onResponseStarted);
}
// Run the test twice to make sure it works with caching.
add_task(test_once);
add_task(test_once);
function waitForLoad(browser = gBrowser.selectedBrowser) {
return new Promise(resolve => {
browser.addEventListener("load", function listener() {
browser.removeEventListener("load", listener, true);
resolve();
}, true);
});
}
@@ -0,0 +1,89 @@
"use strict";
const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
let {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
const BASE = "http://example.com/browser/toolkit/modules/tests/browser";
const URL = BASE + "/WebRequest_dynamic.sjs";
let countBefore = 0;
let countAfter = 0;
function onBeforeSendHeaders(details)
{
if (details.url != URL) {
return;
}
countBefore++;
info(`onBeforeSendHeaders ${details.url}`);
let found = false;
let headers = [];
for (let {name, value} of details.requestHeaders) {
info(`Saw header ${name} '${value}'`);
if (name == "Cookie") {
is(value, "foopy=1", "Cookie is correct");
headers.push({name, value: "blinky=1"});
found = true;
} else {
headers.push({name, value});
}
}
ok(found, "Saw cookie header");
return {requestHeaders: headers};
}
function onResponseStarted(details)
{
if (details.url != URL) {
return;
}
countAfter++;
info(`onResponseStarted ${details.url}`);
let found = false;
for (let {name, value} of details.responseHeaders) {
info(`Saw header ${name} '${value}'`);
if (name == "Set-Cookie") {
is(value, "dinky=1", "Cookie is correct");
found = true;
}
}
ok(found, "Saw cookie header");
}
add_task(function* filter_urls() {
// First load the URL so that we set cookie foopy=1.
gBrowser.selectedTab = gBrowser.addTab(URL);
yield waitForLoad();
gBrowser.removeCurrentTab();
// Now load with WebRequest set up.
WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, null, ["blocking"]);
WebRequest.onResponseStarted.addListener(onResponseStarted, null);
gBrowser.selectedTab = gBrowser.addTab(URL);
yield waitForLoad();
gBrowser.removeCurrentTab();
WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
WebRequest.onResponseStarted.removeListener(onResponseStarted);
is(countBefore, 1, "onBeforeSendHeaders hit once");
is(countAfter, 1, "onResponseStarted hit once");
});
function waitForLoad(browser = gBrowser.selectedBrowser) {
return new Promise(resolve => {
browser.addEventListener("load", function listener() {
browser.removeEventListener("load", listener, true);
resolve();
}, true);
});
}
@@ -0,0 +1,118 @@
"use strict";
const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
let {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
let {MatchPattern} = Cu.import("resource://gre/modules/MatchPattern.jsm", {});
const BASE = "http://example.com/browser/toolkit/modules/tests/browser";
const URL = BASE + "/file_WebRequest_page2.html";
let requested = [];
function onBeforeRequest(details)
{
info(`onBeforeRequest ${details.url}`);
if (details.url.startsWith(BASE)) {
requested.push(details.url);
}
}
let sendHeaders = [];
function onBeforeSendHeaders(details)
{
info(`onBeforeSendHeaders ${details.url}`);
if (details.url.startsWith(BASE)) {
sendHeaders.push(details.url);
}
}
let completed = [];
function onResponseStarted(details)
{
if (details.url.startsWith(BASE)) {
completed.push(details.url);
}
}
const expected_urls = [BASE + "/file_style_good.css",
BASE + "/file_style_bad.css",
BASE + "/file_style_redirect.css"];
function removeDupes(list)
{
let j = 0;
for (let i = 1; i < list.length; i++) {
if (list[i] != list[j]) {
j++;
if (i != j) {
list[j] = list[i];
}
}
}
list.length = j + 1;
}
function compareLists(list1, list2, kind)
{
list1.sort();
removeDupes(list1);
list2.sort();
removeDupes(list2);
is(String(list1), String(list2), `${kind} URLs correct`);
}
add_task(function* filter_urls() {
let filter = {urls: new MatchPattern("*://*/*_style_*")};
WebRequest.onBeforeRequest.addListener(onBeforeRequest, filter, ["blocking"]);
WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, filter, ["blocking"]);
WebRequest.onResponseStarted.addListener(onResponseStarted, filter);
gBrowser.selectedTab = gBrowser.addTab(URL);
yield waitForLoad();
gBrowser.removeCurrentTab();
compareLists(requested, expected_urls, "requested");
compareLists(sendHeaders, expected_urls, "sendHeaders");
compareLists(completed, expected_urls, "completed");
WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
WebRequest.onResponseStarted.removeListener(onResponseStarted);
});
add_task(function* filter_types() {
let filter = {types: ["stylesheet"]};
WebRequest.onBeforeRequest.addListener(onBeforeRequest, filter, ["blocking"]);
WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, filter, ["blocking"]);
WebRequest.onResponseStarted.addListener(onResponseStarted, filter);
gBrowser.selectedTab = gBrowser.addTab(URL);
yield waitForLoad();
gBrowser.removeCurrentTab();
compareLists(requested, expected_urls, "requested");
compareLists(sendHeaders, expected_urls, "sendHeaders");
compareLists(completed, expected_urls, "completed");
WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
WebRequest.onResponseStarted.removeListener(onResponseStarted);
});
function waitForLoad(browser = gBrowser.selectedBrowser) {
return new Promise(resolve => {
browser.addEventListener("load", function listener() {
browser.removeEventListener("load", listener, true);
resolve();
}, true);
});
}
+23
View File
@@ -0,0 +1,23 @@
function removeDupes(list)
{
let j = 0;
for (let i = 1; i < list.length; i++) {
if (list[i] != list[j]) {
j++;
if (i != j) {
list[j] = list[i];
}
}
}
list.length = j + 1;
}
function compareLists(list1, list2, kind)
{
list1.sort();
removeDupes(list1);
list2.sort();
removeDupes(list2);
is(String(list1), String(list2), `${kind} URLs correct`);
}
@@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Components.utils.import("resource://gre/modules/MatchPattern.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
function test(url, pattern)
{
let uri = Services.io.newURI(url, null, null);
let m = new MatchPattern(pattern);
return m.matches(uri);
}
function pass({url, pattern})
{
do_check_true(test(url, pattern), `Expected match: ${JSON.stringify(pattern)}, ${url}`);
}
function fail({url, pattern})
{
do_check_false(test(url, pattern), `Expected no match: ${JSON.stringify(pattern)}, ${url}`);
}
function run_test()
{
// Invalid pattern.
fail({url:"http://mozilla.org", pattern:""});
// Pattern must include trailing slash.
fail({url:"http://mozilla.org", pattern:"http://mozilla.org"});
// Protocol not allowed.
fail({url:"http://mozilla.org", pattern:"gopher://wuarchive.wustl.edu/"});
pass({url:"http://mozilla.org", pattern:"http://mozilla.org/"});
pass({url:"http://mozilla.org/", pattern:"http://mozilla.org/"});
pass({url:"http://mozilla.org/", pattern:"*://mozilla.org/"});
pass({url:"https://mozilla.org/", pattern:"*://mozilla.org/"});
fail({url:"file://mozilla.org/", pattern:"*://mozilla.org/"});
fail({url:"ftp://mozilla.org/", pattern:"*://mozilla.org/"});
fail({url:"http://mozilla.com", pattern:"http://*mozilla.com*/"});
fail({url:"http://mozilla.com", pattern:"http://mozilla.*/"});
fail({url:"http://mozilla.com", pattern:"http:/mozilla.com/"});
pass({url:"http://google.com", pattern:"http://*.google.com/"});
pass({url:"http://docs.google.com", pattern:"http://*.google.com/"});
pass({url:"http://mozilla.org:8080", pattern:"http://mozilla.org/"});
pass({url:"http://mozilla.org:8080", pattern:"*://mozilla.org/"});
fail({url:"http://mozilla.org:8080", pattern:"http://mozilla.org:8080/"});
// Now try with * in the path.
pass({url:"http://mozilla.org", pattern:"http://mozilla.org/*"});
pass({url:"http://mozilla.org/", pattern:"http://mozilla.org/*"});
pass({url:"http://mozilla.org/", pattern:"*://mozilla.org/*"});
pass({url:"https://mozilla.org/", pattern:"*://mozilla.org/*"});
fail({url:"file://mozilla.org/", pattern:"*://mozilla.org/*"});
fail({url:"http://mozilla.com", pattern:"http://mozilla.*/*"});
pass({url:"http://google.com", pattern:"http://*.google.com/*"});
pass({url:"http://docs.google.com", pattern:"http://*.google.com/*"});
// Check path stuff.
fail({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/"});
pass({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/*"});
pass({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/a*f"});
pass({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/a*"});
pass({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/*f"});
fail({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/*e"});
fail({url:"http://mozilla.com/abc/def", pattern:"http://mozilla.com/*c"});
fail({url:"http:///a.html", pattern:"http:///a.html"});
pass({url:"file:///foo", pattern:"file:///foo*"});
pass({url:"file:///foo/bar.html", pattern:"file:///foo*"});
pass({url:"http://mozilla.org/a", pattern:"<all_urls>"});
pass({url:"https://mozilla.org/a", pattern:"<all_urls>"});
pass({url:"ftp://mozilla.org/a", pattern:"<all_urls>"});
pass({url:"file:///a", pattern:"<all_urls>"});
fail({url:"gopher://wuarchive.wustl.edu/a", pattern:"<all_urls>"});
// Multiple patterns.
pass({url:"http://mozilla.org", pattern:["http://mozilla.org/"]});
pass({url:"http://mozilla.org", pattern:["http://mozilla.org/", "http://mozilla.com/"]});
pass({url:"http://mozilla.com", pattern:["http://mozilla.org/", "http://mozilla.com/"]});
fail({url:"http://mozilla.biz", pattern:["http://mozilla.org/", "http://mozilla.com/"]});
}
@@ -15,6 +15,7 @@ support-files =
[test_GMPInstallManager.js]
[test_Http.js]
[test_Log.js]
[test_MatchPattern.js]
[test_NewTabUtils.js]
[test_PermissionsUtils.js]
[test_Preferences.js]
+5
View File
@@ -657,6 +657,11 @@
ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION, FAILURE(98)),
ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION, FAILURE(99)),
/* Error code for Sub-Resource Integrity */
ERROR(NS_ERROR_SRI_CORRUPT, FAILURE(200)),
ERROR(NS_ERROR_SRI_DISABLED, FAILURE(201)),
ERROR(NS_ERROR_SRI_NOT_ELIGIBLE, FAILURE(202)),
/* CMS specific nsresult error codes. Note: the numbers used here correspond
* to the values in nsICMSMessageErrors.idl. */
ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED, FAILURE(1024)),