diff --git a/src/js/resources/prevent-innerHTML.js b/src/js/resources/prevent-innerHTML.js new file mode 100644 index 000000000..0ab40a11e --- /dev/null +++ b/src/js/resources/prevent-innerHTML.js @@ -0,0 +1,79 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2025-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock + +*/ + +import { registerScriptlet } from './base.js'; +import { safeSelf } from './safe-self.js'; + +/** + * @scriptlet prevent-innerHTML + * + * @description + * Conditionally prevent assignment to `innerHTML` property. + * + * @param [selector] + * Optional. The element must matches `selector` for the prevention to take + * place. + * + * @param [pattern] + * Optional. A pattern to match against the assigned value. The pattern can be + * a plain string, or a regex. Prepend with `!` to reverse the match condition. + * + * */ + +export function preventInnerHTML( + selector = '', + pattern = '' +) { + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-innerHTML', selector, pattern); + const matcher = safe.initPattern(pattern, { canNegate: true }); + const current = safe.Object_getOwnPropertyDescriptor(Element.prototype, 'innerHTML'); + if ( current === undefined ) { return; } + const shouldPreventSet = a => { + if ( selector !== '' ) { + if ( typeof this.matches === 'function' === false ) { return false; } + if ( this.matches(selector) === false ) { return false; } + } + return safe.testPattern(matcher, a); + }; + Object.defineProperty(Element.prototype, 'innerHTML', { + get: function() { + return current.get + ? current.get.call(this) + : current.value; + }, + set: function(a) { + if ( shouldPreventSet(a) ) { + safe.uboLog(logPrefix, 'Prevented'); + } else if ( current.set ) { + current.set.call(this, a); + } + current.value = a; + }, + }); +} +registerScriptlet(preventInnerHTML, { + name: 'prevent-innerHTML.js', + dependencies: [ + safeSelf, + ], +}); diff --git a/src/js/resources/scriptlets.js b/src/js/resources/scriptlets.js index c580f4e7b..1f4a8de67 100755 --- a/src/js/resources/scriptlets.js +++ b/src/js/resources/scriptlets.js @@ -23,9 +23,10 @@ import './attribute.js'; import './href-sanitizer.js'; import './noeval.js'; +import './prevent-innerHTML.js'; +import './prevent-settimeout.js'; import './replace-argument.js'; import './spoof-css.js'; -import './prevent-settimeout.js'; import { runAt, runAtHtmlElementFn } from './run-at.js';