Skip to content

Commit adced29

Browse files
committed
Improve trusted-replace-argument scriptlet
As discussed with filter list maintainers, added ability to partially replace an argument using the `repl:` prefix. Updated documentation: --- @Scriptlet trusted-replace-argument.js @description Replace an argument passed to a method. Requires a trusted source. @param propChain The property chain to the function which argument must be replaced when called. @param argposRaw The zero-based position of the argument in the argument list. Use a negative number for a position relative to the last argument. @param argraw The replacement value, validated using the same heuristic as with the `set-constant.js` scriptlet. If the replacement value matches `json:...`, the value will be the json-parsed string after `json:`. If the replacement value matches `repl:/.../.../`, the target argument will be replaced according the regex-replacement directive following `repl:` @param [, condition, pattern] Optional. The replacement will occur only when pattern matches the target argument. --- Aditionally, more scriptlets moved into their own files.
1 parent e43cb67 commit adced29

16 files changed

+723
-516
lines changed

assets/resources/attribute.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
import { registerScriptlet } from './base.js';

assets/resources/base.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
export const registeredScriptlets = [];

assets/resources/cookie.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
import { registerScriptlet } from './base.js';

assets/resources/localstorage.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
import { getSafeCookieValuesFn } from './cookie.js';

assets/resources/parse-replace.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
*/
22+
23+
import { createArglistParser } from './shared.js';
24+
import { registerScriptlet } from './base.js';
25+
26+
/******************************************************************************/
27+
28+
export function parseReplaceFn(s) {
29+
if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; }
30+
const parser = createArglistParser('/');
31+
parser.nextArg(s, 1);
32+
let pattern = s.slice(parser.argBeg, parser.argEnd);
33+
if ( parser.transform ) {
34+
pattern = parser.normalizeArg(pattern);
35+
}
36+
if ( pattern === '' ) { return; }
37+
parser.nextArg(s, parser.separatorEnd);
38+
let replacement = s.slice(parser.argBeg, parser.argEnd);
39+
if ( parser.separatorEnd === parser.separatorBeg ) { return; }
40+
if ( parser.transform ) {
41+
replacement = parser.normalizeArg(replacement);
42+
}
43+
const flags = s.slice(parser.separatorEnd);
44+
try {
45+
return { re: new RegExp(pattern, flags), replacement };
46+
} catch(_) {
47+
}
48+
}
49+
registerScriptlet(parseReplaceFn, {
50+
name: 'parse-replace.fn',
51+
dependencies: [
52+
createArglistParser,
53+
],
54+
});

assets/resources/proxy-apply.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
*/
22+
23+
import { registerScriptlet } from './base.js';
24+
25+
/******************************************************************************/
26+
27+
export function proxyApplyFn(
28+
target = '',
29+
handler = ''
30+
) {
31+
let context = globalThis;
32+
let prop = target;
33+
for (;;) {
34+
const pos = prop.indexOf('.');
35+
if ( pos === -1 ) { break; }
36+
context = context[prop.slice(0, pos)];
37+
if ( context instanceof Object === false ) { return; }
38+
prop = prop.slice(pos+1);
39+
}
40+
const fn = context[prop];
41+
if ( typeof fn !== 'function' ) { return; }
42+
if ( proxyApplyFn.CtorContext === undefined ) {
43+
proxyApplyFn.ctorContexts = [];
44+
proxyApplyFn.CtorContext = class {
45+
constructor(...args) {
46+
this.init(...args);
47+
}
48+
init(callFn, callArgs) {
49+
this.callFn = callFn;
50+
this.callArgs = callArgs;
51+
return this;
52+
}
53+
reflect() {
54+
const r = Reflect.construct(this.callFn, this.callArgs);
55+
this.callFn = this.callArgs = undefined;
56+
proxyApplyFn.ctorContexts.push(this);
57+
return r;
58+
}
59+
static factory(...args) {
60+
return proxyApplyFn.ctorContexts.length !== 0
61+
? proxyApplyFn.ctorContexts.pop().init(...args)
62+
: new proxyApplyFn.CtorContext(...args);
63+
}
64+
};
65+
proxyApplyFn.applyContexts = [];
66+
proxyApplyFn.ApplyContext = class {
67+
constructor(...args) {
68+
this.init(...args);
69+
}
70+
init(callFn, thisArg, callArgs) {
71+
this.callFn = callFn;
72+
this.thisArg = thisArg;
73+
this.callArgs = callArgs;
74+
return this;
75+
}
76+
reflect() {
77+
const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs);
78+
this.callFn = this.thisArg = this.callArgs = undefined;
79+
proxyApplyFn.applyContexts.push(this);
80+
return r;
81+
}
82+
static factory(...args) {
83+
return proxyApplyFn.applyContexts.length !== 0
84+
? proxyApplyFn.applyContexts.pop().init(...args)
85+
: new proxyApplyFn.ApplyContext(...args);
86+
}
87+
};
88+
}
89+
const fnStr = fn.toString();
90+
const toString = (function toString() { return fnStr; }).bind(null);
91+
const proxyDetails = {
92+
apply(target, thisArg, args) {
93+
return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args));
94+
},
95+
get(target, prop) {
96+
if ( prop === 'toString' ) { return toString; }
97+
return Reflect.get(target, prop);
98+
},
99+
};
100+
if ( fn.prototype?.constructor === fn ) {
101+
proxyDetails.construct = function(target, args) {
102+
return handler(proxyApplyFn.CtorContext.factory(target, args));
103+
};
104+
}
105+
context[prop] = new Proxy(fn, proxyDetails);
106+
}
107+
registerScriptlet(proxyApplyFn, {
108+
name: 'proxy-apply.fn',
109+
});

assets/resources/replace-argument.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
*/
22+
23+
import { parseReplaceFn } from './parse-replace.js';
24+
import { proxyApplyFn } from './proxy-apply.js';
25+
import { registerScriptlet } from './base.js';
26+
import { safeSelf } from './safe-self.js';
27+
import { validateConstantFn } from './set-constant.js';
28+
29+
/**
30+
* @scriptlet trusted-replace-argument.js
31+
*
32+
* @description
33+
* Replace an argument passed to a method. Requires a trusted source.
34+
*
35+
* @param propChain
36+
* The property chain to the function which argument must be replaced when
37+
* called.
38+
*
39+
* @param argposRaw
40+
* The zero-based position of the argument in the argument list. Use a negative
41+
* number for a position relative to the last argument.
42+
*
43+
* @param argraw
44+
* The replacement value, validated using the same heuristic as with the
45+
* `set-constant.js` scriptlet.
46+
* If the replacement value matches `json:...`, the value will be the
47+
* json-parsed string after `json:`.
48+
* If the replacement value matches `repl:/.../.../`, the target argument will
49+
* be replaced according the regex-replacement directive following `repl:`
50+
*
51+
* @param [, condition, pattern]
52+
* Optional. The replacement will occur only when pattern matches the target
53+
* argument.
54+
*
55+
* */
56+
57+
export function trustedReplaceArgument(
58+
propChain = '',
59+
argposRaw = '',
60+
argraw = ''
61+
) {
62+
if ( propChain === '' ) { return; }
63+
const safe = safeSelf();
64+
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw);
65+
const argoffset = parseInt(argposRaw, 10) || 0;
66+
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
67+
const replacer = argraw.startsWith('repl:/') &&
68+
parseReplaceFn(argraw.slice(5)) || undefined;
69+
const value = replacer === undefined &&
70+
validateConstantFn(true, argraw, extraArgs) || undefined;
71+
const reCondition = extraArgs.condition
72+
? safe.patternToRegex(extraArgs.condition)
73+
: /^/;
74+
proxyApplyFn(propChain, function(context) {
75+
const { callArgs } = context;
76+
if ( argposRaw === '' ) {
77+
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
78+
return context.reflect();
79+
}
80+
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
81+
if ( argpos < 0 || argpos >= callArgs.length ) {
82+
return context.reflect();
83+
}
84+
const argBefore = callArgs[argpos];
85+
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) {
86+
return context.reflect();
87+
}
88+
const argAfter = replacer && typeof argBefore === 'string'
89+
? argBefore.replace(replacer.re, replacer.replacement)
90+
: value;
91+
callArgs[argpos] = argAfter;
92+
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`);
93+
return context.reflect();
94+
});
95+
}
96+
registerScriptlet(trustedReplaceArgument, {
97+
name: 'trusted-replace-argument.js',
98+
requiresTrust: true,
99+
dependencies: [
100+
parseReplaceFn,
101+
proxyApplyFn,
102+
safeSelf,
103+
validateConstantFn,
104+
],
105+
});

assets/resources/run-at.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
import { registerScriptlet } from './base.js';

assets/resources/safe-self.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
1919
Home: https://github.com/gorhill/uBlock
2020
21-
The scriptlets below are meant to be injected only into a
22-
web page context.
2321
*/
2422

2523
import { registerScriptlet } from './base.js';

0 commit comments

Comments
 (0)