@@ -52,6 +52,8 @@ function safeSelf() {
5252 'Function_toStringFn' : self . Function . prototype . toString ,
5353 'Function_toString' : thisArg => safe . Function_toStringFn . call ( thisArg ) ,
5454 'Math_floor' : Math . floor ,
55+ 'Math_max' : Math . max ,
56+ 'Math_min' : Math . min ,
5557 'Math_random' : Math . random ,
5658 'Object_defineProperty' : Object . defineProperty . bind ( Object ) ,
5759 'RegExp' : self . RegExp ,
@@ -242,6 +244,64 @@ function runAtHtmlElementFn(fn) {
242244
243245/******************************************************************************/
244246
247+ // Reference:
248+ // https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr
249+
250+ builtinScriptlets . push ( {
251+ name : 'generate-content.fn' ,
252+ fn : generateContentFn ,
253+ dependencies : [
254+ 'safe-self.fn' ,
255+ ] ,
256+ } ) ;
257+ function generateContentFn ( directive ) {
258+ const safe = safeSelf ( ) ;
259+ const randomize = len => {
260+ const chunks = [ ] ;
261+ let textSize = 0 ;
262+ do {
263+ const s = safe . Math_random ( ) . toString ( 36 ) . slice ( 2 ) ;
264+ chunks . push ( s ) ;
265+ textSize += s . length ;
266+ }
267+ while ( textSize < len ) ;
268+ return chunks . join ( ' ' ) . slice ( 0 , len ) ;
269+ } ;
270+ if ( directive === 'true' ) {
271+ return Promise . resolve ( randomize ( 10 ) ) ;
272+ }
273+ if ( directive . startsWith ( 'length:' ) ) {
274+ const match = / ^ l e n g t h : ( \d + ) (?: - ( \d + ) ) ? $ / . exec ( directive ) ;
275+ if ( match ) {
276+ const min = parseInt ( match [ 1 ] , 10 ) ;
277+ const extent = safe . Math_max ( parseInt ( match [ 2 ] , 10 ) || 0 , min ) - min ;
278+ const len = safe . Math_min ( min + extent * safe . Math_random ( ) , 500000 ) ;
279+ return Promise . resolve ( randomize ( len | 0 ) ) ;
280+ }
281+ }
282+ if ( directive . startsWith ( 'war:' ) && scriptletGlobals . has ( 'warOrigin' ) ) {
283+ return new Promise ( resolve => {
284+ const warOrigin = scriptletGlobals . get ( 'warOrigin' ) ;
285+ const warName = directive . slice ( 4 ) ;
286+ const fullpath = [ warOrigin , '/' , warName ] ;
287+ const warSecret = scriptletGlobals . get ( 'warSecret' ) ;
288+ if ( warSecret !== undefined ) {
289+ fullpath . push ( '?secret=' , warSecret ) ;
290+ }
291+ const warXHR = new safe . XMLHttpRequest ( ) ;
292+ warXHR . responseType = 'text' ;
293+ warXHR . onloadend = ev => {
294+ resolve ( ev . target . responseText || '' ) ;
295+ } ;
296+ warXHR . open ( 'GET' , fullpath . join ( '' ) ) ;
297+ warXHR . send ( ) ;
298+ } ) ;
299+ }
300+ return Promise . resolve ( '' ) ;
301+ }
302+
303+ /******************************************************************************/
304+
245305builtinScriptlets . push ( {
246306 name : 'abort-current-script-core.fn' ,
247307 fn : abortCurrentScriptCore ,
@@ -1757,16 +1817,18 @@ builtinScriptlets.push({
17571817 ] ,
17581818 fn : noFetchIf ,
17591819 dependencies : [
1820+ 'generate-content.fn' ,
17601821 'safe-self.fn' ,
17611822 ] ,
17621823} ) ;
17631824function noFetchIf (
1764- arg1 = '' ,
1825+ propsToMatch = '' ,
1826+ directive = ''
17651827) {
1766- if ( typeof arg1 !== 'string' ) { return ; }
1828+ if ( typeof propsToMatch !== 'string' ) { return ; }
17671829 const safe = safeSelf ( ) ;
17681830 const needles = [ ] ;
1769- for ( const condition of arg1 . split ( / \s + / ) ) {
1831+ for ( const condition of propsToMatch . split ( / \s + / ) ) {
17701832 if ( condition === '' ) { continue ; }
17711833 const pos = condition . indexOf ( ':' ) ;
17721834 let key , value ;
@@ -1782,14 +1844,11 @@ function noFetchIf(
17821844 const log = needles . length === 0 ? console . log . bind ( console ) : undefined ;
17831845 self . fetch = new Proxy ( self . fetch , {
17841846 apply : function ( target , thisArg , args ) {
1847+ const details = args [ 0 ] instanceof self . Request
1848+ ? args [ 0 ]
1849+ : Object . assign ( { url : args [ 0 ] } , args [ 1 ] ) ;
17851850 let proceed = true ;
17861851 try {
1787- let details ;
1788- if ( args [ 0 ] instanceof self . Request ) {
1789- details = args [ 0 ] ;
1790- } else {
1791- details = Object . assign ( { url : args [ 0 ] } , args [ 1 ] ) ;
1792- }
17931852 const props = new Map ( ) ;
17941853 for ( const prop in details ) {
17951854 let v = details [ prop ] ;
@@ -1818,9 +1877,21 @@ function noFetchIf(
18181877 }
18191878 } catch ( ex ) {
18201879 }
1821- return proceed
1822- ? Reflect . apply ( target , thisArg , args )
1823- : Promise . resolve ( new Response ( ) ) ;
1880+ if ( proceed ) {
1881+ return Reflect . apply ( target , thisArg , args ) ;
1882+ }
1883+ return generateContentFn ( directive ) . then ( text => {
1884+ const response = new Response ( text , {
1885+ statusText : 'OK' ,
1886+ headers : {
1887+ 'Content-Length' : text . length ,
1888+ }
1889+ } ) ;
1890+ Object . defineProperty ( response , 'url' , {
1891+ value : details . url
1892+ } ) ;
1893+ return response ;
1894+ } ) ;
18241895 }
18251896 } ) ;
18261897}
@@ -2259,6 +2330,7 @@ builtinScriptlets.push({
22592330 ] ,
22602331 fn : noXhrIf ,
22612332 dependencies : [
2333+ 'generate-content.fn' ,
22622334 'match-object-properties.fn' ,
22632335 'parse-properties-to-match.fn' ,
22642336 'safe-self.fn' ,
@@ -2269,41 +2341,10 @@ function noXhrIf(
22692341 directive = ''
22702342) {
22712343 if ( typeof propsToMatch !== 'string' ) { return ; }
2272- const safe = safeSelf ( ) ;
22732344 const xhrInstances = new WeakMap ( ) ;
22742345 const propNeedles = parsePropertiesToMatch ( propsToMatch , 'url' ) ;
22752346 const log = propNeedles . size === 0 ? console . log . bind ( console ) : undefined ;
22762347 const warOrigin = scriptletGlobals . get ( 'warOrigin' ) ;
2277- const generateRandomString = len => {
2278- let s = '' ;
2279- do { s += safe . Math_random ( ) . toString ( 36 ) . slice ( 2 ) ; }
2280- while ( s . length < 10 ) ;
2281- return s . slice ( 0 , len ) ;
2282- } ;
2283- const generateContent = async directive => {
2284- if ( directive === 'true' ) {
2285- return generateRandomString ( 10 ) ;
2286- }
2287- if ( directive . startsWith ( 'war:' ) ) {
2288- if ( warOrigin === undefined ) { return '' ; }
2289- return new Promise ( resolve => {
2290- const warName = directive . slice ( 4 ) ;
2291- const fullpath = [ warOrigin , '/' , warName ] ;
2292- const warSecret = scriptletGlobals . get ( 'warSecret' ) ;
2293- if ( warSecret !== undefined ) {
2294- fullpath . push ( '?secret=' , warSecret ) ;
2295- }
2296- const warXHR = new safe . XMLHttpRequest ( ) ;
2297- warXHR . responseType = 'text' ;
2298- warXHR . onloadend = ev => {
2299- resolve ( ev . target . responseText || '' ) ;
2300- } ;
2301- warXHR . open ( 'GET' , fullpath . join ( '' ) ) ;
2302- warXHR . send ( ) ;
2303- } ) ;
2304- }
2305- return '' ;
2306- } ;
23072348 self . XMLHttpRequest = class extends self . XMLHttpRequest {
23082349 open ( method , url , ...args ) {
23092350 if ( log !== undefined ) {
@@ -2370,7 +2411,7 @@ function noXhrIf(
23702411 default :
23712412 if ( directive === '' ) { break ; }
23722413 promise = promise . then ( details => {
2373- return generateContent ( details . directive ) . then ( text => {
2414+ return generateContentFn ( details . directive ) . then ( text => {
23742415 details . props . response . value = text ;
23752416 details . props . responseText . value = text ;
23762417 return details ;
0 commit comments