mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 01:02:08 +01:00
Add trusted-replace-fetch-response
scriptlet
This scriplet requires a trusted source. Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2742 See AdGuard's documentation for usage: https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#trusted-replace-fetch-response
This commit is contained in:
parent
9f91335ae5
commit
82a7d11f78
1 changed files with 120 additions and 32 deletions
|
@ -53,6 +53,9 @@ function safeSelf() {
|
||||||
'RegExp_exec': self.RegExp.prototype.exec,
|
'RegExp_exec': self.RegExp.prototype.exec,
|
||||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||||
|
'fetch': self.fetch,
|
||||||
|
'jsonParse': self.JSON.parse.bind(self.JSON),
|
||||||
|
'jsonStringify': self.JSON.stringify.bind(self.JSON),
|
||||||
'log': console.log.bind(console),
|
'log': console.log.bind(console),
|
||||||
'uboLog': function(...args) {
|
'uboLog': function(...args) {
|
||||||
if ( args.length === 0 ) { return; }
|
if ( args.length === 0 ) { return; }
|
||||||
|
@ -103,10 +106,15 @@ builtinScriptlets.push({
|
||||||
function patternToRegex(pattern, flags = undefined) {
|
function patternToRegex(pattern, flags = undefined) {
|
||||||
if ( pattern === '' ) { return /^/; }
|
if ( pattern === '' ) { return /^/; }
|
||||||
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
||||||
if ( match !== null ) {
|
if ( match === null ) {
|
||||||
|
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
|
||||||
|
}
|
||||||
|
try {
|
||||||
return new RegExp(match[1], match[2] || flags);
|
return new RegExp(match[1], match[2] || flags);
|
||||||
}
|
}
|
||||||
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
|
catch(ex) {
|
||||||
|
}
|
||||||
|
return /^/;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -210,11 +218,14 @@ function runAtHtmlElement(fn) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
builtinScriptlets.push({
|
builtinScriptlets.push({
|
||||||
name: 'get-extra-args-entries.fn',
|
name: 'get-extra-args.fn',
|
||||||
fn: getExtraArgsEntries,
|
fn: getExtraArgs,
|
||||||
|
dependencies: [
|
||||||
|
'get-extra-args-entries.fn',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
function getExtraArgsEntries(args, offset) {
|
function getExtraArgs(args, offset = 0) {
|
||||||
return args.slice(offset).reduce((out, v, i, a) => {
|
const entries = args.slice(offset).reduce((out, v, i, a) => {
|
||||||
if ( (i & 1) === 0 ) {
|
if ( (i & 1) === 0 ) {
|
||||||
const rawValue = a[i+1];
|
const rawValue = a[i+1];
|
||||||
const value = /^\d+$/.test(rawValue)
|
const value = /^\d+$/.test(rawValue)
|
||||||
|
@ -224,28 +235,7 @@ function getExtraArgsEntries(args, offset) {
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
return Object.fromEntries(entries);
|
||||||
|
|
||||||
builtinScriptlets.push({
|
|
||||||
name: 'get-extra-args-map.fn',
|
|
||||||
fn: getExtraArgsMap,
|
|
||||||
dependencies: [
|
|
||||||
'get-extra-args-entries.fn',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
function getExtraArgsMap(args, offset = 0) {
|
|
||||||
return new Map(getExtraArgsEntries(args, offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
builtinScriptlets.push({
|
|
||||||
name: 'get-extra-args.fn',
|
|
||||||
fn: getExtraArgs,
|
|
||||||
dependencies: [
|
|
||||||
'get-extra-args-entries.fn',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
function getExtraArgs(args, offset = 0) {
|
|
||||||
return Object.fromEntries(getExtraArgsEntries(args, offset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -447,7 +437,7 @@ function setConstantCore(
|
||||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||||
} else if ( trusted ) {
|
} else if ( trusted ) {
|
||||||
if ( cValue.startsWith('{') && cValue.endsWith('}') ) {
|
if ( cValue.startsWith('{') && cValue.endsWith('}') ) {
|
||||||
try { cValue = JSON.parse(cValue).value; } catch(ex) { return; }
|
try { cValue = safe.jsonParse(cValue).value; } catch(ex) { return; }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
@ -2415,7 +2405,7 @@ function xmlPrune(
|
||||||
log(`xmlPrune: ${item.constructor.name}.${item.nodeName} removed`);
|
log(`xmlPrune: ${item.constructor.name}.${item.nodeName} removed`);
|
||||||
}
|
}
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
if ( log ) { safeSelf().uboLog(ex); }
|
log(ex);
|
||||||
}
|
}
|
||||||
return xmlDoc;
|
return xmlDoc;
|
||||||
};
|
};
|
||||||
|
@ -2438,13 +2428,12 @@ function xmlPrune(
|
||||||
if ( arg instanceof Request ) { return arg.url; }
|
if ( arg instanceof Request ) { return arg.url; }
|
||||||
return String(arg);
|
return String(arg);
|
||||||
};
|
};
|
||||||
const realFetch = self.fetch;
|
|
||||||
self.fetch = new Proxy(self.fetch, {
|
self.fetch = new Proxy(self.fetch, {
|
||||||
apply: function(target, thisArg, args) {
|
apply: function(target, thisArg, args) {
|
||||||
if ( reUrl.test(urlFromArg(args[0])) === false ) {
|
if ( reUrl.test(urlFromArg(args[0])) === false ) {
|
||||||
return Reflect.apply(target, thisArg, args);
|
return Reflect.apply(target, thisArg, args);
|
||||||
}
|
}
|
||||||
return realFetch(...args).then(realResponse =>
|
return safe.fetch(...args).then(realResponse =>
|
||||||
realResponse.text().then(text =>
|
realResponse.text().then(text =>
|
||||||
new Response(pruneFromText(text), {
|
new Response(pruneFromText(text), {
|
||||||
status: realResponse.status,
|
status: realResponse.status,
|
||||||
|
@ -3329,4 +3318,103 @@ function trustedSetLocalStorageItem(key = '', value = '') {
|
||||||
setLocalStorageItemCore('local', true, key, value);
|
setLocalStorageItemCore('local', true, key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* trusted-replace-fetch-response.js
|
||||||
|
*
|
||||||
|
* Replaces response text content of fetch requests if all given parameters
|
||||||
|
* match.
|
||||||
|
*
|
||||||
|
* Reference:
|
||||||
|
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-replace-fetch-response.js
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
builtinScriptlets.push({
|
||||||
|
name: 'trusted-replace-fetch-response.js',
|
||||||
|
requiresTrust: true,
|
||||||
|
fn: trustedReplaceFetchResponse,
|
||||||
|
dependencies: [
|
||||||
|
'get-extra-args.fn',
|
||||||
|
'pattern-to-regex.fn',
|
||||||
|
'safe-self.fn',
|
||||||
|
'should-log.fn',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function trustedReplaceFetchResponse(
|
||||||
|
pattern = '',
|
||||||
|
replacement = '',
|
||||||
|
propsToMatch = ''
|
||||||
|
) {
|
||||||
|
const safe = safeSelf();
|
||||||
|
const extraArgs = getExtraArgs(Array.from(arguments), 3);
|
||||||
|
const logLevel = shouldLog({
|
||||||
|
log: pattern === '' || extraArgs.log,
|
||||||
|
});
|
||||||
|
const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { });
|
||||||
|
if ( pattern === '*' ) { pattern = '.*'; }
|
||||||
|
const rePattern = patternToRegex(pattern);
|
||||||
|
const propNeedles = new Map();
|
||||||
|
for ( const needle of propsToMatch.split(/\s+/) ) {
|
||||||
|
const [ prop, value ] = needle.split(':');
|
||||||
|
if ( prop === '' ) { continue; }
|
||||||
|
propNeedles.set(prop, patternToRegex(value));
|
||||||
|
}
|
||||||
|
self.fetch = new Proxy(self.fetch, {
|
||||||
|
apply: function(target, thisArg, args) {
|
||||||
|
if ( logLevel === true ) {
|
||||||
|
log('trusted-replace-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1));
|
||||||
|
}
|
||||||
|
const fetchPromise = Reflect.apply(target, thisArg, args);
|
||||||
|
if ( pattern === '' ) { return fetchPromise; }
|
||||||
|
let skip = false;
|
||||||
|
if ( propNeedles.size !== 0 ) {
|
||||||
|
const fetchDetails = {};
|
||||||
|
if ( args[0] instanceof self.Request ) {
|
||||||
|
Object.assign(fetchDetails, args[0]);
|
||||||
|
} else {
|
||||||
|
Object.assign(fetchDetails, { url: args[0] });
|
||||||
|
}
|
||||||
|
if ( args[1] instanceof Object ) {
|
||||||
|
Object.assign(fetchDetails, args[1]);
|
||||||
|
}
|
||||||
|
for ( const prop of Object.keys(fetchDetails) ) {
|
||||||
|
let value = fetchDetails[prop];
|
||||||
|
if ( typeof value !== 'string' ) {
|
||||||
|
try { value = JSON.stringify(value); }
|
||||||
|
catch(ex) { }
|
||||||
|
}
|
||||||
|
if ( typeof value !== 'string' ) { continue; }
|
||||||
|
const needle = propNeedles.get(prop);
|
||||||
|
if ( needle === undefined ) { continue; }
|
||||||
|
if ( needle.test(value) ) { continue; }
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( skip ) { return fetchPromise; }
|
||||||
|
return fetchPromise.then(responseBefore => {
|
||||||
|
return responseBefore.text().then(textBefore => {
|
||||||
|
const textAfter = textBefore.replace(rePattern, replacement);
|
||||||
|
const responseAfter = new Response(textAfter, {
|
||||||
|
status: responseBefore.status,
|
||||||
|
statusText: responseBefore.statusText,
|
||||||
|
headers: responseBefore.headers,
|
||||||
|
});
|
||||||
|
Object.defineProperties(responseAfter, {
|
||||||
|
ok: { value: responseBefore.ok },
|
||||||
|
redirected: { value: responseBefore.redirected },
|
||||||
|
type: { value: responseBefore.type },
|
||||||
|
url: { value: responseBefore.url },
|
||||||
|
});
|
||||||
|
return responseAfter;
|
||||||
|
}).catch(reason => {
|
||||||
|
log('trusted-replace-fetch-response:', reason);
|
||||||
|
return responseBefore;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
Loading…
Reference in a new issue