mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Better detect invalid cosmetic filters (revised)
Related commit:
- 19298fecf3
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/2170
This commit is contained in:
parent
1dd37c8181
commit
97befd116b
1 changed files with 74 additions and 34 deletions
|
@ -19,7 +19,7 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* globals CSSStyleSheet, document */
|
/* globals document */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -1332,27 +1332,66 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
[ 'matches-css-after', ':matches-css-after' ],
|
[ 'matches-css-after', ':matches-css-after' ],
|
||||||
[ 'matches-css-before', ':matches-css-before' ],
|
[ 'matches-css-before', ':matches-css-before' ],
|
||||||
]);
|
]);
|
||||||
this.reSimpleSelector = /^[#.]?[A-Za-z_][\w-]*$/;
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#browser_compatibility
|
// Use a regex for most common CSS selectors known to be valid in any
|
||||||
// Firefox does not support constructor for CSSStyleSheet
|
// context.
|
||||||
this.stylesheet = (( ) => {
|
const cssIdentifier = '[A-Za-z_][\\w-]*';
|
||||||
if ( typeof document !== 'object' ) { return null; }
|
const cssClassOrId = `[.#]${cssIdentifier}`;
|
||||||
if ( document instanceof Object === false ) { return null; }
|
const cssAttribute = `\\[${cssIdentifier}[*^$]?="[^"\\]\\\\]+"\\]`;
|
||||||
|
const cssSimple =
|
||||||
|
'(?:' +
|
||||||
|
`${cssIdentifier}(?:${cssClassOrId})*(?:${cssAttribute})*` + '|' +
|
||||||
|
`${cssClassOrId}(?:${cssClassOrId})*(?:${cssAttribute})*` + '|' +
|
||||||
|
`${cssAttribute}(?:${cssAttribute})*` +
|
||||||
|
')';
|
||||||
|
const cssCombinator = '(?:\\s+|\\s*[>+~]\\s*)';
|
||||||
|
this.reCommonSelector = new RegExp(
|
||||||
|
`^${cssSimple}(?:${cssCombinator}${cssSimple})*$`
|
||||||
|
);
|
||||||
|
// Resulting regex literal:
|
||||||
|
// ^(?:[A-Za-z_][\w-]*(?:[.#][A-Za-z_][\w-]*)*(?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*|[.#][A-Za-z_][\w-]*(?:[.#][A-Za-z_][\w-]*)*(?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*|\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\](?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*)(?:(?:\s+|\s*[>+~]\s*)(?:[A-Za-z_][\w-]*(?:[.#][A-Za-z_][\w-]*)*(?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*|[.#][A-Za-z_][\w-]*(?:[.#][A-Za-z_][\w-]*)*(?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*|\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\](?:\[[A-Za-z_][\w-]*[*^$]?="[^"\]\\]+"\])*))*$
|
||||||
|
|
||||||
|
// We use an actual stylesheet to validate uncommon CSS selectors
|
||||||
|
// which can be used in a CSS declaration.
|
||||||
|
this.sheetValidatorRule = null;
|
||||||
|
(( ) => {
|
||||||
|
if ( typeof document !== 'object' ) { return; }
|
||||||
|
if ( document === null ) { return; }
|
||||||
try {
|
try {
|
||||||
return new CSSStyleSheet();
|
const styleElement = document.createElement('style');
|
||||||
|
document.body.append(styleElement);
|
||||||
|
const sheet = styleElement.sheet;
|
||||||
|
styleElement.remove();
|
||||||
|
sheet.insertRule('_z{color:red}');
|
||||||
|
const rule = sheet.cssRules[0];
|
||||||
|
this.sheetValidatorRule = rule;
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
const style = document.createElement('style');
|
|
||||||
document.body.append(style);
|
|
||||||
const stylesheet = style.sheet;
|
|
||||||
style.remove();
|
|
||||||
return stylesheet;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// We use another stylsheet to validate CSS properties which can be
|
||||||
|
// used in a CSS declaration.
|
||||||
|
this.styleValidatorElement = null;
|
||||||
|
(( ) => {
|
||||||
|
if ( typeof document !== 'object' ) { return; }
|
||||||
|
if ( document === null ) { return; }
|
||||||
|
try {
|
||||||
|
const styleElement = document.createElement('style');
|
||||||
|
styleElement.appendChild(document.createTextNode(' '));
|
||||||
|
document.body.append(styleElement);
|
||||||
|
this.styleValidatorElement = styleElement;
|
||||||
|
} catch(ex) {
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// We use an HTML element to validate selectors which are
|
||||||
|
// querySelector-able.
|
||||||
this.div = (( ) => {
|
this.div = (( ) => {
|
||||||
if ( typeof document !== 'object' ) { return null; }
|
if ( typeof document !== 'object' ) { return null; }
|
||||||
if ( document instanceof Object === false ) { return null; }
|
if ( document instanceof Object === false ) { return null; }
|
||||||
return document.createElement('div');
|
return document.createElement('div');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
this.reProceduralOperator = new RegExp([
|
this.reProceduralOperator = new RegExp([
|
||||||
'^(?:',
|
'^(?:',
|
||||||
Array.from(parser.proceduralOperatorTokens.keys()).join('|'),
|
Array.from(parser.proceduralOperatorTokens.keys()).join('|'),
|
||||||
|
@ -1363,6 +1402,7 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
this.reDropScope = /^\s*:scope\s*(?=[+>~])/;
|
this.reDropScope = /^\s*:scope\s*(?=[+>~])/;
|
||||||
this.reIsDanglingSelector = /[+>~\s]\s*$/;
|
this.reIsDanglingSelector = /[+>~\s]\s*$/;
|
||||||
this.reIsCombinator = /^\s*[+>~]/;
|
this.reIsCombinator = /^\s*[+>~]/;
|
||||||
|
this.reUnicodeSpecials = /[\uFFF0-\uFFFF]/;
|
||||||
this.regexToRawValue = new Map();
|
this.regexToRawValue = new Map();
|
||||||
// https://github.com/gorhill/uBlock/issues/2793
|
// https://github.com/gorhill/uBlock/issues/2793
|
||||||
this.normalizedOperators = new Map([
|
this.normalizedOperators = new Map([
|
||||||
|
@ -1467,26 +1507,25 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1806#issuecomment-963278382
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1806#issuecomment-963278382
|
||||||
// Forbid multiple and unexpected CSS style declarations.
|
// Forbid multiple and unexpected CSS style declarations.
|
||||||
sheetSelectable(s) {
|
sheetSelectable(s) {
|
||||||
if ( this.reSimpleSelector.test(s) ) { return true; }
|
if ( this.reCommonSelector.test(s) ) { return true; }
|
||||||
if ( this.stylesheet === null ) { return true; }
|
const rule = this.sheetValidatorRule;
|
||||||
|
if ( rule === null ) { return true; }
|
||||||
|
let valid = false;
|
||||||
try {
|
try {
|
||||||
this.stylesheet.insertRule(`${s}{color:red}`);
|
rule.selectorText = '_z';
|
||||||
if ( this.stylesheet.cssRules.length !== 1 ) { return false; }
|
rule.selectorText = `_z + ${s}`;
|
||||||
const style = this.stylesheet.cssRules[0].style;
|
valid = rule.selectorText !== '_z' &&
|
||||||
if ( style.length !== 1 ) { return false; }
|
this.reUnicodeSpecials.test(rule.selectorText) === false;
|
||||||
if ( style.getPropertyValue('color') !== 'red' ) { return false; }
|
|
||||||
this.stylesheet.deleteRule(0);
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1806
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1806
|
||||||
// Forbid instances of:
|
// Forbid instances of:
|
||||||
// - opening comment `/*`
|
// - opening comment `/*`
|
||||||
querySelectable(s) {
|
querySelectable(s) {
|
||||||
if ( this.reSimpleSelector.test(s) ) { return true; }
|
if ( this.reCommonSelector.test(s) ) { return true; }
|
||||||
if ( this.div === null ) { return true; }
|
if ( this.div === null ) { return true; }
|
||||||
try {
|
try {
|
||||||
this.div.querySelector(`${s},${s}:not(#foo)`);
|
this.div.querySelector(`${s},${s}:not(#foo)`);
|
||||||
|
@ -1600,17 +1639,15 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
// - opening comment `/*`
|
// - opening comment `/*`
|
||||||
compileStyleProperties(s) {
|
compileStyleProperties(s) {
|
||||||
if ( /image-set\(|url\(|\/\s*\/|\\|\/\*/i.test(s) ) { return; }
|
if ( /image-set\(|url\(|\/\s*\/|\\|\/\*/i.test(s) ) { return; }
|
||||||
if ( this.stylesheet === null ) { return s; }
|
if ( this.styleValidatorElement === null ) { return s; }
|
||||||
let valid = false;
|
let valid = false;
|
||||||
try {
|
try {
|
||||||
this.stylesheet.insertRule(`a{${s}}`);
|
this.styleValidatorElement.childNodes[0].nodeValue = `_z{${s}} _z{color:red;}`;
|
||||||
const rules = this.stylesheet.cssRules;
|
const rules = this.styleValidatorElement.sheet.cssRules;
|
||||||
valid = rules.length !== 0 && rules[0].style.cssText !== '';
|
valid = rules.length >= 2 &&
|
||||||
|
rules[0].style.cssText !== '' &&
|
||||||
|
rules[1].style.cssText !== '';
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( this.stylesheet.cssRules.length !== 0 ) {
|
|
||||||
this.stylesheet.deleteRule(0);
|
|
||||||
}
|
}
|
||||||
if ( valid ) { return s; }
|
if ( valid ) { return s; }
|
||||||
}
|
}
|
||||||
|
@ -1781,8 +1818,12 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
if ( i === n ) { break; }
|
if ( i === n ) { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When there is an explicit action, nothing should be left to parse.
|
||||||
|
if ( action !== undefined && opPrefixBeg < n ) { return; }
|
||||||
|
|
||||||
// No task found: then we have a CSS selector.
|
// No task found: then we have a CSS selector.
|
||||||
// At least one task found: nothing should be left to parse.
|
// At least one task found: either nothing or a valid plain CSS selector
|
||||||
|
// should be left to parse
|
||||||
if ( tasks.length === 0 ) {
|
if ( tasks.length === 0 ) {
|
||||||
if ( action === undefined ) {
|
if ( action === undefined ) {
|
||||||
prefix = raw;
|
prefix = raw;
|
||||||
|
@ -1796,7 +1837,6 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ( opPrefixBeg < n ) {
|
} else if ( opPrefixBeg < n ) {
|
||||||
if ( action !== undefined ) { return; }
|
|
||||||
const spath = this.compileSpathExpression(raw.slice(opPrefixBeg));
|
const spath = this.compileSpathExpression(raw.slice(opPrefixBeg));
|
||||||
if ( spath === undefined ) { return; }
|
if ( spath === undefined ) { return; }
|
||||||
tasks.push([ ':spath', spath ]);
|
tasks.push([ ':spath', spath ]);
|
||||||
|
|
Loading…
Reference in a new issue