mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-13 02:14:17 +01:00
Add trusted-set-constant
scriptlet
Alias: `trusted-set` Behaves exactly like set-constant, except that any arbitrary JSON- compatible value can be set. By default the value is treated as a string, which can be anything. If the value starts with `{` and ends with `}`, the value will be JSON-parsed, and the `value` property of the resulting object will be used. As with any scriptlet requiring trust, filters using `trusted-set-constant` can only come from trusted filter lists, otherwise they are discarded. Related discussion: - https://github.com/uBlockOrigin/uAssets/discussions/18185#discussioncomment-5977456
This commit is contained in:
parent
d74c73e452
commit
19cdd50a1c
1 changed files with 222 additions and 183 deletions
|
@ -157,6 +157,208 @@ function runAt(fn, when) {
|
||||||
safe.addEventListener.apply(document, args);
|
safe.addEventListener.apply(document, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
builtinScriptlets.push({
|
||||||
|
name: 'set-constant-core.fn',
|
||||||
|
fn: setConstantCore,
|
||||||
|
dependencies: [
|
||||||
|
'safe-self.fn',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
function setConstantCore(
|
||||||
|
trusted = false,
|
||||||
|
arg1 = '',
|
||||||
|
arg2 = '',
|
||||||
|
arg3 = ''
|
||||||
|
) {
|
||||||
|
const details = typeof arg1 !== 'object'
|
||||||
|
? { prop: arg1, value: arg2 }
|
||||||
|
: arg1;
|
||||||
|
if ( arg3 !== '' ) {
|
||||||
|
if ( /^\d$/.test(arg3) ) {
|
||||||
|
details.options = [ arg3 ];
|
||||||
|
} else {
|
||||||
|
details.options = Array.from(arguments).slice(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { prop: chain = '', value: cValue = '' } = details;
|
||||||
|
if ( typeof chain !== 'string' ) { return; }
|
||||||
|
if ( chain === '' ) { return; }
|
||||||
|
const options = details.options || [];
|
||||||
|
function setConstant(chain, cValue) {
|
||||||
|
const trappedProp = (( ) => {
|
||||||
|
const pos = chain.lastIndexOf('.');
|
||||||
|
if ( pos === -1 ) { return chain; }
|
||||||
|
return chain.slice(pos+1);
|
||||||
|
})();
|
||||||
|
if ( trappedProp === '' ) { return; }
|
||||||
|
const thisScript = document.currentScript;
|
||||||
|
const objectDefineProperty = Object.defineProperty.bind(Object);
|
||||||
|
const cloakFunc = fn => {
|
||||||
|
objectDefineProperty(fn, 'name', { value: trappedProp });
|
||||||
|
const proxy = new Proxy(fn, {
|
||||||
|
defineProperty(target, prop) {
|
||||||
|
if ( prop !== 'toString' ) {
|
||||||
|
return Reflect.deleteProperty(...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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
if ( cValue === 'undefined' ) {
|
||||||
|
cValue = undefined;
|
||||||
|
} else if ( cValue === 'false' ) {
|
||||||
|
cValue = false;
|
||||||
|
} else if ( cValue === 'true' ) {
|
||||||
|
cValue = true;
|
||||||
|
} else if ( cValue === 'null' ) {
|
||||||
|
cValue = null;
|
||||||
|
} else if ( cValue === "''" ) {
|
||||||
|
cValue = '';
|
||||||
|
} else if ( cValue === '[]' ) {
|
||||||
|
cValue = [];
|
||||||
|
} else if ( cValue === '{}' ) {
|
||||||
|
cValue = {};
|
||||||
|
} else if ( cValue === 'noopFunc' ) {
|
||||||
|
cValue = cloakFunc(function(){});
|
||||||
|
} else if ( cValue === 'trueFunc' ) {
|
||||||
|
cValue = cloakFunc(function(){ return true; });
|
||||||
|
} else if ( cValue === 'falseFunc' ) {
|
||||||
|
cValue = cloakFunc(function(){ return false; });
|
||||||
|
} else if ( /^-?\d+$/.test(cValue) ) {
|
||||||
|
cValue = parseInt(cValue);
|
||||||
|
if ( isNaN(cValue) ) { return; }
|
||||||
|
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||||
|
} else if ( trusted ) {
|
||||||
|
if ( cValue.startsWith('{') && cValue.endsWith('}') ) {
|
||||||
|
try { cValue = JSON.parse(cValue).value; } catch(ex) { return; }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( options.includes('asFunction') ) {
|
||||||
|
cValue = ( ) => cValue;
|
||||||
|
} else if ( options.includes('asCallback') ) {
|
||||||
|
cValue = ( ) => (( ) => cValue);
|
||||||
|
} else if ( options.includes('asResolved') ) {
|
||||||
|
cValue = Promise.resolve(cValue);
|
||||||
|
} else if ( options.includes('asRejected') ) {
|
||||||
|
cValue = Promise.reject(cValue);
|
||||||
|
}
|
||||||
|
let aborted = false;
|
||||||
|
const mustAbort = function(v) {
|
||||||
|
if ( trusted ) { return false; }
|
||||||
|
if ( aborted ) { return true; }
|
||||||
|
aborted =
|
||||||
|
(v !== undefined && v !== null) &&
|
||||||
|
(cValue !== undefined && cValue !== null) &&
|
||||||
|
(typeof v !== typeof cValue);
|
||||||
|
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] : cValue) === false ) { return; }
|
||||||
|
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
||||||
|
let prevGetter, prevSetter;
|
||||||
|
if ( odesc instanceof Object ) {
|
||||||
|
owner[prop] = cValue;
|
||||||
|
if ( odesc.get instanceof Function ) {
|
||||||
|
prevGetter = odesc.get;
|
||||||
|
}
|
||||||
|
if ( odesc.set instanceof Function ) {
|
||||||
|
prevSetter = odesc.set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
objectDefineProperty(owner, prop, {
|
||||||
|
configurable,
|
||||||
|
get() {
|
||||||
|
if ( prevGetter !== undefined ) {
|
||||||
|
prevGetter();
|
||||||
|
}
|
||||||
|
return handler.getter(); // cValue
|
||||||
|
},
|
||||||
|
set(a) {
|
||||||
|
if ( prevSetter !== undefined ) {
|
||||||
|
prevSetter(a);
|
||||||
|
}
|
||||||
|
handler.setter(a);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(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() {
|
||||||
|
return document.currentScript === thisScript
|
||||||
|
? this.v
|
||||||
|
: cValue;
|
||||||
|
},
|
||||||
|
setter: function(a) {
|
||||||
|
if ( mustAbort(a) === false ) { return; }
|
||||||
|
cValue = a;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prop = chain.slice(0, pos);
|
||||||
|
const v = owner[prop];
|
||||||
|
chain = chain.slice(pos + 1);
|
||||||
|
if ( v instanceof 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 Object ) {
|
||||||
|
trapChain(a, chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
trapChain(window, chain);
|
||||||
|
}
|
||||||
|
runAt(( ) => {
|
||||||
|
setConstant(chain, cValue);
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
Injectable scriptlets
|
Injectable scriptlets
|
||||||
|
@ -1029,193 +1231,13 @@ builtinScriptlets.push({
|
||||||
aliases: [ 'set.js' ],
|
aliases: [ 'set.js' ],
|
||||||
fn: setConstant,
|
fn: setConstant,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
'run-at.fn',
|
'set-constant-core.fn'
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
function setConstant(
|
function setConstant(
|
||||||
arg1 = '',
|
...args
|
||||||
arg2 = '',
|
|
||||||
arg3 = ''
|
|
||||||
) {
|
) {
|
||||||
const details = typeof arg1 !== 'object'
|
setConstantCore(false, ...args);
|
||||||
? { prop: arg1, value: arg2 }
|
|
||||||
: arg1;
|
|
||||||
if ( arg3 !== '' ) {
|
|
||||||
if ( /^\d$/.test(arg3) ) {
|
|
||||||
details.options = [ arg3 ];
|
|
||||||
} else {
|
|
||||||
details.options = Array.from(arguments).slice(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { prop: chain = '', value: cValue = '' } = details;
|
|
||||||
if ( typeof chain !== 'string' ) { return; }
|
|
||||||
if ( chain === '' ) { return; }
|
|
||||||
const options = details.options || [];
|
|
||||||
function setConstant(chain, cValue) {
|
|
||||||
const trappedProp = (( ) => {
|
|
||||||
const pos = chain.lastIndexOf('.');
|
|
||||||
if ( pos === -1 ) { return chain; }
|
|
||||||
return chain.slice(pos+1);
|
|
||||||
})();
|
|
||||||
if ( trappedProp === '' ) { return; }
|
|
||||||
const thisScript = document.currentScript;
|
|
||||||
const objectDefineProperty = Object.defineProperty.bind(Object);
|
|
||||||
const cloakFunc = fn => {
|
|
||||||
objectDefineProperty(fn, 'name', { value: trappedProp });
|
|
||||||
const proxy = new Proxy(fn, {
|
|
||||||
defineProperty(target, prop) {
|
|
||||||
if ( prop !== 'toString' ) {
|
|
||||||
return Reflect.deleteProperty(...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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return proxy;
|
|
||||||
};
|
|
||||||
if ( cValue === 'undefined' ) {
|
|
||||||
cValue = undefined;
|
|
||||||
} else if ( cValue === 'false' ) {
|
|
||||||
cValue = false;
|
|
||||||
} else if ( cValue === 'true' ) {
|
|
||||||
cValue = true;
|
|
||||||
} else if ( cValue === 'null' ) {
|
|
||||||
cValue = null;
|
|
||||||
} else if ( cValue === "''" ) {
|
|
||||||
cValue = '';
|
|
||||||
} else if ( cValue === '[]' ) {
|
|
||||||
cValue = [];
|
|
||||||
} else if ( cValue === '{}' ) {
|
|
||||||
cValue = {};
|
|
||||||
} else if ( cValue === 'noopFunc' ) {
|
|
||||||
cValue = cloakFunc(function(){});
|
|
||||||
} else if ( cValue === 'trueFunc' ) {
|
|
||||||
cValue = cloakFunc(function(){ return true; });
|
|
||||||
} else if ( cValue === 'falseFunc' ) {
|
|
||||||
cValue = cloakFunc(function(){ return false; });
|
|
||||||
} else if ( /^-?\d+$/.test(cValue) ) {
|
|
||||||
cValue = parseInt(cValue);
|
|
||||||
if ( isNaN(cValue) ) { return; }
|
|
||||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( options.includes('asFunction') ) {
|
|
||||||
cValue = ( ) => cValue;
|
|
||||||
} else if ( options.includes('asCallback') ) {
|
|
||||||
cValue = ( ) => (( ) => cValue);
|
|
||||||
} else if ( options.includes('asResolved') ) {
|
|
||||||
cValue = Promise.resolve(cValue);
|
|
||||||
} else if ( options.includes('asRejected') ) {
|
|
||||||
cValue = Promise.reject(cValue);
|
|
||||||
}
|
|
||||||
let aborted = false;
|
|
||||||
const mustAbort = function(v) {
|
|
||||||
if ( aborted ) { return true; }
|
|
||||||
aborted =
|
|
||||||
(v !== undefined && v !== null) &&
|
|
||||||
(cValue !== undefined && cValue !== null) &&
|
|
||||||
(typeof v !== typeof cValue);
|
|
||||||
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] : cValue) === false ) { return; }
|
|
||||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
||||||
let prevGetter, prevSetter;
|
|
||||||
if ( odesc instanceof Object ) {
|
|
||||||
owner[prop] = cValue;
|
|
||||||
if ( odesc.get instanceof Function ) {
|
|
||||||
prevGetter = odesc.get;
|
|
||||||
}
|
|
||||||
if ( odesc.set instanceof Function ) {
|
|
||||||
prevSetter = odesc.set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
objectDefineProperty(owner, prop, {
|
|
||||||
configurable,
|
|
||||||
get() {
|
|
||||||
if ( prevGetter !== undefined ) {
|
|
||||||
prevGetter();
|
|
||||||
}
|
|
||||||
return handler.getter(); // cValue
|
|
||||||
},
|
|
||||||
set(a) {
|
|
||||||
if ( prevSetter !== undefined ) {
|
|
||||||
prevSetter(a);
|
|
||||||
}
|
|
||||||
handler.setter(a);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch(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() {
|
|
||||||
return document.currentScript === thisScript
|
|
||||||
? this.v
|
|
||||||
: cValue;
|
|
||||||
},
|
|
||||||
setter: function(a) {
|
|
||||||
if ( mustAbort(a) === false ) { return; }
|
|
||||||
cValue = a;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const prop = chain.slice(0, pos);
|
|
||||||
const v = owner[prop];
|
|
||||||
chain = chain.slice(pos + 1);
|
|
||||||
if ( v instanceof 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 Object ) {
|
|
||||||
trapChain(a, chain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
trapChain(window, chain);
|
|
||||||
}
|
|
||||||
runAt(( ) => {
|
|
||||||
setConstant(chain, cValue);
|
|
||||||
}, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -2338,3 +2360,20 @@ function sed(
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
builtinScriptlets.push({
|
||||||
|
name: 'trusted-set-constant.js',
|
||||||
|
requiresTrust: true,
|
||||||
|
aliases: [ 'trusted-set' ],
|
||||||
|
fn: trustedSetConstant,
|
||||||
|
dependencies: [
|
||||||
|
'set-constant-core.fn'
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function trustedSetConstant(
|
||||||
|
...args
|
||||||
|
) {
|
||||||
|
setConstantCore(true, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
Loading…
Reference in a new issue