| Index: lib/filterClasses.js |
| =================================================================== |
| --- a/lib/filterClasses.js |
| +++ b/lib/filterClasses.js |
| @@ -21,22 +21,42 @@ |
| * @fileOverview Definition of Filter class and its subclasses. |
| */ |
| const {filterNotifier} = require("./filterNotifier"); |
| const {extend} = require("./coreUtils"); |
| const {filterToRegExp} = require("./common"); |
| /** |
| + * Regular expression used to match the <code>||</code> prefix in an otherwise |
| + * literal pattern. |
| + * @type {RegExp} |
| + */ |
| +let doubleAnchorRegExp = new RegExp(filterToRegExp("||") + "$"); |
| + |
| +/** |
| * All known unique domain sources mapped to their parsed values. |
| * @type {Map.<string,Map.<string,boolean>>} |
| */ |
| let knownDomainMaps = new Map(); |
| /** |
| + * Checks whether the given pattern is a string of literal characters with no |
| + * wildcards or any other special characters. If the pattern is prefixed with a |
| + * <code>||</code> but otherwise contains no special characters, it is still |
| + * considered to be a literal pattern. |
| + * @param {string} pattern |
| + * @returns {boolean} |
| + */ |
| +function isLiteralPattern(pattern) |
| +{ |
| + return !/[*^|]/.test(pattern.replace(/^\|{2}/, "")); |
| +} |
| + |
| +/** |
| * Abstract base class for filters |
| * |
| * @param {string} text string representation of the filter |
| * @constructor |
| */ |
| function Filter(text) |
| { |
| this.text = text; |
| @@ -688,16 +708,19 @@ |
| // The filter is a regular expression - convert it immediately to |
| // catch syntax errors |
| let regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), |
| this.matchCase ? "" : "i"); |
| Object.defineProperty(this, "regexp", {value: regexp}); |
| } |
| else |
| { |
| + if (!this.matchCase && isLiteralPattern(regexpSource)) |
| + regexpSource = regexpSource.toLowerCase(); |
| + |
| // No need to convert this filter to regular expression yet, do it on demand |
| this.pattern = regexpSource; |
| } |
| } |
| exports.RegExpFilter = RegExpFilter; |
| RegExpFilter.prototype = extend(ActiveFilter, { |
| /** |
| @@ -719,20 +742,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 || !isLiteralPattern(pattern)) |
| + { |
| + 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 +813,47 @@ |
| * @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); |
| + }, |
| + |
| + /** |
| + * Checks whether the given URL matches this filter's pattern. |
| + * @param {string} location The URL to check. |
| + * @returns {boolean} <code>true</code> if the URL matches. |
| + */ |
| + 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)); |
| + |
| + // The "||" prefix requires that the text that follows does not start |
| + // with a forward slash. |
| + return index != -1 && location[index] != "/" && |
| + doubleAnchorRegExp.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*() |