@@ -780,7 +780,7 @@ export class AstFilterParser {
780780 this . reBadHostnameChars = / [ \x00 - \x24 \x26 - \x29 \x2b \x2c \x2f \x3b - \x40 \x5c \x5e \x60 \x7b - \x7f ] / ;
781781 this . reIsEntity = / ^ [ ^ * ] + \. \* $ / ;
782782 this . rePreparseDirectiveIf = / ^ ! # i f / ;
783- this . rePreparseDirectiveAny = / ^ ! # (?: e n d i f | i f | i n c l u d e ) / ;
783+ this . rePreparseDirectiveAny = / ^ ! # (?: e l s e | e n d i f | i f | i n c l u d e ) / ;
784784 this . reURL = / \b h t t p s ? : \/ \/ \S + / ;
785785 this . reHasPatternSpecialChars = / [ \* \^ ] / ;
786786 this . rePatternAllSpecialChars = / [ \* \^ ] + | [ ^ \x00 - \x7f ] + / g;
@@ -1122,10 +1122,7 @@ export class AstFilterParser {
11221122 this . linkRight ( head , next ) ;
11231123 if ( type === NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE ) {
11241124 const rawToken = this . getNodeString ( next ) . trim ( ) ;
1125- const token = rawToken . charCodeAt ( 0 ) === 0x21 /* ! */
1126- ? rawToken . slice ( 1 )
1127- : rawToken ;
1128- if ( preparserIfTokens . has ( token ) === false ) {
1125+ if ( utils . preparser . evaluateExpr ( rawToken ) === undefined ) {
11291126 this . addNodeFlags ( next , NODE_FLAG_ERROR ) ;
11301127 this . addFlags ( AST_FLAG_HAS_ERROR ) ;
11311128 this . astError = AST_ERROR_IF_TOKEN_UNKNOWN ;
@@ -4137,43 +4134,88 @@ export const utils = (( ) => {
41374134 }
41384135 } ;
41394136
4137+ // Useful reference:
4138+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#conditions-directive
4139+
41404140 class preparser {
4141+ static evaluateExprToken ( token , env = [ ] ) {
4142+ const not = token . charCodeAt ( 0 ) === 0x21 /* ! */ ;
4143+ if ( not ) { token = token . slice ( 1 ) ; }
4144+ const state = preparserTokens . get ( token ) ;
4145+ if ( state === undefined ) { return ; }
4146+ return state === 'false' && not || env . includes ( state ) !== not ;
4147+ }
4148+
4149+ static evaluateExpr ( expr , env = [ ] ) {
4150+ if ( expr . startsWith ( '(' ) && expr . endsWith ( ')' ) ) {
4151+ expr = expr . slice ( 1 , - 1 ) ;
4152+ }
4153+ const matches = Array . from ( expr . matchAll ( / (?: (?: & & | \| \| ) \s + ) ? \S + / g) ) ;
4154+ if ( matches . length === 0 ) { return ; }
4155+ if ( matches [ 0 ] [ 0 ] . startsWith ( '|' ) || matches [ 0 ] [ 0 ] . startsWith ( '&' ) ) { return ; }
4156+ let result = this . evaluateExprToken ( matches [ 0 ] [ 0 ] , env ) ;
4157+ for ( let i = 1 ; i < matches . length ; i ++ ) {
4158+ const parts = matches [ i ] [ 0 ] . split ( / + / ) ;
4159+ if ( parts . length !== 2 ) { return ; }
4160+ const state = this . evaluateExprToken ( parts [ 1 ] , env ) ;
4161+ if ( state === undefined ) { return ; }
4162+ if ( parts [ 0 ] === '||' ) {
4163+ result = result || state ;
4164+ } else if ( parts [ 0 ] === '&&' ) {
4165+ result = result && state ;
4166+ } else {
4167+ return ;
4168+ }
4169+ }
4170+ return result ;
4171+ }
4172+
41414173 // This method returns an array of indices, corresponding to position in
41424174 // the content string which should alternatively be parsed and discarded.
41434175 static splitter ( content , env = [ ] ) {
4144- const reIf = / ^ ! # ( i f | e n d i f ) \b ( [ ^ \n ] * ) (?: [ \n \r ] + | $ ) / gm;
4176+ const reIf = / ^ ! # ( i f | e l s e | e n d i f ) \b ( [ ^ \n ] * ) (?: [ \n \r ] + | $ ) / gm;
41454177 const stack = [ ] ;
4146- const shouldDiscard = ( ) => stack . some ( v => v ) ;
41474178 const parts = [ 0 ] ;
41484179 let discard = false ;
41494180
4181+ const shouldDiscard = ( ) => stack . some ( v => v ) ;
4182+
4183+ const begif = ( startDiscard , match ) => {
4184+ if ( discard === false && startDiscard ) {
4185+ parts . push ( match . index ) ;
4186+ discard = true ;
4187+ }
4188+ stack . push ( startDiscard ) ;
4189+ } ;
4190+
4191+ const endif = match => {
4192+ stack . pop ( ) ;
4193+ const stopDiscard = shouldDiscard ( ) === false ;
4194+ if ( discard && stopDiscard ) {
4195+ parts . push ( match . index + match [ 0 ] . length ) ;
4196+ discard = false ;
4197+ }
4198+ } ;
4199+
41504200 for ( ; ; ) {
41514201 const match = reIf . exec ( content ) ;
41524202 if ( match === null ) { break ; }
41534203
41544204 switch ( match [ 1 ] ) {
41554205 case 'if' : {
4156- let expr = match [ 2 ] . trim ( ) ;
4157- const target = expr . charCodeAt ( 0 ) === 0x21 /* '!' */ ;
4158- if ( target ) { expr = expr . slice ( 1 ) ; }
4159- const token = preparserTokens . get ( expr ) ;
4160- const startDiscard =
4161- token === 'false' && target === false ||
4162- token !== undefined && env . includes ( token ) === target ;
4163- if ( discard === false && startDiscard ) {
4164- parts . push ( match . index ) ;
4165- discard = true ;
4166- }
4167- stack . push ( startDiscard ) ;
4206+ const startDiscard = this . evaluateExpr ( match [ 2 ] . trim ( ) , env ) === false ;
4207+ begif ( startDiscard , match ) ;
4208+ break ;
4209+ }
4210+ case 'else' : {
4211+ if ( stack . length === 0 ) { break ; }
4212+ const startDiscard = stack [ stack . length - 1 ] === false ;
4213+ endif ( match ) ;
4214+ begif ( startDiscard , match ) ;
41684215 break ;
41694216 }
41704217 case 'endif' : {
4171- stack . pop ( ) ;
4172- const stopDiscard = shouldDiscard ( ) === false ;
4173- if ( discard && stopDiscard ) {
4174- parts . push ( match . index + match [ 0 ] . length ) ;
4175- discard = false ;
4176- }
4218+ endif ( match ) ;
41774219 break ;
41784220 }
41794221 default :
0 commit comments