Bug 1280370 - Properly parse MatchPattern for schemes with no authority draft
authorRob Wu <rob@robwu.nl>
Tue, 24 Jul 2018 16:35:45 +0200
changeset 827704 6546660bd7b6c4b23fdbb53a11c656d1aed0cbd3
parent 827663 3f03af6235584023e6ee4207af62a792f9efd05a
child 827705 fdf039bc1cc469e958e74d85062c02582198822c
push id118573
push userbmo:rob@robwu.nl
push dateWed, 08 Aug 2018 21:39:11 +0000
bugs1280370
milestone63.0a1
Bug 1280370 - Properly parse MatchPattern for schemes with no authority I found these schemes by enumerating all registered nsIProtocolHandler and checking whether they require a "//". The list of schemes in HOST_LOCATOR_SCHEMES only includes schemes that are known to Firefox. Any other scheme is parsed as if the separator is ":". For example, NetUtil.newURI("unknown-scheme://host/path") has its pathQueryRef member set to "//host/path". For the purpose of matching in MatchPattern, this unknown scheme has therefore no host and only a path. MozReview-Commit-ID: KGNRXGcZTZx
toolkit/components/extensions/MatchPattern.cpp
toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -260,16 +260,19 @@ CookieInfo::RawHost() const
 
 
 /*****************************************************************************
  * MatchPattern
  *****************************************************************************/
 
 const char* PERMITTED_SCHEMES[] = {"http", "https", "ws", "wss", "file", "ftp", "data", nullptr};
 
+// Known schemes that are followed by "://" instead of ":".
+const char* HOST_LOCATOR_SCHEMES[] = {"http", "https", "ws", "wss", "file", "ftp", "moz-extension", "chrome", "resource", "moz", "moz-icon", "moz-gio", nullptr};
+
 const char* WILDCARD_SCHEMES[] = {"http", "https", "ws", "wss", nullptr};
 
 /* static */ already_AddRefed<MatchPattern>
 MatchPattern::Constructor(dom::GlobalObject& aGlobal,
                           const nsAString& aPattern,
                           const MatchPatternOptions& aOptions,
                           ErrorResult& aRv)
 {
@@ -305,35 +308,39 @@ MatchPattern::Init(JSContext* aCx, const
    ***************************************************************************/
   int32_t index = aPattern.FindChar(':');
   if (index <= 0) {
     aRv.Throw(NS_ERROR_INVALID_ARG);
     return;
   }
 
   RefPtr<nsAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
+  bool requireHostLocatorScheme = true;
   if (scheme == nsGkAtoms::_asterisk) {
     mSchemes = AtomSet::Get<WILDCARD_SCHEMES>();
   } else if (!aRestrictSchemes ||
              permittedSchemes->Contains(scheme) ||
              scheme == nsGkAtoms::moz_extension) {
     mSchemes = new AtomSet({scheme});
+    RefPtr<AtomSet> hostLocatorSchemes = AtomSet::Get<HOST_LOCATOR_SCHEMES>();
+    requireHostLocatorScheme = hostLocatorSchemes->Contains(scheme);
   } else {
     aRv.Throw(NS_ERROR_INVALID_ARG);
     return;
   }
 
   /***************************************************************************
    * Host
    ***************************************************************************/
   offset = index + 1;
   tail.Rebind(aPattern, offset);
 
-  if (scheme == nsGkAtoms::about || scheme == nsGkAtoms::data) {
-    // about: and data: URIs don't have hosts, so just match on the path.
+  if (!requireHostLocatorScheme) {
+    // Unrecognized schemes and some schemes such as about: and data: URIs
+    // don't have hosts, so just match on the path.
     // And so, ignorePath doesn't make sense for these matchers.
     aIgnorePath = false;
   } else {
     if (!StringHead(tail, 2).EqualsLiteral("//")) {
       aRv.Throw(NS_ERROR_INVALID_ARG);
       return;
     }
 
--- a/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
+++ b/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js
@@ -135,16 +135,34 @@ add_task(async function test_MatchPatter
   pass({url: "about:reader?http://e.com/", pattern: ["about:reader*"], options: {ignorePath: true, restrictSchemes: false}});
   pass({url: "data:,", pattern: ["data:,*"], options: {ignorePath: true}});
 
   // Matchers for schems without host should still match even if the explicit (host) flag is set.
   pass({url: "about:reader?explicit", pattern: ["about:reader*"], options: {restrictSchemes: false}, explicit: true});
   pass({url: "about:reader?explicit", pattern: ["about:reader?explicit"], options: {restrictSchemes: false}, explicit: true});
   pass({url: "data:,explicit", pattern: ["data:,explicit"], explicit: true});
   pass({url: "data:,explicit", pattern: ["data:,*"], explicit: true});
+
+  // Matchers without "//" separator in the pattern.
+  pass({url: "data:text/plain;charset=utf-8,foo", pattern: ["data:*"]});
+  pass({url: "about:blank", pattern: ["about:*"], options: {restrictSchemes: false}});
+  pass({url: "view-source:https://example.com", pattern: ["view-source:*"], options: {restrictSchemes: false}});
+  invalid({pattern: ["chrome:*"], options: {restrictSchemes: false}});
+  invalid({pattern: "http:*"});
+
+  // Matchers for unrecognized schemes.
+  invalid({pattern: "unknown-scheme:*"});
+  pass({url: "unknown-scheme:foo", pattern: ["unknown-scheme:foo"], options: {restrictSchemes: false}});
+  pass({url: "unknown-scheme:foo", pattern: ["unknown-scheme:*"], options: {restrictSchemes: false}});
+  pass({url: "unknown-scheme://foo", pattern: ["unknown-scheme://foo"], options: {restrictSchemes: false}});
+  pass({url: "unknown-scheme://foo", pattern: ["unknown-scheme://*"], options: {restrictSchemes: false}});
+  pass({url: "unknown-scheme://foo", pattern: ["unknown-scheme:*"], options: {restrictSchemes: false}});
+  fail({url: "unknown-scheme://foo", pattern: ["unknown-scheme:foo"], options: {restrictSchemes: false}});
+  fail({url: "unknown-scheme:foo", pattern: ["unknown-scheme://foo"], options: {restrictSchemes: false}});
+  fail({url: "unknown-scheme:foo", pattern: ["unknown-scheme://*"], 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);