Improve trusted-replace-argument scriptlet

As discussed with filter list maintainers.
This commit is contained in:
Raymond Hill 2024-12-06 11:53:42 -05:00
parent 36db7f8327
commit 3417fe3d5d
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
7 changed files with 63 additions and 33 deletions

View file

@ -229,7 +229,7 @@ export function removeAttr(
if ( rawToken === '' ) { return; } if ( rawToken === '' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior); const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
const tokens = rawToken.split(/\s*\|\s*/); const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
const selector = tokens const selector = tokens
.map(a => `${rawSelector}[${CSS.escape(a)}]`) .map(a => `${rawSelector}[${CSS.escape(a)}]`)
.join(','); .join(',');
@ -289,7 +289,7 @@ export function removeAttr(
subtree: true, subtree: true,
}); });
}; };
runAt(( ) => { start(); }, behavior.split(/\s+/)); runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/));
} }
registerScriptlet(removeAttr, { registerScriptlet(removeAttr, {
name: 'remove-attr.js', name: 'remove-attr.js',

View file

@ -53,7 +53,8 @@ registerScriptlet(getSafeCookieValuesFn, {
/******************************************************************************/ /******************************************************************************/
export function getAllCookiesFn() { export function getAllCookiesFn() {
return document.cookie.split(/\s*;\s*/).map(s => { const safe = safeSelf();
return safe.String_split.call(document.cookie, /\s*;\s*/).map(s => {
const pos = s.indexOf('='); const pos = s.indexOf('=');
if ( pos === 0 ) { return; } if ( pos === 0 ) { return; }
if ( pos === -1 ) { return `${s.trim()}=`; } if ( pos === -1 ) { return `${s.trim()}=`; }
@ -64,6 +65,9 @@ export function getAllCookiesFn() {
} }
registerScriptlet(getAllCookiesFn, { registerScriptlet(getAllCookiesFn, {
name: 'get-all-cookies.fn', name: 'get-all-cookies.fn',
dependencies: [
safeSelf,
],
}); });
/******************************************************************************/ /******************************************************************************/
@ -71,7 +75,8 @@ registerScriptlet(getAllCookiesFn, {
export function getCookieFn( export function getCookieFn(
name = '' name = ''
) { ) {
for ( const s of document.cookie.split(/\s*;\s*/) ) { const safe = safeSelf();
for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) {
const pos = s.indexOf('='); const pos = s.indexOf('=');
if ( pos === -1 ) { continue; } if ( pos === -1 ) { continue; }
if ( s.slice(0, pos) !== name ) { continue; } if ( s.slice(0, pos) !== name ) { continue; }
@ -80,6 +85,9 @@ export function getCookieFn(
} }
registerScriptlet(getCookieFn, { registerScriptlet(getCookieFn, {
name: 'get-cookie.fn', name: 'get-cookie.fn',
dependencies: [
safeSelf,
],
}); });
/******************************************************************************/ /******************************************************************************/
@ -349,7 +357,7 @@ export function removeCookie(
}, ms); }, ms);
}; };
const remove = ( ) => { const remove = ( ) => {
document.cookie.split(';').forEach(cookieStr => { safe.String_split.call(document.cookie, ';').forEach(cookieStr => {
const pos = cookieStr.indexOf('='); const pos = cookieStr.indexOf('=');
if ( pos === -1 ) { return; } if ( pos === -1 ) { return; }
const cookieName = cookieStr.slice(0, pos).trim(); const cookieName = cookieStr.slice(0, pos).trim();
@ -387,7 +395,7 @@ export function removeCookie(
window.addEventListener('beforeunload', remove); window.addEventListener('beforeunload', remove);
if ( typeof extraArgs.when !== 'string' ) { return; } if ( typeof extraArgs.when !== 'string' ) { return; }
const supportedEventTypes = [ 'scroll', 'keydown' ]; const supportedEventTypes = [ 'scroll', 'keydown' ];
const eventTypes = extraArgs.when.split(/\s/); const eventTypes = safe.String_split.call(extraArgs.when, /\s/);
for ( const type of eventTypes ) { for ( const type of eventTypes ) {
if ( supportedEventTypes.includes(type) === false ) { continue; } if ( supportedEventTypes.includes(type) === false ) { continue; }
document.addEventListener(type, ( ) => { document.addEventListener(type, ( ) => {

View file

@ -52,7 +52,7 @@ export function proxyApplyFn(
} }
reflect() { reflect() {
const r = Reflect.construct(this.callFn, this.callArgs); const r = Reflect.construct(this.callFn, this.callArgs);
this.callFn = this.callArgs = undefined; this.callFn = this.callArgs = this.private = undefined;
proxyApplyFn.ctorContexts.push(this); proxyApplyFn.ctorContexts.push(this);
return r; return r;
} }
@ -75,7 +75,7 @@ export function proxyApplyFn(
} }
reflect() { reflect() {
const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs); const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs);
this.callFn = this.thisArg = this.callArgs = undefined; this.callFn = this.thisArg = this.callArgs = this.private = undefined;
proxyApplyFn.applyContexts.push(this); proxyApplyFn.applyContexts.push(this);
return r; return r;
} }

View file

@ -71,25 +71,39 @@ export function trustedReplaceArgument(
const reCondition = extraArgs.condition const reCondition = extraArgs.condition
? safe.patternToRegex(extraArgs.condition) ? safe.patternToRegex(extraArgs.condition)
: /^/; : /^/;
proxyApplyFn(propChain, function(context) { const getArg = context => {
if ( argposRaw === 'this' ) { return context.thisArg; }
const { callArgs } = context; const { callArgs } = context;
if ( argposRaw === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
return context.reflect();
}
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset; const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
if ( argpos < 0 || argpos >= callArgs.length ) { if ( argpos < 0 || argpos >= callArgs.length ) { return; }
context.private = { argpos };
return callArgs[argpos];
};
const setArg = (context, value) => {
if ( argposRaw === 'this' ) {
if ( value !== context.thisArg ) {
context.thisArg = value;
}
} else if ( context.private ) {
context.callArgs[context.private.argpos] = value;
}
};
proxyApplyFn(propChain, function(context) {
if ( argposRaw === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`);
return context.reflect(); return context.reflect();
} }
const argBefore = callArgs[argpos]; const argBefore = getArg(context);
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) { if ( safe.RegExp_test.call(reCondition, argBefore) === false ) {
return context.reflect(); return context.reflect();
} }
const argAfter = replacer && typeof argBefore === 'string' const argAfter = replacer && typeof argBefore === 'string'
? argBefore.replace(replacer.re, replacer.replacement) ? argBefore.replace(replacer.re, replacer.replacement)
: value; : value;
callArgs[argpos] = argAfter; if ( argAfter !== argBefore ) {
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`); setArg(context, argAfter);
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`);
}
return context.reflect(); return context.reflect();
}); });
} }

View file

@ -51,6 +51,7 @@ export function safeSelf() {
'RegExp_exec': self.RegExp.prototype.exec, 'RegExp_exec': self.RegExp.prototype.exec,
'Request_clone': self.Request.prototype.clone, 'Request_clone': self.Request.prototype.clone,
'String_fromCharCode': String.fromCharCode, 'String_fromCharCode': String.fromCharCode,
'String_split': String.prototype.split,
'XMLHttpRequest': self.XMLHttpRequest, 'XMLHttpRequest': self.XMLHttpRequest,
'addEventListener': self.EventTarget.prototype.addEventListener, 'addEventListener': self.EventTarget.prototype.addEventListener,
'removeEventListener': self.EventTarget.prototype.removeEventListener, 'removeEventListener': self.EventTarget.prototype.removeEventListener,

View file

@ -192,7 +192,7 @@ function abortCurrentScriptCore(
const reContext = safe.patternToRegex(context); const reContext = safe.patternToRegex(context);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const thisScript = document.currentScript; const thisScript = document.currentScript;
const chain = target.split('.'); const chain = safe.String_split.call(target, '.');
let owner = window; let owner = window;
let prop; let prop;
for (;;) { for (;;) {
@ -406,6 +406,7 @@ builtinScriptlets.push({
dependencies: [ dependencies: [
'matches-stack-trace.fn', 'matches-stack-trace.fn',
'object-find-owner.fn', 'object-find-owner.fn',
'safe-self.fn',
], ],
}); });
// When no "prune paths" argument is provided, the scriptlet is // When no "prune paths" argument is provided, the scriptlet is
@ -422,11 +423,12 @@ function objectPruneFn(
extraArgs = {} extraArgs = {}
) { ) {
if ( typeof rawPrunePaths !== 'string' ) { return; } if ( typeof rawPrunePaths !== 'string' ) { return; }
const safe = safeSelf();
const prunePaths = rawPrunePaths !== '' const prunePaths = rawPrunePaths !== ''
? rawPrunePaths.split(/ +/) ? safe.String_split.call(rawPrunePaths, / +/)
: []; : [];
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
? rawNeedlePaths.split(/ +/) ? safe.String_split.call(rawNeedlePaths, / +/)
: []; : [];
if ( stackNeedleDetails.matchAll !== true ) { if ( stackNeedleDetails.matchAll !== true ) {
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) { if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
@ -547,7 +549,7 @@ function matchesStackTrace(
// Normalize stack trace // Normalize stack trace
const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/; const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/;
const lines = []; const lines = [];
for ( let line of error.stack.split(/[\n\r]+/) ) { for ( let line of safe.String_split.call(error.stack, /[\n\r]+/) ) {
if ( line.includes(exceptionToken) ) { continue; } if ( line.includes(exceptionToken) ) { continue; }
line = line.trim(); line = line.trim();
const match = safe.RegExp_exec.call(reLine, line); const match = safe.RegExp_exec.call(reLine, line);
@ -594,8 +596,8 @@ function parsePropertiesToMatch(propsToMatch, implicit = '') {
const needles = new Map(); const needles = new Map();
if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; }
const options = { canNegate: true }; const options = { canNegate: true };
for ( const needle of propsToMatch.split(/\s+/) ) { for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) {
const [ prop, pattern ] = needle.split(':'); const [ prop, pattern ] = safe.String_split.call(needle, ':');
if ( prop === '' ) { continue; } if ( prop === '' ) { continue; }
if ( pattern !== undefined ) { if ( pattern !== undefined ) {
needles.set(prop, safe.initPattern(pattern, options)); needles.set(prop, safe.initPattern(pattern, options));
@ -1643,7 +1645,7 @@ function noFetchIf(
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType); const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType);
const needles = []; const needles = [];
for ( const condition of propsToMatch.split(/\s+/) ) { for ( const condition of safe.String_split.call(propsToMatch, /\s+/) ) {
if ( condition === '' ) { continue; } if ( condition === '' ) { continue; }
const pos = condition.indexOf(':'); const pos = condition.indexOf(':');
let key, value; let key, value;
@ -1797,7 +1799,7 @@ function removeClass(
if ( rawToken === '' ) { return; } if ( rawToken === '' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior); const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior);
const tokens = rawToken.split(/\s*\|\s*/); const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
const selector = tokens const selector = tokens
.map(a => `${rawSelector}.${CSS.escape(a)}`) .map(a => `${rawSelector}.${CSS.escape(a)}`)
.join(','); .join(',');
@ -2510,12 +2512,12 @@ function m3uPrune(
} }
text = before.trim() + '\n' + after.trim(); text = before.trim() + '\n' + after.trim();
reM3u.lastIndex = before.length + 1; reM3u.lastIndex = before.length + 1;
toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`)); toLog.push('Discarding', ...safe.String_split.call(discard, /\n+/).map(s => `\t${s}`));
if ( reM3u.global === false ) { break; } if ( reM3u.global === false ) { break; }
} }
return text; return text;
} }
const lines = text.split(/\n\r|\n|\r/); const lines = safe.String_split.call(text, /\n\r|\n|\r/);
for ( let i = 0; i < lines.length; i++ ) { for ( let i = 0; i < lines.length; i++ ) {
if ( lines[i] === undefined ) { continue; } if ( lines[i] === undefined ) { continue; }
if ( pruneSpliceoutBlock(lines, i) ) { continue; } if ( pruneSpliceoutBlock(lines, i) ) { continue; }
@ -2758,13 +2760,17 @@ function hrefSanitizer(
builtinScriptlets.push({ builtinScriptlets.push({
name: 'call-nothrow.js', name: 'call-nothrow.js',
fn: callNothrow, fn: callNothrow,
dependencies: [
'safe-self.fn',
],
}); });
function callNothrow( function callNothrow(
chain = '' chain = ''
) { ) {
if ( typeof chain !== 'string' ) { return; } if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; } if ( chain === '' ) { return; }
const parts = chain.split('.'); const safe = safeSelf();
const parts = safe.String_split.call(chain, '.');
let owner = window, prop; let owner = window, prop;
for (;;) { for (;;) {
prop = parts.shift(); prop = parts.shift();
@ -3095,7 +3101,7 @@ function trustedClickElement(
const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay); const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay);
if ( extraMatch !== '' ) { if ( extraMatch !== '' ) {
const assertions = extraMatch.split(',').map(s => { const assertions = safe.String_split.call(extraMatch, ',').map(s => {
const pos1 = s.indexOf(':'); const pos1 = s.indexOf(':');
const s1 = pos1 !== -1 ? s.slice(0, pos1) : s; const s1 = pos1 !== -1 ? s.slice(0, pos1) : s;
const not = s1.startsWith('!'); const not = s1.startsWith('!');
@ -3163,7 +3169,7 @@ function trustedClickElement(
return shadowRoot && querySelectorEx(inside, shadowRoot); return shadowRoot && querySelectorEx(inside, shadowRoot);
}; };
const selectorList = selectors.split(/\s*,\s*/) const selectorList = safe.String_split.call(selectors, /\s*,\s*/)
.filter(s => { .filter(s => {
try { try {
void querySelectorEx(s); void querySelectorEx(s);
@ -3290,10 +3296,10 @@ function trustedPruneInboundObject(
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
const needlePaths = []; const needlePaths = [];
if ( rawPrunePaths !== '' ) { if ( rawPrunePaths !== '' ) {
needlePaths.push(...rawPrunePaths.split(/ +/)); needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/));
} }
if ( rawNeedlePaths !== '' ) { if ( rawNeedlePaths !== '' ) {
needlePaths.push(...rawNeedlePaths.split(/ +/)); needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/));
} }
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
const mustProcess = root => { const mustProcess = root => {
@ -3455,7 +3461,7 @@ function trustedSuppressNativeMethod(
if ( stack !== '' ) { return; } if ( stack !== '' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how); const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how);
const signatureArgs = signature.split(/\s*\|\s*/).map(v => { const signatureArgs = safe.String_split.call(signature, /\s*\|\s*/).map(v => {
if ( /^".*"$/.test(v) ) { if ( /^".*"$/.test(v) ) {
return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) }; return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) };
} }

View file

@ -61,6 +61,7 @@ export function validateConstantFn(trusted, raw, extraArgs = {}) {
} else if ( raw.startsWith('{') && raw.endsWith('}') ) { } else if ( raw.startsWith('{') && raw.endsWith('}') ) {
try { value = safe.JSON_parse(raw).value; } catch(ex) { return; } try { value = safe.JSON_parse(raw).value; } catch(ex) { return; }
} }
return raw;
} else { } else {
return; return;
} }