diff options
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/set-constant.js')
-rw-r--r-- | data/extensions/uBlock0@raymondhill.net/js/resources/set-constant.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/set-constant.js b/data/extensions/uBlock0@raymondhill.net/js/resources/set-constant.js new file mode 100644 index 0000000..9ee9859 --- /dev/null +++ b/data/extensions/uBlock0@raymondhill.net/js/resources/set-constant.js @@ -0,0 +1,287 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2019-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 { runAt } from './run-at.js'; +import { safeSelf } from './safe-self.js'; + +/******************************************************************************/ + +export function validateConstantFn(trusted, raw, extraArgs = {}) { + const safe = safeSelf(); + let value; + if ( raw === 'undefined' ) { + value = undefined; + } else if ( raw === 'false' ) { + value = false; + } else if ( raw === 'true' ) { + value = true; + } else if ( raw === 'null' ) { + value = null; + } else if ( raw === "''" || raw === '' ) { + value = ''; + } else if ( raw === '[]' || raw === 'emptyArr' ) { + value = []; + } else if ( raw === '{}' || raw === 'emptyObj' ) { + value = {}; + } else if ( raw === 'noopFunc' ) { + value = function(){}; + } else if ( raw === 'trueFunc' ) { + value = function(){ return true; }; + } else if ( raw === 'falseFunc' ) { + value = function(){ return false; }; + } else if ( raw === 'throwFunc' ) { + value = function(){ throw ''; }; + } else if ( /^-?\d+$/.test(raw) ) { + value = parseInt(raw); + if ( isNaN(raw) ) { return; } + if ( Math.abs(raw) > 0x7FFF ) { return; } + } else if ( trusted ) { + if ( raw.startsWith('json:') ) { + try { value = safe.JSON_parse(raw.slice(5)); } catch { return; } + } else if ( raw.startsWith('{') && raw.endsWith('}') ) { + try { value = safe.JSON_parse(raw).value; } catch { return; } + } + } else { + return; + } + if ( extraArgs.as !== undefined ) { + if ( extraArgs.as === 'function' ) { + return ( ) => value; + } else if ( extraArgs.as === 'callback' ) { + return ( ) => (( ) => value); + } else if ( extraArgs.as === 'resolved' ) { + return Promise.resolve(value); + } else if ( extraArgs.as === 'rejected' ) { + return Promise.reject(value); + } + } + return value; +} +registerScriptlet(validateConstantFn, { + name: 'validate-constant.fn', + dependencies: [ + safeSelf, + ], +}); + +/******************************************************************************/ + +export function setConstantFn( + trusted = false, + chain = '', + rawValue = '' +) { + if ( chain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('set-constant', chain, rawValue); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + function setConstant(chain, rawValue) { + const trappedProp = (( ) => { + const pos = chain.lastIndexOf('.'); + if ( pos === -1 ) { return chain; } + return chain.slice(pos+1); + })(); + const cloakFunc = fn => { + safe.Object_defineProperty(fn, 'name', { value: trappedProp }); + return new Proxy(fn, { + defineProperty(target, prop) { + if ( prop !== 'toString' ) { + return Reflect.defineProperty(...arguments); + } + return true; + }, + deleteProperty(target, prop) { + if ( prop !== 'toString' ) { + return Reflect.deleteProperty(...arguments); + } + return true; + }, + get(target, prop) { + if ( prop === 'toString' ) { + return function() { + return `function ${trappedProp}() { [native code] }`; + }.bind(null); + } + return Reflect.get(...arguments); + }, + }); + }; + if ( trappedProp === '' ) { return; } + const thisScript = document.currentScript; + let normalValue = validateConstantFn(trusted, rawValue, extraArgs); + if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) { + normalValue = cloakFunc(normalValue); + } + let aborted = false; + const mustAbort = function(v) { + if ( trusted ) { return false; } + if ( aborted ) { return true; } + aborted = + (v !== undefined && v !== null) && + (normalValue !== undefined && normalValue !== null) && + (typeof v !== typeof normalValue); + if ( aborted ) { + safe.uboLog(logPrefix, `Aborted because value set to ${v}`); + } + return aborted; + }; + // https://github.com/uBlockOrigin/uBlock-issues/issues/156 + // Support multiple trappers for the same property. + const trapProp = function(owner, prop, configurable, handler) { + if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; } + const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop); + let prevGetter, prevSetter; + if ( odesc instanceof safe.Object ) { + owner[prop] = normalValue; + if ( odesc.get instanceof Function ) { + prevGetter = odesc.get; + } + if ( odesc.set instanceof Function ) { + prevSetter = odesc.set; + } + } + try { + safe.Object_defineProperty(owner, prop, { + configurable, + get() { + if ( prevGetter !== undefined ) { + prevGetter(); + } + return handler.getter(); + }, + set(a) { + if ( prevSetter !== undefined ) { + prevSetter(a); + } + handler.setter(a); + } + }); + safe.uboLog(logPrefix, 'Trap installed'); + } catch(ex) { + safe.uboErr(logPrefix, ex); + } + }; + const trapChain = function(owner, chain) { + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + trapProp(owner, chain, false, { + v: undefined, + init: function(v) { + if ( mustAbort(v) ) { return false; } + this.v = v; + return true; + }, + getter: function() { + if ( document.currentScript === thisScript ) { + return this.v; + } + safe.uboLog(logPrefix, 'Property read'); + return normalValue; + }, + setter: function(a) { + if ( mustAbort(a) === false ) { return; } + normalValue = a; + } + }); + return; + } + const prop = chain.slice(0, pos); + const v = owner[prop]; + chain = chain.slice(pos + 1); + if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) { + trapChain(v, chain); + return; + } + trapProp(owner, prop, true, { + v: undefined, + init: function(v) { + this.v = v; + return true; + }, + getter: function() { + return this.v; + }, + setter: function(a) { + this.v = a; + if ( a instanceof safe.Object ) { + trapChain(a, chain); + } + } + }); + }; + trapChain(window, chain); + } + runAt(( ) => { + setConstant(chain, rawValue); + }, extraArgs.runAt); +} +registerScriptlet(setConstantFn, { + name: 'set-constant.fn', + dependencies: [ + runAt, + safeSelf, + validateConstantFn, + ], +}); + +/******************************************************************************/ + +export function setConstant( + ...args +) { + setConstantFn(false, ...args); +} +registerScriptlet(setConstant, { + name: 'set-constant.js', + aliases: [ + 'set.js', + ], + dependencies: [ + setConstantFn, + ], +}); + +/******************************************************************************* + * + * trusted-set-constant.js + * + * Set specified property to any value. This is essentially the same as + * set-constant.js, but with no restriction as to which values can be used. + * + **/ + +export function trustedSetConstant( + ...args +) { + setConstantFn(true, ...args); +} +registerScriptlet(trustedSetConstant, { + name: 'trusted-set-constant.js', + requiresTrust: true, + aliases: [ + 'trusted-set.js', + ], + dependencies: [ + setConstantFn, + ], +}); |