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
--- 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);