@@ -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