From e785b99338ea364ed1b618cbc2b471ff065b15f3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 13 Jul 2024 11:02:54 -0400 Subject: [PATCH] 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 --- assets/resources/scriptlets.js | 51 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index d3aedadc2..f753ba5a8 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -57,6 +57,7 @@ function safeSelf() { 'Math_random': Math.random, 'Object': Object, 'Object_defineProperty': Object.defineProperty.bind(Object), + 'Object_defineProperties': Object.defineProperties.bind(Object), 'Object_fromEntries': Object.fromEntries.bind(Object), 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object), 'RegExp': self.RegExp, @@ -2067,11 +2068,11 @@ builtinScriptlets.push({ }); function noFetchIf( propsToMatch = '', - responseBody = '' + responseBody = '', + responseType = '' ) { - if ( typeof propsToMatch !== 'string' ) { return; } const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody); + const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType); const needles = []; for ( const condition of propsToMatch.split(/\s+/) ) { if ( condition === '' ) { continue; } @@ -2086,6 +2087,26 @@ function noFetchIf( } 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, { apply: function(target, thisArg, args) { const details = args[0] instanceof self.Request @@ -2123,17 +2144,6 @@ function noFetchIf( if ( proceed ) { 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 => { safe.uboLog(logPrefix, `Prevented with response "${text}"`); const response = new Response(text, { @@ -2142,14 +2152,11 @@ function noFetchIf( 'Content-Length': text.length, } }); - safe.Object_defineProperty(response, 'url', { - value: details.url - }); - if ( responseType !== '' ) { - safe.Object_defineProperty(response, 'type', { - value: responseType - }); - } + const props = Object.assign( + { url: { value: details.url } }, + responseProps + ); + safe.Object_defineProperties(response, props); return response; }); }