| Index: lib/filterClasses.js |
| =================================================================== |
| --- a/lib/filterClasses.js |
| +++ b/lib/filterClasses.js |
| @@ -382,32 +382,63 @@ |
| /** |
| * Checks whether this filter is active on a domain. |
| + * @param {String} docDomain domain name of the document that loads the URL |
| + * @param {String} sitekey (optional) public key provided by the document |
| + * @return {Boolean} true in case of the filter being active |
| */ |
| - isActiveOnDomain: function(/**String*/ docDomain) /**Boolean*/ |
| + isActiveOnDomain: function(docDomain, sitekey) |
| { |
| - // If no domains are set the rule matches everywhere |
| - if (!this.domains) |
| + // If no domains or sitekeys are set the rule matches everywhere |
| + if (!this.domains && !this.sitekeys) |
| return true; |
| - // If the document has no host name, match only if the filter isn't restricted to specific domains |
| - if (!docDomain) |
| - return this.domains[""]; |
| + let activeDomain = true; |
| + if (this.domains) |
| + { |
| + if (!docDomain) |
| + // If the document has no host name, match only if the filter isn't restricted to specific domains |
| + activeDomain = this.domains[""]; |
| + else |
| + { |
| + if (this.ignoreTrailingDot) |
| + docDomain = docDomain.replace(/\.+$/, ""); |
| + docDomain = docDomain.toUpperCase(); |
| - if (this.ignoreTrailingDot) |
| - docDomain = docDomain.replace(/\.+$/, ""); |
| - docDomain = docDomain.toUpperCase(); |
| + let domain = ""; |
| + while (true) |
| + { |
| + if (docDomain in this.domains) |
| + { |
| + domain = docDomain; |
| + break; |
| + } |
| - while (true) |
| + let nextDot = docDomain.indexOf("."); |
| + if (nextDot < 0) |
| + break; |
| + docDomain = docDomain.substr(nextDot + 1); |
| + } |
| + activeDomain = this.domains[domain]; |
| + } |
| + } |
| + |
| + let activeSitekey = true; |
| + if (this.sitekeys) |
| { |
| - if (docDomain in this.domains) |
| - return this.domains[docDomain]; |
| - |
| - let nextDot = docDomain.indexOf("."); |
| - if (nextDot < 0) |
| - break; |
| - docDomain = docDomain.substr(nextDot + 1); |
| + if (!sitekey) |
| + // If the document has no sitekey, match only if the filter isn't restricted by sitekeys |
| + activeSitekey = this.sitekeys[""]; |
| + else |
| + { |
| + let [key, signature] = sitekey.split("_", 2); |
| + let formattedKey = key.replace(/=/g, "").toUpperCase(); |
| + if (formattedKey in this.sitekeys) |
| + activeSitekey = this.sitekeys[formattedKey]; |
| + else |
| + activeSitekey = this.sitekeys[""]; |
| + } |
| } |
| - return this.domains[""]; |
| + return activeDomain && activeSitekey; |
| }, |
| /** |
| @@ -455,12 +486,13 @@ |
| * @param {Boolean} matchCase (optional) Defines whether the filter should distinguish between lower and upper case letters |
| * @param {String} domains (optional) Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com" |
| * @param {Boolean} thirdParty (optional) Defines whether the filter should apply to third-party or first-party content only |
| + * @param {String} sitekeys (optional) Public keys of websites that this filter should apply to, e.g. "foo|bar|~baz" |
| * @constructor |
| * @augments ActiveFilter |
| */ |
| -function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty) |
| +function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) |
| { |
| - ActiveFilter.call(this, text, domains); |
| + ActiveFilter.call(this, text, domains, sitekeys); |
| if (contentType != null) |
| this.contentType = contentType; |
| @@ -468,6 +500,8 @@ |
| this.matchCase = matchCase; |
| if (thirdParty != null) |
| this.thirdParty = thirdParty; |
| + if (sitekeys != null) |
| + this.sitekeySource = sitekeys; |
| if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpSource.length - 1] == "/") |
| { |
| @@ -549,19 +583,80 @@ |
| thirdParty: null, |
| /** |
| + * String that the sitekey property should be generated from |
| + * @type String |
| + */ |
| + sitekeySource: null, |
| + |
| + /** |
| + * Map containing public keys of websites that this filter should apply to |
| + * @type Object |
| + */ |
| + get sitekeys() |
| + { |
| + let sitekeys = null; |
| + |
| + if (this.sitekeySource) |
| + { |
| + let source = this.sitekeySource; |
| + let list = source.split("|"); |
| + if (list.length == 1 && list[0][0] != "~") |
| + { |
| + // Fast track for the common one-sitekey scenario |
| + sitekeys = {__proto__: null, "": false}; |
| + sitekeys[list[0]] = true; |
| + } |
| + else |
| + { |
| + let hasIncludes = false; |
| + for (let i = 0; i < list.length; i++) |
| + { |
| + let sitekey = list[i]; |
| + if (sitekey == "") |
| + continue; |
| + |
| + let include; |
| + if (sitekey[0] == "~") |
| + { |
| + include = false; |
| + sitekey = sitekey.substr(1); |
| + } |
| + else |
| + { |
| + include = true; |
| + hasIncludes = true; |
| + } |
| + |
| + if (!sitekeys) |
| + sitekeys = Object.create(null); |
| + |
| + sitekeys[sitekey] = include; |
| + } |
| + sitekeys[""] = !hasIncludes; |
| + } |
| + |
| + this.sitekeySource = null; |
| + } |
| + |
| + Object.defineProperty(this, "sitekeys", {value: sitekeys, enumerable: true}); |
| + return this.sitekeys; |
| + }, |
| + |
| + /** |
| * Tests whether the URL matches this filter |
| * @param {String} location URL to be tested |
| * @param {String} contentType content type identifier of the URL |
| * @param {String} docDomain domain name of the document that loads the URL |
| * @param {Boolean} thirdParty should be true if the URL is a third-party request |
| + * @param {String} sitekey public key provided by the document |
| * @return {Boolean} true in case of a match |
| */ |
| - matches: function(location, contentType, docDomain, thirdParty) |
| + matches: function(location, contentType, docDomain, thirdParty, sitekey) |
| { |
| if (this.regexp.test(location) && |
| (RegExpFilter.typeMap[contentType] & this.contentType) != 0 && |
| (this.thirdParty == null || this.thirdParty == thirdParty) && |
| - this.isActiveOnDomain(docDomain)) |
| + this.isActiveOnDomain(docDomain, sitekey)) |
| { |
| return true; |
| } |
| @@ -593,7 +688,7 @@ |
| let contentType = null; |
| let matchCase = null; |
| let domains = null; |
| - let siteKeys = null; |
| + let sitekeys = null; |
| let thirdParty = null; |
| let collapse = null; |
| let options; |
| @@ -639,7 +734,7 @@ |
| else if (option == "~COLLAPSE") |
| collapse = false; |
| else if (option == "SITEKEY" && typeof value != "undefined") |
| - siteKeys = value.split(/\|/); |
| + sitekeys = value; |
| else |
| return new InvalidFilter(origText, "Unknown option " + option.toLowerCase()); |
| } |
| @@ -653,15 +748,13 @@ |
| contentType = RegExpFilter.prototype.contentType; |
| contentType &= ~RegExpFilter.typeMap.DOCUMENT; |
| } |
| - if (!blocking && siteKeys) |
| - contentType = RegExpFilter.typeMap.DOCUMENT; |
| try |
| { |
| if (blocking) |
| - return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, collapse); |
| + return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse); |
| else |
| - return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, siteKeys); |
| + return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys); |
| } |
| catch (e) |
| { |
| @@ -705,13 +798,14 @@ |
| * @param {Boolean} matchCase see RegExpFilter() |
| * @param {String} domains see RegExpFilter() |
| * @param {Boolean} thirdParty see RegExpFilter() |
| + * @param {String} sitekeys see RegExpFilter() |
| * @param {Boolean} collapse defines whether the filter should collapse blocked content, can be null |
| * @constructor |
| * @augments RegExpFilter |
| */ |
| -function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, collapse) |
| +function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys, collapse) |
| { |
| - RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty); |
| + RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys); |
| this.collapse = collapse; |
| } |
| @@ -736,28 +830,19 @@ |
| * @param {Boolean} matchCase see RegExpFilter() |
| * @param {String} domains see RegExpFilter() |
| * @param {Boolean} thirdParty see RegExpFilter() |
| - * @param {String[]} siteKeys public keys of websites that this filter should apply to |
| + * @param {String} sitekeys see RegExpFilter() |
| * @constructor |
| * @augments RegExpFilter |
| */ |
| -function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys) |
| +function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) |
| { |
| - RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty); |
| - |
| - if (siteKeys != null) |
| - this.siteKeys = siteKeys; |
| + RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys); |
| } |
| exports.WhitelistFilter = WhitelistFilter; |
| WhitelistFilter.prototype = |
| { |
| - __proto__: RegExpFilter.prototype, |
| - |
| - /** |
| - * List of public keys of websites that this filter should apply to |
| - * @type String[] |
| - */ |
| - siteKeys: null |
| + __proto__: RegExpFilter.prototype |
| } |
| /** |