Improve scriptlet helper proxy-apply

Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/3378
This commit is contained in:
Raymond Hill 2024-09-17 09:09:19 -04:00
parent ef1e134460
commit 547fae4842
No known key found for this signature in database
GPG key ID: 25E1490B761470C2

View file

@ -1488,26 +1488,44 @@ function proxyApplyFn(
} }
const fn = context[prop]; const fn = context[prop];
if ( typeof fn !== 'function' ) { return; } if ( typeof fn !== 'function' ) { return; }
if ( proxyApplyFn.CtorContext === undefined ) {
proxyApplyFn.CtorContext = class {
constructor(callFn, callArgs) {
this.callFn = callFn;
this.callArgs = callArgs;
}
reflect() {
return Reflect.construct(this.callFn, this.callArgs);
}
};
proxyApplyFn.ApplyContext = class {
constructor(callFn, thisArg, callArgs) {
this.callFn = callFn;
this.thisArg = thisArg;
this.callArgs = callArgs;
}
reflect() {
return Reflect.apply(this.callFn, this.thisArg, this.callArgs);
}
};
}
const fnStr = fn.toString(); const fnStr = fn.toString();
const toString = (function toString() { return fnStr; }).bind(null); const toString = (function toString() { return fnStr; }).bind(null);
if ( fn.prototype && fn.prototype.constructor === fn ) { const proxyDetails = {
context[prop] = new Proxy(fn, { apply(target, thisArg, args) {
construct: handler, return handler(new proxyApplyFn.ApplyContext(target, thisArg, args));
get(target, prop, receiver) { },
if ( prop === 'toString' ) { return toString; }
return Reflect.get(target, prop, receiver);
},
});
return (...args) => Reflect.construct(...args);
}
context[prop] = new Proxy(fn, {
apply: handler,
get(target, prop, receiver) { get(target, prop, receiver) {
if ( prop === 'toString' ) { return toString; } if ( prop === 'toString' ) { return toString; }
return Reflect.get(target, prop, receiver); return Reflect.get(target, prop, receiver);
}, },
}); };
return (...args) => Reflect.apply(...args); if ( fn.prototype?.constructor === fn ) {
proxyDetails.construct = function(target, args) {
return handler(new proxyApplyFn.CtorContext(target, args));
};
}
context[prop] = new Proxy(fn, proxyDetails);
} }
/******************************************************************************* /*******************************************************************************
@ -1771,18 +1789,19 @@ function addEventListenerDefuser(
return matchesBoth; return matchesBoth;
}; };
runAt(( ) => { runAt(( ) => {
proxyApplyFn('EventTarget.prototype.addEventListener', function(target, thisArg, args) { proxyApplyFn('EventTarget.prototype.addEventListener', function(context) {
const { callArgs, thisArg } = context;
let t, h; let t, h;
try { try {
t = String(args[0]); t = String(callArgs[0]);
if ( typeof args[1] === 'function' ) { if ( typeof callArgs[1] === 'function' ) {
h = String(safe.Function_toString(args[1])); h = String(safe.Function_toString(callArgs[1]));
} else if ( typeof args[1] === 'object' && args[1] !== null ) { } else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
if ( typeof args[1].handleEvent === 'function' ) { if ( typeof callArgs[1].handleEvent === 'function' ) {
h = String(safe.Function_toString(args[1].handleEvent)); h = String(safe.Function_toString(callArgs[1].handleEvent));
} }
} else { } else {
h = String(args[1]); h = String(callArgs[1]);
} }
} catch(ex) { } catch(ex) {
} }
@ -1791,7 +1810,7 @@ function addEventListenerDefuser(
} else if ( shouldPrevent(thisArg, t, h) ) { } else if ( shouldPrevent(thisArg, t, h) ) {
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`); return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
} }
return Reflect.apply(target, thisArg, args); return context.reflect();
}); });
}, extraArgs.runAt); }, extraArgs.runAt);
} }
@ -2176,10 +2195,11 @@ function noFetchIf(
responseProps.type = { value: responseType }; responseProps.type = { value: responseType };
} }
} }
proxyApplyFn('fetch', function fetch(target, thisArg, args) { proxyApplyFn('fetch', function fetch(context) {
const details = args[0] instanceof self.Request const { callArgs } = context;
? args[0] const details = callArgs[0] instanceof self.Request
: Object.assign({ url: args[0] }, args[1]); ? callArgs[0]
: Object.assign({ url: callArgs[0] }, callArgs[1]);
let proceed = true; let proceed = true;
try { try {
const props = new Map(); const props = new Map();
@ -2197,7 +2217,7 @@ function noFetchIf(
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`); safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
} }
if ( propsToMatch === '' && responseBody === '' ) { if ( propsToMatch === '' && responseBody === '' ) {
return Reflect.apply(target, thisArg, args); return context.reflect();
} }
proceed = needles.length === 0; proceed = needles.length === 0;
for ( const { key, pattern } of needles ) { for ( const { key, pattern } of needles ) {
@ -2212,7 +2232,7 @@ function noFetchIf(
} catch(ex) { } catch(ex) {
} }
if ( proceed ) { if ( proceed ) {
return Reflect.apply(target, thisArg, args); return context.reflect();
} }
return generateContentFn(responseBody).then(text => { return generateContentFn(responseBody).then(text => {
safe.uboLog(logPrefix, `Prevented with response "${text}"`); safe.uboLog(logPrefix, `Prevented with response "${text}"`);
@ -2520,14 +2540,15 @@ function noSetIntervalIf(
delay = parseInt(delay, 10); delay = parseInt(delay, 10);
} }
const reNeedle = safe.patternToRegex(needle); const reNeedle = safe.patternToRegex(needle);
proxyApplyFn('setInterval', function setInterval(target, thisArg, args) { proxyApplyFn('setInterval', function setInterval(context) {
const a = args[0] instanceof Function const { callArgs } = context;
? String(safe.Function_toString(args[0])) const a = callArgs[0] instanceof Function
: String(args[0]); ? String(safe.Function_toString(callArgs[0]))
const b = args[1]; : String(callArgs[0]);
const b = callArgs[1];
if ( needle === '' && delay === undefined ) { if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`); safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args); return context.reflect();
} }
let defuse; let defuse;
if ( needle !== '' ) { if ( needle !== '' ) {
@ -2537,10 +2558,10 @@ function noSetIntervalIf(
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
} }
if ( defuse ) { if ( defuse ) {
args[0] = function(){}; callArgs[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
} }
return Reflect.apply(target, thisArg, args); return context.reflect();
}); });
} }
@ -2576,14 +2597,15 @@ function noSetTimeoutIf(
delay = parseInt(delay, 10); delay = parseInt(delay, 10);
} }
const reNeedle = safe.patternToRegex(needle); const reNeedle = safe.patternToRegex(needle);
proxyApplyFn('setTimeout', function setTimeout(target, thisArg, args) { proxyApplyFn('setTimeout', function setTimeout(context) {
const a = args[0] instanceof Function const { callArgs } = context;
? String(safe.Function_toString(args[0])) const a = callArgs[0] instanceof Function
: String(args[0]); ? String(safe.Function_toString(callArgs[0]))
const b = args[1]; : String(callArgs[0]);
const b = callArgs[1];
if ( needle === '' && delay === undefined ) { if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`); safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args); return context.reflect();
} }
let defuse; let defuse;
if ( needle !== '' ) { if ( needle !== '' ) {
@ -2593,10 +2615,10 @@ function noSetTimeoutIf(
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
} }
if ( defuse ) { if ( defuse ) {
args[0] = function(){}; callArgs[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
} }
return Reflect.apply(target, thisArg, args); return context.reflect();
}); });
} }
@ -2900,25 +2922,26 @@ function noWindowOpenIf(
return decoyElem; return decoyElem;
}; };
const noopFunc = function(){}; const noopFunc = function(){};
proxyApplyFn('open', function open(target, thisArg, args) { proxyApplyFn('open', function open(context) {
const haystack = args.join(' '); const { callArgs } = context;
const haystack = callArgs.join(' ');
if ( rePattern.test(haystack) !== targetMatchResult ) { if ( rePattern.test(haystack) !== targetMatchResult ) {
if ( safe.logLevel > 1 ) { if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`); safe.uboLog(logPrefix, `Allowed (${callArgs.join(', ')})`);
} }
return Reflect.apply(target, thisArg, args); return context.reflect();
} }
safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`); safe.uboLog(logPrefix, `Prevented (${callArgs.join(', ')})`);
if ( delay === '' ) { return null; } if ( delay === '' ) { return null; }
if ( decoy === 'blank' ) { if ( decoy === 'blank' ) {
args[0] = 'about:blank'; callArgs[0] = 'about:blank';
const r = Reflect.apply(target, thisArg, args); const r = context.reflect();
setTimeout(( ) => { r.close(); }, autoRemoveAfter); setTimeout(( ) => { r.close(); }, autoRemoveAfter);
return r; return r;
} }
const decoyElem = decoy === 'obj' const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...args) ? createDecoy('object', 'data', ...callArgs)
: createDecoy('iframe', 'src', ...args); : createDecoy('iframe', 'src', ...callArgs);
let popup = decoyElem.contentWindow; let popup = decoyElem.contentWindow;
if ( typeof popup === 'object' && popup !== null ) { if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false }); Object.defineProperty(popup, 'closed', { value: false });
@ -4850,8 +4873,8 @@ function trustedPruneOutboundObject(
if ( propChain === '' ) { return; } if ( propChain === '' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const reflector = proxyApplyFn(propChain, function(...args) { proxyApplyFn(propChain, function(context) {
const objBefore = reflector(...args); const objBefore = context.reflect();
if ( objBefore instanceof Object === false ) { return objBefore; } if ( objBefore instanceof Object === false ) { return objBefore; }
const objAfter = objectPruneFn( const objAfter = objectPruneFn(
objBefore, objBefore,
@ -4884,26 +4907,27 @@ function trustedReplaceArgument(
if ( propChain === '' ) { return; } if ( propChain === '' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw); const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw);
const argpos = parseInt(argposRaw, 10) || 0; const argoffset = parseInt(argposRaw, 10) || 0;
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const normalValue = validateConstantFn(true, argraw, extraArgs); const normalValue = validateConstantFn(true, argraw, extraArgs);
const reCondition = extraArgs.condition const reCondition = extraArgs.condition
? safe.patternToRegex(extraArgs.condition) ? safe.patternToRegex(extraArgs.condition)
: /^/; : /^/;
const reflector = proxyApplyFn(propChain, function(...args) { proxyApplyFn(propChain, function(context) {
const { callArgs } = context;
if ( argposRaw === '' ) { if ( argposRaw === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${args.join('\n')}`); safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
return reflector(...args); return context.reflect();
} }
const arglist = args[args.length-1]; const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
if ( Array.isArray(arglist) === false ) { return reflector(...args); } if ( argpos >= 0 && argpos < callArgs.length ) {
const argBefore = arglist[argpos]; const argBefore = callArgs[argpos];
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) { if ( safe.RegExp_test.call(reCondition, argBefore) ) {
return reflector(...args); callArgs[argpos] = normalValue;
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
}
} }
arglist[argpos] = normalValue; return context.reflect();
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
return reflector(...args);
}); });
} }
@ -4933,8 +4957,8 @@ function trustedReplaceOutboundText(
: rawReplacement; : rawReplacement;
const extraArgs = safe.getExtraArgs(args); const extraArgs = safe.getExtraArgs(args);
const reCondition = safe.patternToRegex(extraArgs.condition || ''); const reCondition = safe.patternToRegex(extraArgs.condition || '');
const reflector = proxyApplyFn(propChain, function(...args) { proxyApplyFn(propChain, function(context) {
const encodedTextBefore = reflector(...args); const encodedTextBefore = context.reflect();
let textBefore = encodedTextBefore; let textBefore = encodedTextBefore;
if ( extraArgs.encoding === 'base64' ) { if ( extraArgs.encoding === 'base64' ) {
try { textBefore = self.atob(encodedTextBefore); } try { textBefore = self.atob(encodedTextBefore); }
@ -5011,34 +5035,31 @@ function trustedSuppressNativeMethod(
return { type: 'exact', value: undefined }; return { type: 'exact', value: undefined };
} }
}); });
const reflector = proxyApplyFn(methodPath, function(...args) { proxyApplyFn(methodPath, function(context) {
const { callArgs } = context;
if ( signature === '' ) { if ( signature === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${args.join('\n')}`); safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
return reflector(...args); return context.reflect();
} }
const arglist = args[args.length-1]; if ( callArgs.length < signatureArgs.length ) {
if ( Array.isArray(arglist) === false ) { return context.reflect();
return reflector(...args);
}
if ( arglist.length < signatureArgs.length ) {
return reflector(...args);
} }
for ( let i = 0; i < signatureArgs.length; i++ ) { for ( let i = 0; i < signatureArgs.length; i++ ) {
const signatureArg = signatureArgs[i]; const signatureArg = signatureArgs[i];
if ( signatureArg === undefined ) { continue; } if ( signatureArg === undefined ) { continue; }
const targetArg = arglist[i]; const targetArg = callArgs[i];
if ( signatureArg.type === 'exact' ) { if ( signatureArg.type === 'exact' ) {
if ( targetArg !== signatureArg.value ) { if ( targetArg !== signatureArg.value ) {
return reflector(...args); return context.reflect();
} }
} }
if ( signatureArg.type === 'pattern' ) { if ( signatureArg.type === 'pattern' ) {
if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) { if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) {
return reflector(...args); return context.reflect();
} }
} }
} }
safe.uboLog(logPrefix, `Suppressed:\n${args.join('\n')}`); safe.uboLog(logPrefix, `Suppressed:\n${callArgs.join('\n')}`);
if ( how === 'abort' ) { if ( how === 'abort' ) {
throw new ReferenceError(); throw new ReferenceError();
} }