Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: lib/filterClasses.js

Issue 29680689: [$csp2 adblockpluscore] Issue 6329 - Add the CSP filter type (Closed)
Patch Set: Fully normalise whitespace, avoid strict equality Created March 12, 2018, 1:35 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | test/filterClasses.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/filterClasses.js
diff --git a/lib/filterClasses.js b/lib/filterClasses.js
index 1498ad8db2da7975ef3dce4916ef843d29d51420..67cb41ccea56eb2f03b837d28e7fdbd0f73f5de1 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
@@ -175,19 +175,60 @@ Filter.normalize = function(text)
// Remove line breaks and such
text = text.replace(/[^\S ]/g, "");
+ // Don't remove spaces inside comments
if (/^\s*!/.test(text))
- {
- // Don't remove spaces inside comments
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 text.replace(/\s/g, "");
+
+ // For most regexp filters we strip all whitespace, but the values of $csp
Manish Jethani 2018/03/12 18:53:59 So this doesn't work for the following cases: 1.
Sebastian Noack 2018/03/12 23:19:01 Splitting the options list into an array (for ever
Manish Jethani 2018/03/13 06:59:30 You mean in terms of memory consumption? I ran a t
Manish Jethani 2018/03/13 07:35:00 OK, so I inlined that bit, now this seems to perfo
kzar 2018/03/14 13:54:37 Well spotted, I've added some unit tests for those
Sebastian Noack 2018/03/14 20:59:20 Any reason, why this suggestion was ignored, witho
kzar 2018/03/15 10:26:24 I ignored that since it assumed the first '$' was
Manish Jethani 2018/03/15 10:42:41 I think Sebastian is referring to the rest of the
kzar 2018/03/15 11:38:50 So instead of splitting the options string by ","
Manish Jethani 2018/03/15 12:00:11 Yes, it just turned out to be faster.
Sebastian Noack 2018/03/15 17:25:41 I guess, if it's only in the code path hit for $cs
+ // filter options are allowed to contain single (non trailing) spaces.
+ let strippedText = text.replace(/\s/g, "");
Manish Jethani 2018/03/12 16:34:53 Since we have already stripped out all non-space w
kzar 2018/03/14 13:54:38 I think you're right, Done.
+ if (!/csp=/i.test(strippedText))
+ return strippedText;
+
+ let optionsMatch = Filter.optionsRegExp.exec(strippedText);
Manish Jethani 2018/03/12 16:34:53 We can just look for "$" here and if none is prese
kzar 2018/03/14 13:54:38 No, that's not good enough unfortunately. There ca
Manish Jethani 2018/03/14 18:10:49 OK, but I wonder if we shouldn't just look for a "
+ if (!optionsMatch)
+ return strippedText;
+
+ // We know where the options part starts in the filter text that's stripped
+ // of whitespace, next we must find the corresponding position in the original
+ // filter text.
+ let optionsPosition = 0;
Manish Jethani 2018/03/12 16:34:53 This part is not being used at all.
kzar 2018/03/14 13:54:38 Whoops, you're right. I've fixed that now.
+ let offset = 0;
+ while (offset > -1)
+ {
+ optionsPosition = text.substring(optionsPosition).indexOf("$");
+ offset = strippedText.substring(offset, optionsPosition).indexOf("$");
+ }
+
+ // Finally with that we can generally strip whitespace, being careful to not
+ // to for $csp filter values.
+ let parts = [];
+ let position = 0;
+ let cspRegexp = /(c\s*s\s*p\s*=)([^,]+)/ig;
+ let cspMatch;
+ while (cspMatch = cspRegexp.exec(text))
+ {
+ parts.push(
Manish Jethani 2018/03/12 16:34:53 We've already stripped whitespace once, now we're
kzar 2018/03/14 13:54:37 No because we're expecting thousands of non-csp fi
+ text.substring(position, cspMatch.index + cspMatch[1].length)
+ .replace(/\s/g, "")
+ );
+ parts.push(
+ text.substr(cspMatch.index + cspMatch[1].length, cspMatch[2].length)
+ .replace(/\s+/g, " ")
+ .trim()
+ );
+ position = cspMatch.index + cspMatch[0].length;
+ }
+ parts.push(text.substr(position).replace(/\s/g, ""));
+ return parts.join("");
};
/**
@@ -727,11 +768,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)
{
@@ -740,14 +782,31 @@ RegExpFilter.fromText = function(text)
if (separatorIndex >= 0)
{
value = option.substr(separatorIndex + 1);
- option = option.substr(0, separatorIndex);
+ option = option.substr(0, separatorIndex).toUpperCase();
+
+ if (option == "CSP")
+ value = value.trim();
+ else
+ value = value.replace(/\s/g, "");
}
+ else
+ option = option.toUpperCase();
+
option = option.replace(/-/, "_");
+
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 +819,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,18 +829,30 @@ 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");
}
}
+ text = text.replace(/\s/g, "");
try
{
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 +876,7 @@ RegExpFilter.typeMap = {
DOCUMENT: 64,
WEBSOCKET: 128,
WEBRTC: 256,
+ CSP: 512,
XBL: 1,
PING: 1024,
XMLHTTPREQUEST: 2048,
@@ -821,9 +893,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 +913,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 +937,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
});
/**
« no previous file with comments | « no previous file | test/filterClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld