mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:37:11 +00:00
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:
@@ -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
|
||||
|
||||
@@ -429,6 +429,7 @@ LOCAL_INCLUDES += [
|
||||
'/layout/svg',
|
||||
'/layout/xul',
|
||||
'/netwerk/base',
|
||||
'/security/manager/ssl',
|
||||
'/widget',
|
||||
'/xpcom/ds',
|
||||
]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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]
|
||||
@@ -0,0 +1 @@
|
||||
var load=true;
|
||||
@@ -0,0 +1 @@
|
||||
Cache-control: public
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
var load=false;
|
||||
@@ -0,0 +1,2 @@
|
||||
HTTP 302 Found
|
||||
Location: /tests/dom/security/test/sri/script.js
|
||||
@@ -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: *
|
||||
@@ -0,0 +1,3 @@
|
||||
#red-text {
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Access-Control-Allow-Origin: http://mochi.test:8888
|
||||
@@ -0,0 +1 @@
|
||||
; A valid but somewhat uninteresting stylesheet
|
||||
@@ -0,0 +1,3 @@
|
||||
#black-text {
|
||||
color: green;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user