| Index: lib/filterClasses.js |
| =================================================================== |
| --- a/lib/filterClasses.js |
| +++ b/lib/filterClasses.js |
| @@ -20,16 +20,18 @@ |
| /** |
| * @fileOverview Definition of Filter class and its subclasses. |
| */ |
| const {filterNotifier} = require("./filterNotifier"); |
| const {extend} = require("./coreUtils"); |
| const {filterToRegExp} = require("./common"); |
| +let tripleAnchorRegExp = new RegExp(filterToRegExp("|||")); |
| + |
| /** |
| * All known unique domain sources mapped to their parsed values. |
| * @type {Map.<string,Map.<string,boolean>>} |
| */ |
| let knownDomainMaps = new Map(); |
| /** |
| * Abstract base class for filters |
| @@ -659,21 +661,22 @@ |
| * letters |
| * @param {string} [domains] |
| * Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com" |
| * @param {boolean} [thirdParty] |
| * Defines whether the filter should apply to third-party or first-party |
| * content only |
| * @param {string} [sitekeys] |
| * Public keys of websites that this filter should apply to |
| - * @constructor |
| + * @param {?string} [rewrite] |
| + * The (optional) rule specifying how to rewrite the URL. |
| * @augments ActiveFilter |
| */ |
| function RegExpFilter(text, regexpSource, contentType, matchCase, domains, |
| - thirdParty, sitekeys) |
| + thirdParty, sitekeys, rewrite) |
| { |
| ActiveFilter.call(this, text, domains); |
| if (contentType != null) |
| this.contentType = contentType; |
| if (matchCase) |
| this.matchCase = matchCase; |
| if (thirdParty != null) |
| @@ -690,16 +693,22 @@ |
| let regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), |
| this.matchCase ? "" : "i"); |
| Object.defineProperty(this, "regexp", {value: regexp}); |
| } |
| else |
| { |
| // No need to convert this filter to regular expression yet, do it on demand |
| this.pattern = regexpSource; |
| + |
| + if (!this.matchCase && rewrite == null && |
| + !/[*^|]/.test(this.pattern.replace(/^\|{2}/, ""))) |
| + { |
| + this.pattern = this.pattern.toLowerCase(); |
| + } |
| } |
| } |
| exports.RegExpFilter = RegExpFilter; |
| RegExpFilter.prototype = extend(ActiveFilter, { |
| /** |
| * Number of filters contained, will always be 1 (required to |
| * optimize {@link Matcher}). |
| @@ -719,20 +728,27 @@ |
| */ |
| pattern: null, |
| /** |
| * Regular expression to be used when testing against this filter |
| * @type {RegExp} |
| */ |
| get regexp() |
| { |
| - let source = filterToRegExp(this.pattern, this.rewrite != null); |
| - let regexp = new RegExp(source, this.matchCase ? "" : "i"); |
| - Object.defineProperty(this, "regexp", {value: regexp}); |
| - return regexp; |
| + let value = null; |
| + |
| + let {pattern, rewrite} = this; |
| + if (rewrite != null || /[*^|]/.test(pattern.replace(/^\|{2}/, ""))) |
| + { |
| + value = new RegExp(filterToRegExp(pattern, rewrite != null), |
| + this.matchCase ? "" : "i"); |
| + } |
| + |
| + Object.defineProperty(this, "regexp", {value}); |
| + return value; |
| }, |
| /** |
| * Content types the filter applies to, combination of values from |
| * RegExpFilter.typeMap |
| * @type {number} |
| */ |
| contentType: 0x7FFFFFFF, |
| /** |
| @@ -783,17 +799,39 @@ |
| * @param {string} [sitekey] public key provided by the document |
| * @return {boolean} true in case of a match |
| */ |
| matches(location, typeMask, docDomain, thirdParty, sitekey) |
| { |
| return (this.contentType & typeMask) != 0 && |
| (this.thirdParty == null || this.thirdParty == thirdParty) && |
| this.isActiveOnDomain(docDomain, sitekey) && |
| - this.regexp.test(location); |
| + this.matchesLocation(location); |
| + }, |
| + |
| + matchesLocation(location) |
| + { |
| + let {regexp} = this; |
| + |
| + if (regexp) |
| + return regexp.test(location); |
| + |
| + if (!this.matchCase) |
| + location = location.toLowerCase(); |
| + |
| + let {pattern} = this; |
| + |
| + if (pattern[0] == "|" && pattern[1] == "|") |
| + { |
| + let index = location.indexOf(pattern.substring(2)); |
| + return index != -1 && location[index] != "/" && |
| + tripleAnchorRegExp.test(location.substring(0, index)); |
| + } |
| + |
| + return location.includes(pattern); |
| } |
| }); |
| /** |
| * Yields the filter itself (required to optimize {@link Matcher}). |
| * @yields {RegExpFilter} |
| */ |
| RegExpFilter.prototype[Symbol.iterator] = function*() |
| @@ -988,17 +1026,17 @@ |
| * BlockingFilter.prototype.rewrite. |
| * @constructor |
| * @augments RegExpFilter |
| */ |
| function BlockingFilter(text, regexpSource, contentType, matchCase, domains, |
| thirdParty, sitekeys, collapse, csp, rewrite) |
| { |
| RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, |
| - thirdParty, sitekeys); |
| + thirdParty, sitekeys, rewrite); |
| if (collapse != null) |
| this.collapse = collapse; |
| if (csp != null) |
| this.csp = csp; |
| if (rewrite != null) |