| Index: lib/filterClasses.js |
| diff --git a/lib/filterClasses.js b/lib/filterClasses.js |
| index 1498ad8db2da7975ef3dce4916ef843d29d51420..f43dc92ba6a280ef749fb19116fc8c4fe615baaf 100644 |
| --- a/lib/filterClasses.js |
| +++ b/lib/filterClasses.js |
| @@ -97,7 +97,7 @@ Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w-]+(?:=[^,\s]+)?(?:,~?[\w-]+(?:=[^, |
| * Regular expression that options on a RegExp filter should match |
| * @type {RegExp} |
| */ |
| -Filter.optionsRegExp = /\$(~?[\w-]+(?:=[^,\s]+)?(?:,~?[\w-]+(?:=[^,\s]+)?)*)$/; |
| +Filter.optionsRegExp = /\$(~?[\w-]+(?:=[^,]+)?(?:,~?[\w-]+(?:=[^,]+)?)*)$/; |
| /** |
| * Creates a filter of correct type from its text representation - does the |
| @@ -173,21 +173,51 @@ Filter.normalize = function(text) |
| return text; |
| // Remove line breaks and such |
| - text = text.replace(/[^\S ]/g, ""); |
| + text = text.replace(/[^\S ]+/g, ""); |
| - if (/^\s*!/.test(text)) |
| - { |
| - // Don't remove spaces inside comments |
| + // Don't remove spaces inside comments |
| + if (/^ *!/.test(text)) |
|
Sebastian Noack
2018/03/14 20:59:20
It seems previously, comments were allowed do be p
Manish Jethani
2018/03/15 08:09:59
In the preceding code we're stripping out all non-
kzar
2018/03/15 10:26:24
(See this comment as well https://codereview.adblo
|
| return text.trim(); |
| - } |
| - else if (Filter.elemhideRegExp.test(text)) |
| + |
| + // Special treatment for element hiding filters, right side is allowed to |
| + // contain spaces |
| + if (Filter.elemhideRegExp.test(text)) |
| { |
| - // Special treatment for element hiding filters, right side is allowed to |
| - // contain spaces |
| let [, domain, separator, selector] = /^(.*?)(#@?#?)(.*)$/.exec(text); |
| - return domain.replace(/\s/g, "") + separator + selector.trim(); |
| + return domain.replace(/ +/g, "") + separator + selector.trim(); |
|
Sebastian Noack
2018/03/14 20:59:20
Similarly, here.
kzar
2018/03/15 10:26:25
See our responses above.
|
| + } |
| + |
| + // For most regexp filters we strip all whitespace. |
| + let strippedText = text.replace(/ +/g, ""); |
| + if (!strippedText.includes("$") || !/csp=/i.test(strippedText)) |
|
Manish Jethani
2018/03/15 08:23:17
Can we make this /\bcsp=/i instead?
kzar
2018/03/15 10:26:24
Done.
|
| + return strippedText; |
| + |
| + let optionsMatch = Filter.optionsRegExp.exec(strippedText); |
| + if (!optionsMatch) |
| + return strippedText; |
| + |
| + // But since the values of $csp filter options are allowed to contain single |
| + // (non trailing) spaces we have to be more careful if they might be present. |
| + let beforeOptions = strippedText.substring(0, optionsMatch.index); |
| + let optionsText = text; |
| + for (let i = beforeOptions.split("$").length; i > 0; i--) |
| + optionsText = optionsText.substr(optionsText.indexOf("$") + 1); |
|
Sebastian Noack
2018/03/14 20:59:20
It seems the same can be achieved with less code a
Manish Jethani
2018/03/15 08:09:59
This only looks for the last "$" in the string, wh
Manish Jethani
2018/03/15 09:11:06
Actually I still don't get why we have to do this.
kzar
2018/03/15 10:26:24
Then the options regexp might not match depending
kzar
2018/03/15 10:26:24
I can't tell if you're trolling or not at this poi
Manish Jethani
2018/03/15 10:42:42
Sorry, then I must have misunderstood. Are "$" sup
Manish Jethani
2018/03/15 10:42:42
Sorry, yeah, I see what you mean. It wouldn't work
Manish Jethani
2018/03/15 11:25:47
OK, I think I understand what you mean.
normali
kzar
2018/03/15 11:38:50
Well the part of the filter following a "$" is con
kzar
2018/03/15 11:43:52
Sure or we could "officially" not support filters
Manish Jethani
2018/03/15 12:00:11
But the two are not comparable, are they? If we do
Manish Jethani
2018/03/15 12:14:21
To sum this up, the normalize function looks good
kzar
2018/03/15 13:13:33
Thanks OK. (If you can see how to optimise the com
Manish Jethani
2018/03/15 13:47:33
Acknowledged.
|
| + |
| + let options = optionsText.split(","); |
| + for (let i = 0; i < options.length; i++) |
| + { |
| + let option = options[i]; |
| + let cspMatch = /^ *c *s *p *=/i.exec(option); |
| + if (cspMatch) |
| + { |
| + options[i] = option.substring(0, cspMatch[0].length).replace(/ +/g, "") + |
|
Manish Jethani
2018/03/15 08:23:17
Well option.substring(0, cspMatch[0].length) is th
kzar
2018/03/15 10:26:24
Done.
|
| + option.substr(cspMatch[0].length).trim().replace(/ +/g, " "); |
|
Manish Jethani
2018/03/15 08:23:17
Any reason why this uses substr instead of substri
kzar
2018/03/15 10:26:25
substr and substring are the same unless the secon
|
| + } |
| + else |
| + options[i] = option.replace(/ +/g, ""); |
| } |
| - return text.replace(/\s/g, ""); |
| + |
| + return beforeOptions + "$" + options.join(); |
| }; |
| /** |
| @@ -727,11 +757,12 @@ RegExpFilter.fromText = function(text) |
| let sitekeys = null; |
| let thirdParty = null; |
| let collapse = null; |
| + let csp = null; |
| let options; |
| let match = (text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null); |
| if (match) |
| { |
| - options = match[1].toUpperCase().split(","); |
| + options = match[1].split(","); |
| text = match.input.substr(0, match.index); |
| for (let option of options) |
| { |
| @@ -742,12 +773,20 @@ RegExpFilter.fromText = function(text) |
| value = option.substr(separatorIndex + 1); |
| option = option.substr(0, separatorIndex); |
| } |
| - option = option.replace(/-/, "_"); |
| + option = option.replace(/-/, "_").toUpperCase(); |
| if (option in RegExpFilter.typeMap) |
| { |
| if (contentType == null) |
| contentType = 0; |
| contentType |= RegExpFilter.typeMap[option]; |
| + |
| + if (option == "CSP" && typeof value != "undefined") |
| + { |
| + if (csp) |
| + csp.push(value); |
| + else |
| + csp = [value]; |
| + } |
| } |
| else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap) |
| { |
| @@ -760,7 +799,7 @@ RegExpFilter.fromText = function(text) |
| else if (option == "~MATCH_CASE") |
| matchCase = false; |
| else if (option == "DOMAIN" && typeof value != "undefined") |
| - domains = value; |
| + domains = value.toUpperCase(); |
| else if (option == "THIRD_PARTY") |
| thirdParty = true; |
| else if (option == "~THIRD_PARTY") |
| @@ -770,7 +809,7 @@ RegExpFilter.fromText = function(text) |
| else if (option == "~COLLAPSE") |
| collapse = false; |
| else if (option == "SITEKEY" && typeof value != "undefined") |
| - sitekeys = value; |
| + sitekeys = value.toUpperCase(); |
| else |
| return new InvalidFilter(origText, "filter_unknown_option"); |
| } |
| @@ -780,8 +819,19 @@ RegExpFilter.fromText = function(text) |
| { |
| if (blocking) |
| { |
| + if (csp) |
| + { |
| + csp = csp.join("; ").toLowerCase(); |
| + |
| + // Prevent filters from injecting report-uri or report-to directives |
| + // since they are a privacy concern. Regexp based upon reBadCSP[1]. |
| + // [1] - https://github.com/gorhill/uBlock/blob/67e06f53b4d73df6179f6d320553a55da4ead40e/src/js/static-net-filtering.js#L1362 |
| + if (/(;|^)\s*report-(to|uri)\b/.test(csp)) |
| + return new InvalidFilter(origText, "filter_invalid_csp"); |
| + } |
| + |
| return new BlockingFilter(origText, text, contentType, matchCase, domains, |
| - thirdParty, sitekeys, collapse); |
| + thirdParty, sitekeys, collapse, csp); |
| } |
| return new WhitelistFilter(origText, text, contentType, matchCase, domains, |
| thirdParty, sitekeys); |
| @@ -805,6 +855,7 @@ RegExpFilter.typeMap = { |
| DOCUMENT: 64, |
| WEBSOCKET: 128, |
| WEBRTC: 256, |
| + CSP: 512, |
| XBL: 1, |
| PING: 1024, |
| XMLHTTPREQUEST: 2048, |
| @@ -821,9 +872,10 @@ RegExpFilter.typeMap = { |
| GENERICHIDE: 0x80000000 |
| }; |
| -// DOCUMENT, ELEMHIDE, POPUP, GENERICHIDE and GENERICBLOCK options shouldn't |
| -// be there by default |
| -RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.DOCUMENT | |
| +// CSP, DOCUMENT, ELEMHIDE, POPUP, GENERICHIDE and GENERICBLOCK options |
| +// shouldn't be there by default |
| +RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.CSP | |
| + RegExpFilter.typeMap.DOCUMENT | |
| RegExpFilter.typeMap.ELEMHIDE | |
| RegExpFilter.typeMap.POPUP | |
| RegExpFilter.typeMap.GENERICHIDE | |
| @@ -840,16 +892,19 @@ RegExpFilter.prototype.contentType &= ~(RegExpFilter.typeMap.DOCUMENT | |
| * @param {string} sitekeys see RegExpFilter() |
| * @param {boolean} collapse |
| * defines whether the filter should collapse blocked content, can be null |
| + * @param {string} [csp] |
| + * Content Security Policy to inject when the filter matches |
| * @constructor |
| * @augments RegExpFilter |
| */ |
| function BlockingFilter(text, regexpSource, contentType, matchCase, domains, |
| - thirdParty, sitekeys, collapse) |
| + thirdParty, sitekeys, collapse, csp) |
| { |
| RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, |
| thirdParty, sitekeys); |
| this.collapse = collapse; |
| + this.csp = csp; |
| } |
| exports.BlockingFilter = BlockingFilter; |
| @@ -861,7 +916,13 @@ BlockingFilter.prototype = extend(RegExpFilter, { |
| * Can be null (use the global preference). |
| * @type {boolean} |
| */ |
| - collapse: null |
| + collapse: null, |
| + |
| + /** |
| + * Content Security Policy to inject for matching requests. |
| + * @type {string} |
| + */ |
| + csp: null |
| }); |
| /** |