/* 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; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); this.EXPORTED_SYMBOLS = ["MatchPattern", "MatchGlobs"]; /* globals MatchPattern, MatchGlobs */ const PERMITTED_SCHEMES = ["http", "https", "file", "ftp", "app", "data"]; const PERMITTED_SCHEMES_REGEXP = PERMITTED_SCHEMES.join("|"); // 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 == "") { this.schemes = PERMITTED_SCHEMES; this.host = "*"; this.path = new RegExp(".*"); } else if (!pat) { this.schemes = []; } else { let re = new RegExp(`^(${PERMITTED_SCHEMES_REGEXP}|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$`); let match = re.exec(pat); if (!match) { Cu.reportError(`Invalid match pattern: '${pat}'`); this.schemes = []; return; } if (match[1] == "*") { this.schemes = ["http", "https"]; } else { this.schemes = [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.schemes[0] != "file") { Cu.reportError(`Invalid match pattern: '${pat}'`); this.schemes = []; return; } } } SingleMatchPattern.prototype = { matches(uri, ignorePath = false) { if (!this.schemes.includes(uri.scheme)) { 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 (!ignorePath && !this.path.test(uri.path)) { return false; } return true; }, }; this.MatchPattern = function(pat) { this.pat = pat; if (!pat) { this.matchers = []; } else if (pat instanceof String || typeof(pat) == "string") { this.matchers = [new SingleMatchPattern(pat)]; } else { this.matchers = pat.map(p => 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; }, matchesIgnoringPath(uri) { for (let matcher of this.matchers) { if (matcher.matches(uri, true)) { return true; } } return false; }, // Checks that this match pattern grants access to read the given // cookie. |cookie| should be an |nsICookie2| instance. matchesCookie(cookie) { // First check for simple matches. let secureURI = NetUtil.newURI(`https://${cookie.rawHost}/`); if (this.matchesIgnoringPath(secureURI)) { return true; } let plainURI = NetUtil.newURI(`http://${cookie.rawHost}/`); if (!cookie.isSecure && this.matchesIgnoringPath(plainURI)) { return true; } if (!cookie.isDomain) { return false; } // Things get tricker for domain cookies. The extension needs to be able // to read any cookies that could be read any host it has permissions // for. This means that our normal host matching checks won't work, // since the pattern "*://*.foo.example.com/" doesn't match ".example.com", // but it does match "bar.foo.example.com", which can read cookies // with the domain ".example.com". // // So, instead, we need to manually check our filters, and accept any // with hosts that end with our cookie's host. let {host, isSecure} = cookie; for (let matcher of this.matchers) { let schemes = matcher.schemes; if (schemes.includes("https") || (!isSecure && schemes.includes("http"))) { if (matcher.host.endsWith(host)) { return true; } } } return false; }, serialize() { return this.pat; }, }; // Globs can match everything. Be careful, this DOES NOT filter by allowed schemes! this.MatchGlobs = function(globs) { this.original = globs; if (globs) { this.regexps = Array.from(globs, (glob) => globToRegexp(glob, true)); } else { this.regexps = []; } }; MatchGlobs.prototype = { matches(str) { return this.regexps.some(regexp => regexp.test(str)); }, serialize() { return this.original; }, };