mirror of https://github.com/gorhill/uBlock.git
Improve `prevent-fetch` scriptlet
Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2526 Improvements: Support fulfilling the response with the content of a `web_accessible_resources` resource, using the syntax already supported by `prevent-xhr`: `war:[name of resource]` Support fulfilling the response with randomized text with length specified using `length:min[-max]` directive.
This commit is contained in:
parent
74f54d0633
commit
6aeab2adbc
|
@ -52,6 +52,8 @@ function safeSelf() {
|
||||||
'Function_toStringFn': self.Function.prototype.toString,
|
'Function_toStringFn': self.Function.prototype.toString,
|
||||||
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
|
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
|
||||||
'Math_floor': Math.floor,
|
'Math_floor': Math.floor,
|
||||||
|
'Math_max': Math.max,
|
||||||
|
'Math_min': Math.min,
|
||||||
'Math_random': Math.random,
|
'Math_random': Math.random,
|
||||||
'Object_defineProperty': Object.defineProperty.bind(Object),
|
'Object_defineProperty': Object.defineProperty.bind(Object),
|
||||||
'RegExp': self.RegExp,
|
'RegExp': self.RegExp,
|
||||||
|
@ -242,6 +244,64 @@ function runAtHtmlElementFn(fn) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Reference:
|
||||||
|
// https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr
|
||||||
|
|
||||||
|
builtinScriptlets.push({
|
||||||
|
name: 'generate-content.fn',
|
||||||
|
fn: generateContentFn,
|
||||||
|
dependencies: [
|
||||||
|
'safe-self.fn',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function generateContentFn(directive) {
|
||||||
|
const safe = safeSelf();
|
||||||
|
const randomize = len => {
|
||||||
|
const chunks = [];
|
||||||
|
let textSize = 0;
|
||||||
|
do {
|
||||||
|
const s = safe.Math_random().toString(36).slice(2);
|
||||||
|
chunks.push(s);
|
||||||
|
textSize += s.length;
|
||||||
|
}
|
||||||
|
while ( textSize < len );
|
||||||
|
return chunks.join(' ').slice(0, len);
|
||||||
|
};
|
||||||
|
if ( directive === 'true' ) {
|
||||||
|
return Promise.resolve(randomize(10));
|
||||||
|
}
|
||||||
|
if ( directive.startsWith('length:') ) {
|
||||||
|
const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive);
|
||||||
|
if ( match ) {
|
||||||
|
const min = parseInt(match[1], 10);
|
||||||
|
const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min;
|
||||||
|
const len = safe.Math_min(min + extent * safe.Math_random(), 500000);
|
||||||
|
return Promise.resolve(randomize(len | 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( directive.startsWith('war:') && scriptletGlobals.has('warOrigin') ) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const warOrigin = scriptletGlobals.get('warOrigin');
|
||||||
|
const warName = directive.slice(4);
|
||||||
|
const fullpath = [ warOrigin, '/', warName ];
|
||||||
|
const warSecret = scriptletGlobals.get('warSecret');
|
||||||
|
if ( warSecret !== undefined ) {
|
||||||
|
fullpath.push('?secret=', warSecret);
|
||||||
|
}
|
||||||
|
const warXHR = new safe.XMLHttpRequest();
|
||||||
|
warXHR.responseType = 'text';
|
||||||
|
warXHR.onloadend = ev => {
|
||||||
|
resolve(ev.target.responseText || '');
|
||||||
|
};
|
||||||
|
warXHR.open('GET', fullpath.join(''));
|
||||||
|
warXHR.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
builtinScriptlets.push({
|
builtinScriptlets.push({
|
||||||
name: 'abort-current-script-core.fn',
|
name: 'abort-current-script-core.fn',
|
||||||
fn: abortCurrentScriptCore,
|
fn: abortCurrentScriptCore,
|
||||||
|
@ -1757,16 +1817,18 @@ builtinScriptlets.push({
|
||||||
],
|
],
|
||||||
fn: noFetchIf,
|
fn: noFetchIf,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
'generate-content.fn',
|
||||||
'safe-self.fn',
|
'safe-self.fn',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
function noFetchIf(
|
function noFetchIf(
|
||||||
arg1 = '',
|
propsToMatch = '',
|
||||||
|
directive = ''
|
||||||
) {
|
) {
|
||||||
if ( typeof arg1 !== 'string' ) { return; }
|
if ( typeof propsToMatch !== 'string' ) { return; }
|
||||||
const safe = safeSelf();
|
const safe = safeSelf();
|
||||||
const needles = [];
|
const needles = [];
|
||||||
for ( const condition of arg1.split(/\s+/) ) {
|
for ( const condition of propsToMatch.split(/\s+/) ) {
|
||||||
if ( condition === '' ) { continue; }
|
if ( condition === '' ) { continue; }
|
||||||
const pos = condition.indexOf(':');
|
const pos = condition.indexOf(':');
|
||||||
let key, value;
|
let key, value;
|
||||||
|
@ -1782,14 +1844,11 @@ function noFetchIf(
|
||||||
const log = needles.length === 0 ? console.log.bind(console) : undefined;
|
const log = needles.length === 0 ? console.log.bind(console) : undefined;
|
||||||
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
|
||||||
|
? args[0]
|
||||||
|
: Object.assign({ url: args[0] }, args[1]);
|
||||||
let proceed = true;
|
let proceed = true;
|
||||||
try {
|
try {
|
||||||
let details;
|
|
||||||
if ( args[0] instanceof self.Request ) {
|
|
||||||
details = args[0];
|
|
||||||
} else {
|
|
||||||
details = Object.assign({ url: args[0] }, args[1]);
|
|
||||||
}
|
|
||||||
const props = new Map();
|
const props = new Map();
|
||||||
for ( const prop in details ) {
|
for ( const prop in details ) {
|
||||||
let v = details[prop];
|
let v = details[prop];
|
||||||
|
@ -1818,9 +1877,21 @@ function noFetchIf(
|
||||||
}
|
}
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
return proceed
|
if ( proceed ) {
|
||||||
? Reflect.apply(target, thisArg, args)
|
return Reflect.apply(target, thisArg, args);
|
||||||
: Promise.resolve(new Response());
|
}
|
||||||
|
return generateContentFn(directive).then(text => {
|
||||||
|
const response = new Response(text, {
|
||||||
|
statusText: 'OK',
|
||||||
|
headers: {
|
||||||
|
'Content-Length': text.length,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(response, 'url', {
|
||||||
|
value: details.url
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2259,6 +2330,7 @@ builtinScriptlets.push({
|
||||||
],
|
],
|
||||||
fn: noXhrIf,
|
fn: noXhrIf,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
'generate-content.fn',
|
||||||
'match-object-properties.fn',
|
'match-object-properties.fn',
|
||||||
'parse-properties-to-match.fn',
|
'parse-properties-to-match.fn',
|
||||||
'safe-self.fn',
|
'safe-self.fn',
|
||||||
|
@ -2269,41 +2341,10 @@ function noXhrIf(
|
||||||
directive = ''
|
directive = ''
|
||||||
) {
|
) {
|
||||||
if ( typeof propsToMatch !== 'string' ) { return; }
|
if ( typeof propsToMatch !== 'string' ) { return; }
|
||||||
const safe = safeSelf();
|
|
||||||
const xhrInstances = new WeakMap();
|
const xhrInstances = new WeakMap();
|
||||||
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
||||||
const log = propNeedles.size === 0 ? console.log.bind(console) : undefined;
|
const log = propNeedles.size === 0 ? console.log.bind(console) : undefined;
|
||||||
const warOrigin = scriptletGlobals.get('warOrigin');
|
const warOrigin = scriptletGlobals.get('warOrigin');
|
||||||
const generateRandomString = len => {
|
|
||||||
let s = '';
|
|
||||||
do { s += safe.Math_random().toString(36).slice(2); }
|
|
||||||
while ( s.length < 10 );
|
|
||||||
return s.slice(0, len);
|
|
||||||
};
|
|
||||||
const generateContent = async directive => {
|
|
||||||
if ( directive === 'true' ) {
|
|
||||||
return generateRandomString(10);
|
|
||||||
}
|
|
||||||
if ( directive.startsWith('war:') ) {
|
|
||||||
if ( warOrigin === undefined ) { return ''; }
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const warName = directive.slice(4);
|
|
||||||
const fullpath = [ warOrigin, '/', warName ];
|
|
||||||
const warSecret = scriptletGlobals.get('warSecret');
|
|
||||||
if ( warSecret !== undefined ) {
|
|
||||||
fullpath.push('?secret=', warSecret);
|
|
||||||
}
|
|
||||||
const warXHR = new safe.XMLHttpRequest();
|
|
||||||
warXHR.responseType = 'text';
|
|
||||||
warXHR.onloadend = ev => {
|
|
||||||
resolve(ev.target.responseText || '');
|
|
||||||
};
|
|
||||||
warXHR.open('GET', fullpath.join(''));
|
|
||||||
warXHR.send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
||||||
open(method, url, ...args) {
|
open(method, url, ...args) {
|
||||||
if ( log !== undefined ) {
|
if ( log !== undefined ) {
|
||||||
|
@ -2370,7 +2411,7 @@ function noXhrIf(
|
||||||
default:
|
default:
|
||||||
if ( directive === '' ) { break; }
|
if ( directive === '' ) { break; }
|
||||||
promise = promise.then(details => {
|
promise = promise.then(details => {
|
||||||
return generateContent(details.directive).then(text => {
|
return generateContentFn(details.directive).then(text => {
|
||||||
details.props.response.value = text;
|
details.props.response.value = text;
|
||||||
details.props.responseText.value = text;
|
details.props.responseText.value = text;
|
||||||
return details;
|
return details;
|
||||||
|
|
Loading…
Reference in New Issue