Improve `prevent-fetch` scriptlet

Related discussion:
https://github.com/uBlockOrigin/uBlock-discussions/discussions/848#discussioncomment-10027757

Added support for AdGuard's `responseType` parameter. Extended the
meaning of that 3rd parameter to also be a JSON string with
properties to set on the returned response instance. Currently
supported properties:

- `ok`, supported values: `false`, `true`
- `type, supported values: `"basic"`, `"cors"`, `"opaque"`

Reference:
https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#-%EF%B8%8F-prevent-fetch
This commit is contained in:
Raymond Hill 2024-07-13 11:02:54 -04:00
parent a54d416143
commit e785b99338
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
1 changed files with 29 additions and 22 deletions

View File

@ -57,6 +57,7 @@ function safeSelf() {
'Math_random': Math.random, 'Math_random': Math.random,
'Object': Object, 'Object': Object,
'Object_defineProperty': Object.defineProperty.bind(Object), 'Object_defineProperty': Object.defineProperty.bind(Object),
'Object_defineProperties': Object.defineProperties.bind(Object),
'Object_fromEntries': Object.fromEntries.bind(Object), 'Object_fromEntries': Object.fromEntries.bind(Object),
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
'RegExp': self.RegExp, 'RegExp': self.RegExp,
@ -2067,11 +2068,11 @@ builtinScriptlets.push({
}); });
function noFetchIf( function noFetchIf(
propsToMatch = '', propsToMatch = '',
responseBody = '' responseBody = '',
responseType = ''
) { ) {
if ( typeof propsToMatch !== 'string' ) { return; }
const safe = safeSelf(); const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody); const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType);
const needles = []; const needles = [];
for ( const condition of propsToMatch.split(/\s+/) ) { for ( const condition of propsToMatch.split(/\s+/) ) {
if ( condition === '' ) { continue; } if ( condition === '' ) { continue; }
@ -2086,6 +2087,26 @@ function noFetchIf(
} }
needles.push({ key, re: safe.patternToRegex(value) }); needles.push({ key, re: safe.patternToRegex(value) });
} }
const validResponseProps = {
ok: [ false, true ],
type: [ 'basic', 'cors', 'opaque' ],
};
let responseProps;
if ( /^\{.*\}$/.test(responseType) ) {
responseProps = {};
try {
Object.entries(JSON.parse(responseType)).forEach(([ p, v ]) => {
if ( validResponseProps[p] === undefined ) { return; }
if ( validResponseProps[p].includes(v) === false ) { return; }
responseProps[p] = { value: v };
});
}
catch(ex) {}
} else if ( responseType !== '' ) {
if ( validResponseProps.type.includes(responseType) ) {
responseProps = { type: { value: responseType } };
}
}
self.fetch = new Proxy(self.fetch, { self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) { apply: function(target, thisArg, args) {
const details = args[0] instanceof self.Request const details = args[0] instanceof self.Request
@ -2123,17 +2144,6 @@ function noFetchIf(
if ( proceed ) { if ( proceed ) {
return Reflect.apply(target, thisArg, args); return Reflect.apply(target, thisArg, args);
} }
let responseType = '';
if ( details.mode === undefined || details.mode === 'cors' ) {
try {
const desURL = new URL(details.url);
responseType = desURL.origin !== document.location.origin
? 'cors'
: 'basic';
} catch(ex) {
safe.uboErr(logPrefix, `Error: ${ex}`);
}
}
return generateContentFn(responseBody).then(text => { return generateContentFn(responseBody).then(text => {
safe.uboLog(logPrefix, `Prevented with response "${text}"`); safe.uboLog(logPrefix, `Prevented with response "${text}"`);
const response = new Response(text, { const response = new Response(text, {
@ -2142,14 +2152,11 @@ function noFetchIf(
'Content-Length': text.length, 'Content-Length': text.length,
} }
}); });
safe.Object_defineProperty(response, 'url', { const props = Object.assign(
value: details.url { url: { value: details.url } },
}); responseProps
if ( responseType !== '' ) { );
safe.Object_defineProperty(response, 'type', { safe.Object_defineProperties(response, props);
value: responseType
});
}
return response; return response;
}); });
} }