Skip to content

Commit 3417fe3

Browse files
committed
Improve trusted-replace-argument scriptlet
As discussed with filter list maintainers.
1 parent 36db7f8 commit 3417fe3

7 files changed

Lines changed: 63 additions & 33 deletions

File tree

src/js/resources/attribute.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export function removeAttr(
229229
if ( rawToken === '' ) { return; }
230230
const safe = safeSelf();
231231
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
232-
const tokens = rawToken.split(/\s*\|\s*/);
232+
const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
233233
const selector = tokens
234234
.map(a => `${rawSelector}[${CSS.escape(a)}]`)
235235
.join(',');
@@ -289,7 +289,7 @@ export function removeAttr(
289289
subtree: true,
290290
});
291291
};
292-
runAt(( ) => { start(); }, behavior.split(/\s+/));
292+
runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/));
293293
}
294294
registerScriptlet(removeAttr, {
295295
name: 'remove-attr.js',

src/js/resources/cookie.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ registerScriptlet(getSafeCookieValuesFn, {
5353
/******************************************************************************/
5454

5555
export function getAllCookiesFn() {
56-
return document.cookie.split(/\s*;\s*/).map(s => {
56+
const safe = safeSelf();
57+
return safe.String_split.call(document.cookie, /\s*;\s*/).map(s => {
5758
const pos = s.indexOf('=');
5859
if ( pos === 0 ) { return; }
5960
if ( pos === -1 ) { return `${s.trim()}=`; }
@@ -64,14 +65,18 @@ export function getAllCookiesFn() {
6465
}
6566
registerScriptlet(getAllCookiesFn, {
6667
name: 'get-all-cookies.fn',
68+
dependencies: [
69+
safeSelf,
70+
],
6771
});
6872

6973
/******************************************************************************/
7074

7175
export function getCookieFn(
7276
name = ''
7377
) {
74-
for ( const s of document.cookie.split(/\s*;\s*/) ) {
78+
const safe = safeSelf();
79+
for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) {
7580
const pos = s.indexOf('=');
7681
if ( pos === -1 ) { continue; }
7782
if ( s.slice(0, pos) !== name ) { continue; }
@@ -80,6 +85,9 @@ export function getCookieFn(
8085
}
8186
registerScriptlet(getCookieFn, {
8287
name: 'get-cookie.fn',
88+
dependencies: [
89+
safeSelf,
90+
],
8391
});
8492

8593
/******************************************************************************/
@@ -349,7 +357,7 @@ export function removeCookie(
349357
}, ms);
350358
};
351359
const remove = ( ) => {
352-
document.cookie.split(';').forEach(cookieStr => {
360+
safe.String_split.call(document.cookie, ';').forEach(cookieStr => {
353361
const pos = cookieStr.indexOf('=');
354362
if ( pos === -1 ) { return; }
355363
const cookieName = cookieStr.slice(0, pos).trim();
@@ -387,7 +395,7 @@ export function removeCookie(
387395
window.addEventListener('beforeunload', remove);
388396
if ( typeof extraArgs.when !== 'string' ) { return; }
389397
const supportedEventTypes = [ 'scroll', 'keydown' ];
390-
const eventTypes = extraArgs.when.split(/\s/);
398+
const eventTypes = safe.String_split.call(extraArgs.when, /\s/);
391399
for ( const type of eventTypes ) {
392400
if ( supportedEventTypes.includes(type) === false ) { continue; }
393401
document.addEventListener(type, ( ) => {

src/js/resources/proxy-apply.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function proxyApplyFn(
5252
}
5353
reflect() {
5454
const r = Reflect.construct(this.callFn, this.callArgs);
55-
this.callFn = this.callArgs = undefined;
55+
this.callFn = this.callArgs = this.private = undefined;
5656
proxyApplyFn.ctorContexts.push(this);
5757
return r;
5858
}
@@ -75,7 +75,7 @@ export function proxyApplyFn(
7575
}
7676
reflect() {
7777
const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs);
78-
this.callFn = this.thisArg = this.callArgs = undefined;
78+
this.callFn = this.thisArg = this.callArgs = this.private = undefined;
7979
proxyApplyFn.applyContexts.push(this);
8080
return r;
8181
}

src/js/resources/replace-argument.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,39 @@ export function trustedReplaceArgument(
7171
const reCondition = extraArgs.condition
7272
? safe.patternToRegex(extraArgs.condition)
7373
: /^/;
74-
proxyApplyFn(propChain, function(context) {
74+
const getArg = context => {
75+
if ( argposRaw === 'this' ) { return context.thisArg; }
7576
const { callArgs } = context;
76-
if ( argposRaw === '' ) {
77-
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
78-
return context.reflect();
79-
}
8077
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
81-
if ( argpos < 0 || argpos >= callArgs.length ) {
78+
if ( argpos < 0 || argpos >= callArgs.length ) { return; }
79+
context.private = { argpos };
80+
return callArgs[argpos];
81+
};
82+
const setArg = (context, value) => {
83+
if ( argposRaw === 'this' ) {
84+
if ( value !== context.thisArg ) {
85+
context.thisArg = value;
86+
}
87+
} else if ( context.private ) {
88+
context.callArgs[context.private.argpos] = value;
89+
}
90+
};
91+
proxyApplyFn(propChain, function(context) {
92+
if ( argposRaw === '' ) {
93+
safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`);
8294
return context.reflect();
8395
}
84-
const argBefore = callArgs[argpos];
96+
const argBefore = getArg(context);
8597
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) {
8698
return context.reflect();
8799
}
88100
const argAfter = replacer && typeof argBefore === 'string'
89101
? argBefore.replace(replacer.re, replacer.replacement)
90102
: value;
91-
callArgs[argpos] = argAfter;
92-
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`);
103+
if ( argAfter !== argBefore ) {
104+
setArg(context, argAfter);
105+
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`);
106+
}
93107
return context.reflect();
94108
});
95109
}

src/js/resources/safe-self.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function safeSelf() {
5151
'RegExp_exec': self.RegExp.prototype.exec,
5252
'Request_clone': self.Request.prototype.clone,
5353
'String_fromCharCode': String.fromCharCode,
54+
'String_split': String.prototype.split,
5455
'XMLHttpRequest': self.XMLHttpRequest,
5556
'addEventListener': self.EventTarget.prototype.addEventListener,
5657
'removeEventListener': self.EventTarget.prototype.removeEventListener,

src/js/resources/scriptlets.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ function abortCurrentScriptCore(
192192
const reContext = safe.patternToRegex(context);
193193
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
194194
const thisScript = document.currentScript;
195-
const chain = target.split('.');
195+
const chain = safe.String_split.call(target, '.');
196196
let owner = window;
197197
let prop;
198198
for (;;) {
@@ -406,6 +406,7 @@ builtinScriptlets.push({
406406
dependencies: [
407407
'matches-stack-trace.fn',
408408
'object-find-owner.fn',
409+
'safe-self.fn',
409410
],
410411
});
411412
// When no "prune paths" argument is provided, the scriptlet is
@@ -422,11 +423,12 @@ function objectPruneFn(
422423
extraArgs = {}
423424
) {
424425
if ( typeof rawPrunePaths !== 'string' ) { return; }
426+
const safe = safeSelf();
425427
const prunePaths = rawPrunePaths !== ''
426-
? rawPrunePaths.split(/ +/)
428+
? safe.String_split.call(rawPrunePaths, / +/)
427429
: [];
428430
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
429-
? rawNeedlePaths.split(/ +/)
431+
? safe.String_split.call(rawNeedlePaths, / +/)
430432
: [];
431433
if ( stackNeedleDetails.matchAll !== true ) {
432434
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
@@ -547,7 +549,7 @@ function matchesStackTrace(
547549
// Normalize stack trace
548550
const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/;
549551
const lines = [];
550-
for ( let line of error.stack.split(/[\n\r]+/) ) {
552+
for ( let line of safe.String_split.call(error.stack, /[\n\r]+/) ) {
551553
if ( line.includes(exceptionToken) ) { continue; }
552554
line = line.trim();
553555
const match = safe.RegExp_exec.call(reLine, line);
@@ -594,8 +596,8 @@ function parsePropertiesToMatch(propsToMatch, implicit = '') {
594596
const needles = new Map();
595597
if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; }
596598
const options = { canNegate: true };
597-
for ( const needle of propsToMatch.split(/\s+/) ) {
598-
const [ prop, pattern ] = needle.split(':');
599+
for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) {
600+
const [ prop, pattern ] = safe.String_split.call(needle, ':');
599601
if ( prop === '' ) { continue; }
600602
if ( pattern !== undefined ) {
601603
needles.set(prop, safe.initPattern(pattern, options));
@@ -1643,7 +1645,7 @@ function noFetchIf(
16431645
const safe = safeSelf();
16441646
const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType);
16451647
const needles = [];
1646-
for ( const condition of propsToMatch.split(/\s+/) ) {
1648+
for ( const condition of safe.String_split.call(propsToMatch, /\s+/) ) {
16471649
if ( condition === '' ) { continue; }
16481650
const pos = condition.indexOf(':');
16491651
let key, value;
@@ -1797,7 +1799,7 @@ function removeClass(
17971799
if ( rawToken === '' ) { return; }
17981800
const safe = safeSelf();
17991801
const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior);
1800-
const tokens = rawToken.split(/\s*\|\s*/);
1802+
const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
18011803
const selector = tokens
18021804
.map(a => `${rawSelector}.${CSS.escape(a)}`)
18031805
.join(',');
@@ -2510,12 +2512,12 @@ function m3uPrune(
25102512
}
25112513
text = before.trim() + '\n' + after.trim();
25122514
reM3u.lastIndex = before.length + 1;
2513-
toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`));
2515+
toLog.push('Discarding', ...safe.String_split.call(discard, /\n+/).map(s => `\t${s}`));
25142516
if ( reM3u.global === false ) { break; }
25152517
}
25162518
return text;
25172519
}
2518-
const lines = text.split(/\n\r|\n|\r/);
2520+
const lines = safe.String_split.call(text, /\n\r|\n|\r/);
25192521
for ( let i = 0; i < lines.length; i++ ) {
25202522
if ( lines[i] === undefined ) { continue; }
25212523
if ( pruneSpliceoutBlock(lines, i) ) { continue; }
@@ -2758,13 +2760,17 @@ function hrefSanitizer(
27582760
builtinScriptlets.push({
27592761
name: 'call-nothrow.js',
27602762
fn: callNothrow,
2763+
dependencies: [
2764+
'safe-self.fn',
2765+
],
27612766
});
27622767
function callNothrow(
27632768
chain = ''
27642769
) {
27652770
if ( typeof chain !== 'string' ) { return; }
27662771
if ( chain === '' ) { return; }
2767-
const parts = chain.split('.');
2772+
const safe = safeSelf();
2773+
const parts = safe.String_split.call(chain, '.');
27682774
let owner = window, prop;
27692775
for (;;) {
27702776
prop = parts.shift();
@@ -3095,7 +3101,7 @@ function trustedClickElement(
30953101
const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay);
30963102

30973103
if ( extraMatch !== '' ) {
3098-
const assertions = extraMatch.split(',').map(s => {
3104+
const assertions = safe.String_split.call(extraMatch, ',').map(s => {
30993105
const pos1 = s.indexOf(':');
31003106
const s1 = pos1 !== -1 ? s.slice(0, pos1) : s;
31013107
const not = s1.startsWith('!');
@@ -3163,7 +3169,7 @@ function trustedClickElement(
31633169
return shadowRoot && querySelectorEx(inside, shadowRoot);
31643170
};
31653171

3166-
const selectorList = selectors.split(/\s*,\s*/)
3172+
const selectorList = safe.String_split.call(selectors, /\s*,\s*/)
31673173
.filter(s => {
31683174
try {
31693175
void querySelectorEx(s);
@@ -3290,10 +3296,10 @@ function trustedPruneInboundObject(
32903296
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
32913297
const needlePaths = [];
32923298
if ( rawPrunePaths !== '' ) {
3293-
needlePaths.push(...rawPrunePaths.split(/ +/));
3299+
needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/));
32943300
}
32953301
if ( rawNeedlePaths !== '' ) {
3296-
needlePaths.push(...rawNeedlePaths.split(/ +/));
3302+
needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/));
32973303
}
32983304
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
32993305
const mustProcess = root => {
@@ -3455,7 +3461,7 @@ function trustedSuppressNativeMethod(
34553461
if ( stack !== '' ) { return; }
34563462
const safe = safeSelf();
34573463
const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how);
3458-
const signatureArgs = signature.split(/\s*\|\s*/).map(v => {
3464+
const signatureArgs = safe.String_split.call(signature, /\s*\|\s*/).map(v => {
34593465
if ( /^".*"$/.test(v) ) {
34603466
return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) };
34613467
}

src/js/resources/set-constant.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export function validateConstantFn(trusted, raw, extraArgs = {}) {
6161
} else if ( raw.startsWith('{') && raw.endsWith('}') ) {
6262
try { value = safe.JSON_parse(raw).value; } catch(ex) { return; }
6363
}
64+
return raw;
6465
} else {
6566
return;
6667
}

0 commit comments

Comments
 (0)