Bug 1456485: Part 1 - Support unrestricted matching in MatchPattern. r=zombie
authorKris Maglione <maglione.k@gmail.com>
Wed, 18 Apr 2018 14:02:05 -0700
changeset 419432 8c259ed0e0d1cecf8fdc6092b8714e880e3169d5
parent 419431 96c45411c05187203a8eeca35b1b4bcf3503240c
child 419433 364fa52d097e335146f94cbc98238c77b7789d61
push id34037
push userdluca@mozilla.com
push dateWed, 23 May 2018 09:51:55 +0000
treeherdermozilla-central@d36cd8bdbc5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszombie
bugs1456485, 1425104
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1456485: Part 1 - Support unrestricted matching in MatchPattern. r=zombie This is grafted from the first part of bug 1425104. MozReview-Commit-ID: 4aW2w81LDTI
dom/chrome-webidl/MatchPattern.webidl
toolkit/components/extensions/MatchPattern.cpp
toolkit/components/extensions/MatchPattern.h
toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
--- 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);