Bug 1425104: Part 1a - Support unrestricted matching in MatchPattern.
MozReview-Commit-ID: 4aW2w81LDTI
--- a/dom/chrome-webidl/MatchPattern.webidl
+++ b/dom/chrome-webidl/MatchPattern.webidl
@@ -120,9 +120,15 @@ interface MatchPatternSet {
};
dictionary MatchPatternOptions {
/**
* If true, the path portion of the pattern is ignored, and replaced with a
* wildcard. The `pattern` property is updated to reflect this.
*/
boolean ignorePath = false;
+
+ /**
+ * If true, the set of schemes this pattern can match is restricted to
+ * those accessible by WebExtensions.
+ */
+ boolean restrictSchemes = true;
};
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -269,25 +269,27 @@ const char* WILDCARD_SCHEMES[] = {"http"
/* static */ already_AddRefed<MatchPattern>
MatchPattern::Constructor(dom::GlobalObject& aGlobal,
const nsAString& aPattern,
const MatchPatternOptions& aOptions,
ErrorResult& aRv)
{
RefPtr<MatchPattern> pattern = new MatchPattern(aGlobal.GetAsSupports());
- pattern->Init(aGlobal.Context(), aPattern, aOptions.mIgnorePath, aRv);
+ pattern->Init(aGlobal.Context(), aPattern, aOptions.mIgnorePath,
+ aOptions.mRestrictSchemes, aRv);
if (aRv.Failed()) {
return nullptr;
}
return pattern.forget();
}
void
-MatchPattern::Init(JSContext* aCx, const nsAString& aPattern, bool aIgnorePath, ErrorResult& aRv)
+MatchPattern::Init(JSContext* aCx, const nsAString& aPattern, bool aIgnorePath,
+ bool aRestrictSchemes, ErrorResult& aRv)
{
RefPtr<AtomSet> permittedSchemes = AtomSet::Get<PERMITTED_SCHEMES>();
mPattern = aPattern;
if (aPattern.EqualsLiteral("<all_urls>")) {
mSchemes = permittedSchemes;
mMatchSubdomain = true;
@@ -305,57 +307,65 @@ MatchPattern::Init(JSContext* aCx, const
if (index <= 0) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
RefPtr<nsAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
if (scheme == nsGkAtoms::_asterisk) {
mSchemes = AtomSet::Get<WILDCARD_SCHEMES>();
- } else if (permittedSchemes->Contains(scheme) || scheme == nsGkAtoms::moz_extension) {
+ } else if (!aRestrictSchemes ||
+ permittedSchemes->Contains(scheme) ||
+ scheme == nsGkAtoms::moz_extension) {
mSchemes = new AtomSet({scheme});
} else {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
/***************************************************************************
* Host
***************************************************************************/
offset = index + 1;
tail.Rebind(aPattern, offset);
- if (!StringHead(tail, 2).EqualsLiteral("//")) {
- aRv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- offset += 2;
- tail.Rebind(aPattern, offset);
- index = tail.FindChar('/');
- if (index < 0) {
- index = tail.Length();
- }
-
- auto host = StringHead(tail, index);
- if (host.IsEmpty() && scheme != nsGkAtoms::file) {
- aRv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- offset += index;
- tail.Rebind(aPattern, offset);
-
- if (host.EqualsLiteral("*")) {
- mMatchSubdomain = true;
- } else if (StringHead(host, 2).EqualsLiteral("*.")) {
- mDomain = NS_ConvertUTF16toUTF8(Substring(host, 2));
+ if (scheme == nsGkAtoms::about) {
+ // about: URIs don't have hosts, so just treat the host as a wildcard and
+ // match on the path.
mMatchSubdomain = true;
} else {
- mDomain = NS_ConvertUTF16toUTF8(host);
+ if (!StringHead(tail, 2).EqualsLiteral("//")) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ offset += 2;
+ tail.Rebind(aPattern, offset);
+ index = tail.FindChar('/');
+ if (index < 0) {
+ index = tail.Length();
+ }
+
+ auto host = StringHead(tail, index);
+ if (host.IsEmpty() && scheme != nsGkAtoms::file) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ offset += index;
+ tail.Rebind(aPattern, offset);
+
+ if (host.EqualsLiteral("*")) {
+ mMatchSubdomain = true;
+ } else if (StringHead(host, 2).EqualsLiteral("*.")) {
+ mDomain = NS_ConvertUTF16toUTF8(Substring(host, 2));
+ mMatchSubdomain = true;
+ } else {
+ mDomain = NS_ConvertUTF16toUTF8(host);
+ }
}
/***************************************************************************
* Path
***************************************************************************/
if (aIgnorePath) {
mPattern.Truncate(offset);
mPattern.AppendLiteral("/*");
--- a/toolkit/components/extensions/MatchPattern.h
+++ b/toolkit/components/extensions/MatchPattern.h
@@ -252,17 +252,18 @@ class MatchPattern final : public nsISup
virtual JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
protected:
virtual ~MatchPattern() = default;
private:
explicit MatchPattern(nsISupports* aParent) : mParent(aParent) {}
- void Init(JSContext* aCx, const nsAString& aPattern, bool aIgnorePath, ErrorResult& aRv);
+ void Init(JSContext* aCx, const nsAString& aPattern, bool aIgnorePath,
+ bool aRestrictSchemes, ErrorResult& aRv);
bool SubsumesDomain(const MatchPattern& aPattern) const;
nsCOMPtr<nsISupports> mParent;
// The normalized match pattern string that this object represents.
nsString mPattern;
--- a/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
+++ b/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
@@ -1,45 +1,45 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(async function test_MatchPattern_matches() {
- function test(url, pattern, normalized = pattern) {
+ function test(url, pattern, normalized = pattern, options = {}) {
let uri = Services.io.newURI(url);
pattern = Array.concat(pattern);
normalized = Array.concat(normalized);
- let patterns = pattern.map(pat => new MatchPattern(pat));
+ let patterns = pattern.map(pat => new MatchPattern(pat, options));
- let set = new MatchPatternSet(pattern);
- let set2 = new MatchPatternSet(patterns);
+ let set = new MatchPatternSet(pattern, options);
+ let set2 = new MatchPatternSet(patterns, options);
deepEqual(set2.patterns, patterns, "Patterns in set should equal the input patterns");
equal(set.matches(uri), set2.matches(uri), "Single pattern and pattern set should return the same match");
for (let [i, pat] of patterns.entries()) {
equal(pat.pattern, normalized[i], "Pattern property should contain correct normalized pattern value");
}
if (patterns.length == 1) {
equal(patterns[0].matches(uri), set.matches(uri), "Single pattern and string set should return the same match");
}
return set.matches(uri);
}
- function pass({url, pattern, normalized}) {
- ok(test(url, pattern, normalized), `Expected match: ${JSON.stringify(pattern)}, ${url}`);
+ function pass({url, pattern, normalized, options}) {
+ ok(test(url, pattern, normalized, options), `Expected match: ${JSON.stringify(pattern)}, ${url}`);
}
- function fail({url, pattern, normalized}) {
- ok(!test(url, pattern, normalized), `Expected no match: ${JSON.stringify(pattern)}, ${url}`);
+ function fail({url, pattern, normalized, options}) {
+ ok(!test(url, pattern, normalized, options), `Expected no match: ${JSON.stringify(pattern)}, ${url}`);
}
function invalid({pattern}) {
Assert.throws(() => new MatchPattern(pattern), /.*/,
`Invalid pattern '${pattern}' should throw`);
Assert.throws(() => new MatchPatternSet([pattern]), /.*/,
`Invalid pattern '${pattern}' should throw`);
}
@@ -106,16 +106,27 @@ add_task(async function test_MatchPatter
// 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/"]});
// Match url with fragments.
pass({url: "http://mozilla.org/base#some-fragment", pattern: "http://mozilla.org/base"});
+
+ // Privileged matchers:
+ invalid({pattern: "about:foo"});
+ invalid({pattern: "resource://foo/*"});
+
+ pass({url: "about:foo", pattern: ["about:foo", "about:foo*"], options: {restrictSchemes: false}});
+ pass({url: "about:foo", pattern: ["about:foo*"], options: {restrictSchemes: false}});
+ pass({url: "about:foobar", pattern: ["about:foo*"], options: {restrictSchemes: false}});
+ pass({url: "resource://foo/bar", pattern: ["resource://foo/bar"], options: {restrictSchemes: false}});
+ fail({url: "resource://fog/bar", pattern: ["resource://foo/bar"], options: {restrictSchemes: false}});
+ fail({url: "about:foo", pattern: ["about:meh"], options: {restrictSchemes: false}});
});
add_task(async function test_MatchPattern_overlaps() {
function test(filter, hosts, optional) {
filter = Array.concat(filter);
hosts = Array.concat(hosts);
optional = Array.concat(optional);