Add trusted-replace-fetch-response scriptlet · gorhill/uBlock@82a7d11 (original) (raw)
`@@ -53,6 +53,9 @@ function safeSelf() {
`
53
53
`'RegExp_exec': self.RegExp.prototype.exec,
`
54
54
`'addEventListener': self.EventTarget.prototype.addEventListener,
`
55
55
`'removeEventListener': self.EventTarget.prototype.removeEventListener,
`
``
56
`+
'fetch': self.fetch,
`
``
57
`+
'jsonParse': self.JSON.parse.bind(self.JSON),
`
``
58
`+
'jsonStringify': self.JSON.stringify.bind(self.JSON),
`
56
59
`'log': console.log.bind(console),
`
57
60
`'uboLog': function(...args) {
`
58
61
`if ( args.length === 0 ) { return; }
`
`@@ -103,10 +106,15 @@ builtinScriptlets.push({
`
103
106
`function patternToRegex(pattern, flags = undefined) {
`
104
107
`if ( pattern === '' ) { return /^/; }
`
105
108
`const match = /^/(.+)/([gimsu]*)$/.exec(pattern);
`
106
``
`-
if ( match !== null ) {
`
``
109
`+
if ( match === null ) {
`
``
110
`+
return new RegExp(pattern.replace(/[.*+?^${}()|[]\]/g, '\$&'), flags);
`
``
111
`+
}
`
``
112
`+
try {
`
107
113
`return new RegExp(match[1], match[2] || flags);
`
108
114
`}
`
109
``
`-
return new RegExp(pattern.replace(/[.*+?^${}()|[]\]/g, '\$&'), flags);
`
``
115
`+
catch(ex) {
`
``
116
`+
}
`
``
117
`+
return /^/;
`
110
118
`}
`
111
119
``
112
120
`/******************************************************************************/
`
`@@ -210,11 +218,14 @@ function runAtHtmlElement(fn) {
`
210
218
`/******************************************************************************/
`
211
219
``
212
220
`builtinScriptlets.push({
`
213
``
`-
name: 'get-extra-args-entries.fn',
`
214
``
`-
fn: getExtraArgsEntries,
`
``
221
`+
name: 'get-extra-args.fn',
`
``
222
`+
fn: getExtraArgs,
`
``
223
`+
dependencies: [
`
``
224
`+
'get-extra-args-entries.fn',
`
``
225
`+
],
`
215
226
`});
`
216
``
`-
function getExtraArgsEntries(args, offset) {
`
217
``
`-
return args.slice(offset).reduce((out, v, i, a) => {
`
``
227
`+
function getExtraArgs(args, offset = 0) {
`
``
228
`+
const entries = args.slice(offset).reduce((out, v, i, a) => {
`
218
229
`if ( (i & 1) === 0 ) {
`
219
230
`const rawValue = a[i+1];
`
220
231
`const value = /^\d+$/.test(rawValue)
`
`@@ -224,28 +235,7 @@ function getExtraArgsEntries(args, offset) {
`
224
235
`}
`
225
236
`return out;
`
226
237
`}, []);
`
227
``
`-
}
`
228
``
-
229
``
`-
builtinScriptlets.push({
`
230
``
`-
name: 'get-extra-args-map.fn',
`
231
``
`-
fn: getExtraArgsMap,
`
232
``
`-
dependencies: [
`
233
``
`-
'get-extra-args-entries.fn',
`
234
``
`-
],
`
235
``
`-
});
`
236
``
`-
function getExtraArgsMap(args, offset = 0) {
`
237
``
`-
return new Map(getExtraArgsEntries(args, offset));
`
238
``
`-
}
`
239
``
-
240
``
`-
builtinScriptlets.push({
`
241
``
`-
name: 'get-extra-args.fn',
`
242
``
`-
fn: getExtraArgs,
`
243
``
`-
dependencies: [
`
244
``
`-
'get-extra-args-entries.fn',
`
245
``
`-
],
`
246
``
`-
});
`
247
``
`-
function getExtraArgs(args, offset = 0) {
`
248
``
`-
return Object.fromEntries(getExtraArgsEntries(args, offset));
`
``
238
`+
return Object.fromEntries(entries);
`
249
239
`}
`
250
240
``
251
241
`/******************************************************************************/
`
`@@ -447,7 +437,7 @@ function setConstantCore(
`
447
437
`if ( Math.abs(cValue) > 0x7FFF ) { return; }
`
448
438
`} else if ( trusted ) {
`
449
439
`if ( cValue.startsWith('{') && cValue.endsWith('}') ) {
`
450
``
`-
try { cValue = JSON.parse(cValue).value; } catch(ex) { return; }
`
``
440
`+
try { cValue = safe.jsonParse(cValue).value; } catch(ex) { return; }
`
451
441
`}
`
452
442
`} else {
`
453
443
`return;
`
`@@ -2415,7 +2405,7 @@ function xmlPrune(
`
2415
2405
`` log(xmlPrune: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>i</mi><mi>t</mi><mi>e</mi><mi>m</mi><mi mathvariant="normal">.</mi><mi>c</mi><mi>o</mi><mi>n</mi><mi>s</mi><mi>t</mi><mi>r</mi><mi>u</mi><mi>c</mi><mi>t</mi><mi>o</mi><mi>r</mi><mi mathvariant="normal">.</mi><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">{item.constructor.name}.</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal">m</span><span class="mord">.</span><span class="mord mathnormal">co</span><span class="mord mathnormal">n</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">u</span><span class="mord mathnormal">c</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">or</span><span class="mord">.</span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span></span><span class="mord">.</span></span></span></span>{item.nodeName} removed);
``
2416
2406
`}
`
2417
2407
`} catch(ex) {
`
2418
``
`-
if ( log ) { safeSelf().uboLog(ex); }
`
``
2408
`+
log(ex);
`
2419
2409
`}
`
2420
2410
`return xmlDoc;
`
2421
2411
`};
`
`@@ -2438,13 +2428,12 @@ function xmlPrune(
`
2438
2428
`if ( arg instanceof Request ) { return arg.url; }
`
2439
2429
`return String(arg);
`
2440
2430
`};
`
2441
``
`-
const realFetch = self.fetch;
`
2442
2431
`self.fetch = new Proxy(self.fetch, {
`
2443
2432
`apply: function(target, thisArg, args) {
`
2444
2433
`if ( reUrl.test(urlFromArg(args[0])) === false ) {
`
2445
2434
`return Reflect.apply(target, thisArg, args);
`
2446
2435
`}
`
2447
``
`-
return realFetch(...args).then(realResponse =>
`
``
2436
`+
return safe.fetch(...args).then(realResponse =>
`
2448
2437
`realResponse.text().then(text =>
`
2449
2438
`new Response(pruneFromText(text), {
`
2450
2439
`status: realResponse.status,
`
`@@ -3329,4 +3318,103 @@ function trustedSetLocalStorageItem(key = '', value = '') {
`
3329
3318
`setLocalStorageItemCore('local', true, key, value);
`
3330
3319
`}
`
3331
3320
``
``
3321
`+
/*******************************************************************************
`
``
3322
`+
*
`
``
3323
`+
- trusted-replace-fetch-response.js
`
``
3324
`+
*
`
``
3325
`+
- Replaces response text content of fetch requests if all given parameters
`
``
3326
`+
- match.
`
``
3327
`+
*
`
``
3328
`+
- Reference:
`
``
3329
`+
`
``
3330
`+
*
`
``
3331
`+
**/
`
``
3332
+
``
3333
`+
builtinScriptlets.push({
`
``
3334
`+
name: 'trusted-replace-fetch-response.js',
`
``
3335
`+
requiresTrust: true,
`
``
3336
`+
fn: trustedReplaceFetchResponse,
`
``
3337
`+
dependencies: [
`
``
3338
`+
'get-extra-args.fn',
`
``
3339
`+
'pattern-to-regex.fn',
`
``
3340
`+
'safe-self.fn',
`
``
3341
`+
'should-log.fn',
`
``
3342
`+
],
`
``
3343
`+
});
`
``
3344
`+
function trustedReplaceFetchResponse(
`
``
3345
`+
pattern = '',
`
``
3346
`+
replacement = '',
`
``
3347
`+
propsToMatch = ''
`
``
3348
`+
) {
`
``
3349
`+
const safe = safeSelf();
`
``
3350
`+
const extraArgs = getExtraArgs(Array.from(arguments), 3);
`
``
3351
`+
const logLevel = shouldLog({
`
``
3352
`+
log: pattern === '' || extraArgs.log,
`
``
3353
`+
});
`
``
3354
`+
const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
`
``
3355
`+
if ( pattern === '' ) { pattern = '.'; }
`
``
3356
`+
const rePattern = patternToRegex(pattern);
`
``
3357
`+
const propNeedles = new Map();
`
``
3358
`+
for ( const needle of propsToMatch.split(/\s+/) ) {
`
``
3359
`+
const [ prop, value ] = needle.split(':');
`
``
3360
`+
if ( prop === '' ) { continue; }
`
``
3361
`+
propNeedles.set(prop, patternToRegex(value));
`
``
3362
`+
}
`
``
3363
`+
self.fetch = new Proxy(self.fetch, {
`
``
3364
`+
apply: function(target, thisArg, args) {
`
``
3365
`+
if ( logLevel === true ) {
`
``
3366
`+
log('trusted-replace-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1));
`
``
3367
`+
}
`
``
3368
`+
const fetchPromise = Reflect.apply(target, thisArg, args);
`
``
3369
`+
if ( pattern === '' ) { return fetchPromise; }
`
``
3370
`+
let skip = false;
`
``
3371
`+
if ( propNeedles.size !== 0 ) {
`
``
3372
`+
const fetchDetails = {};
`
``
3373
`+
if ( args[0] instanceof self.Request ) {
`
``
3374
`+
Object.assign(fetchDetails, args[0]);
`
``
3375
`+
} else {
`
``
3376
`+
Object.assign(fetchDetails, { url: args[0] });
`
``
3377
`+
}
`
``
3378
`+
if ( args[1] instanceof Object ) {
`
``
3379
`+
Object.assign(fetchDetails, args[1]);
`
``
3380
`+
}
`
``
3381
`+
for ( const prop of Object.keys(fetchDetails) ) {
`
``
3382
`+
let value = fetchDetails[prop];
`
``
3383
`+
if ( typeof value !== 'string' ) {
`
``
3384
`+
try { value = JSON.stringify(value); }
`
``
3385
`+
catch(ex) { }
`
``
3386
`+
}
`
``
3387
`+
if ( typeof value !== 'string' ) { continue; }
`
``
3388
`+
const needle = propNeedles.get(prop);
`
``
3389
`+
if ( needle === undefined ) { continue; }
`
``
3390
`+
if ( needle.test(value) ) { continue; }
`
``
3391
`+
skip = true;
`
``
3392
`+
break;
`
``
3393
`+
}
`
``
3394
`+
}
`
``
3395
`+
if ( skip ) { return fetchPromise; }
`
``
3396
`+
return fetchPromise.then(responseBefore => {
`
``
3397
`+
return responseBefore.text().then(textBefore => {
`
``
3398
`+
const textAfter = textBefore.replace(rePattern, replacement);
`
``
3399
`+
const responseAfter = new Response(textAfter, {
`
``
3400
`+
status: responseBefore.status,
`
``
3401
`+
statusText: responseBefore.statusText,
`
``
3402
`+
headers: responseBefore.headers,
`
``
3403
`+
});
`
``
3404
`+
Object.defineProperties(responseAfter, {
`
``
3405
`+
ok: { value: responseBefore.ok },
`
``
3406
`+
redirected: { value: responseBefore.redirected },
`
``
3407
`+
type: { value: responseBefore.type },
`
``
3408
`+
url: { value: responseBefore.url },
`
``
3409
`+
});
`
``
3410
`+
return responseAfter;
`
``
3411
`+
}).catch(reason => {
`
``
3412
`+
log('trusted-replace-fetch-response:', reason);
`
``
3413
`+
return responseBefore;
`
``
3414
`+
});
`
``
3415
`+
});
`
``
3416
`+
}
`
``
3417
`+
});
`
``
3418
`+
}
`
``
3419
+
3332
3420
`/******************************************************************************/
`