@@ -187,6 +187,7 @@ export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
187187export const NODE_TYPE_NET_OPTION_NAME_REDIRECT = iota ++ ;
188188export const NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE = iota ++ ;
189189export const NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM = iota ++ ;
190+ export const NODE_TYPE_NET_OPTION_NAME_REPLACE = iota ++ ;
190191export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota ++ ;
191192export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota ++ ;
192193export const NODE_TYPE_NET_OPTION_NAME_TO = iota ++ ;
@@ -265,6 +266,7 @@ export const nodeTypeFromOptionName = new Map([
265266 /* synonym */ [ 'rewrite' , NODE_TYPE_NET_OPTION_NAME_REDIRECT ] ,
266267 [ 'redirect-rule' , NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE ] ,
267268 [ 'removeparam' , NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ] ,
269+ [ 'replace' , NODE_TYPE_NET_OPTION_NAME_REPLACE ] ,
268270 /* synonym */ [ 'queryprune' , NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ] ,
269271 [ 'script' , NODE_TYPE_NET_OPTION_NAME_SCRIPT ] ,
270272 [ 'shide' , NODE_TYPE_NET_OPTION_NAME_SHIDE ] ,
@@ -597,9 +599,14 @@ const exCharCodeAt = (s, i) => {
597599 return pos >= 0 ? s . charCodeAt ( pos ) : - 1 ;
598600} ;
599601
602+ const toEscapedCharRegex = c => {
603+ const safe = c . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
604+ return new RegExp ( `((?:^|[^\\\\])(?:\\\\\\\\)*)\\\\${ safe } ` , 'g' ) ;
605+ } ;
606+
600607/******************************************************************************/
601608
602- class argListParser {
609+ class ArgListParser {
603610 constructor ( separatorChar = ',' , mustQuote = false ) {
604611 this . separatorChar = this . actualSeparatorChar = separatorChar ;
605612 this . separatorCode = this . actualSeparatorCode = separatorChar . charCodeAt ( 0 ) ;
@@ -612,10 +619,10 @@ class argListParser {
612619 this . reWhitespaceStart = / ^ \s + / ;
613620 this . reWhitespaceEnd = / \s + $ / ;
614621 this . reOddTrailingEscape = / (?: ^ | [ ^ \\ ] ) (?: \\ \\ ) * \\ $ / ;
615- this . reEscapedDoubleQuote = / ( (?: ^ | [ ^ \\ ] ) (?: \\ \\ ) * ) \\ " / g ;
616- this . reEscapedSingleQuote = / ( (?: ^ | [ ^ \\ ] ) (?: \\ \\ ) * ) \\ ' / g ;
617- this . reEscapedBacktick = / ( (?: ^ | [ ^ \\ ] ) (?: \\ \\ ) * ) \\ ` / g ;
618- this . reEscapedSeparator = new RegExp ( `((?:^|[^\\\\])(?:\\\\\\\\)*)\\\\ ${ this . separatorChar } ` , 'g' ) ;
622+ this . reEscapedDoubleQuote = toEscapedCharRegex ( '"' ) ;
623+ this . reEscapedSingleQuote = toEscapedCharRegex ( "'" ) ;
624+ this . reEscapedBacktick = toEscapedCharRegex ( '`' ) ;
625+ this . reEscapedSeparator = toEscapedCharRegex ( this . separatorChar ) ;
619626 this . unescapedSeparator = `$1${ this . separatorChar } ` ;
620627 }
621628 nextArg ( pattern , beg = 0 ) {
@@ -871,7 +878,7 @@ export class AstFilterParser {
871878 this . rePlainEntity = / ^ (?: [ \d a - z ] [ \d a - z _ - ] * \. ) + \* $ / ;
872879 this . reHostsSink = / ^ [ \w % . : \[ \] - ] + \s + / ;
873880 this . reHostsRedirect = / (?: 0 \. 0 \. 0 \. 0 | b r o a d c a s t h o s t | l o c a l | l o c a l h o s t (?: \. l o c a l d o m a i n ) ? | i p 6 - \w + ) (?: [ ^ \w . - ] | $ ) / ;
874- this . reNetOptionComma = / , (? ! \d * \} ) / g ;
881+ this . reNetOptionComma = / , (?: ~ ? [ 1 3 a - z - ] + (?: = . * ? ) ? | _ + ) (?: , | $ ) / ;
875882 this . rePointlessLeftAnchor = / ^ \| \| ? \* + / ;
876883 this . reIsTokenChar = / ^ [ % 0 - 9 A - Z a - z ] / ;
877884 this . rePointlessLeadingWildcards = / ^ ( \* + ) [ ^ % 0 - 9 A - Z a - z \u{a0} - \u{10FFFF} ] / u;
@@ -898,7 +905,7 @@ export class AstFilterParser {
898905 this . reGoodRegexToken = / [ ^ \x01 % 0 - 9 A - Z a - z ] [ % 0 - 9 A - Z a - z ] { 7 , } | [ ^ \x01 % 0 - 9 A - Z a - z ] [ % 0 - 9 A - Z a - z ] { 1 , 6 } [ ^ \x01 % 0 - 9 A - Z a - z ] / ;
899906 this . reBadCSP = / (?: = | ; ) \s * r e p o r t - (?: t o | u r i ) \b / ;
900907 this . reNoopOption = / ^ _ + $ / ;
901- this . scriptletArgListParser = new argListParser ( ',' ) ;
908+ this . scriptletArgListParser = new ArgListParser ( ',' ) ;
902909 }
903910
904911 parse ( raw ) {
@@ -1414,6 +1421,7 @@ export class AstFilterParser {
14141421 break ;
14151422 case NODE_TYPE_NET_OPTION_NAME_REDIRECT :
14161423 case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE :
1424+ case NODE_TYPE_NET_OPTION_NAME_REPLACE :
14171425 case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM :
14181426 realBad = isNegated || ( isException || hasValue ) === false ||
14191427 modifierType !== 0 ;
@@ -1474,6 +1482,20 @@ export class AstFilterParser {
14741482 realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount ;
14751483 break ;
14761484 }
1485+ case NODE_TYPE_NET_OPTION_NAME_REPLACE : {
1486+ realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount ;
1487+ if ( realBad ) { break ; }
1488+ if ( this . options . trustedSource !== true ) {
1489+ this . astError = AST_ERROR_UNTRUSTED_SOURCE ;
1490+ realBad = true ;
1491+ break ;
1492+ }
1493+ if ( this . interactive ) {
1494+ const value = this . getNetOptionValue ( NODE_TYPE_NET_OPTION_NAME_REPLACE ) ;
1495+ realBad = parseReplaceValue ( value ) === undefined ;
1496+ }
1497+ break ;
1498+ }
14771499 case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM :
14781500 realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount ;
14791501 if ( realBad ) { break ; }
@@ -1959,9 +1981,8 @@ export class AstFilterParser {
19591981 }
19601982
19611983 endOfNetOption ( s , beg ) {
1962- this . reNetOptionComma . lastIndex = beg ;
1963- const match = this . reNetOptionComma . exec ( s ) ;
1964- return match !== null ? match . index : s . length ;
1984+ const match = this . reNetOptionComma . exec ( s . slice ( beg ) ) ;
1985+ return match !== null ? beg + match . index : s . length ;
19651986 }
19661987
19671988 parseNetOption ( parent ) {
@@ -2975,6 +2996,39 @@ export function parseHeaderValue(arg) {
29752996 return out ;
29762997}
29772998
2999+
3000+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#replace-modifier
3001+
3002+ export function parseReplaceValue ( s ) {
3003+ if ( s . charCodeAt ( 0 ) !== 0x2F /* / */ ) { return ; }
3004+ const { reEscapedComma, reEscapedDollarSign } = parseReplaceValue ;
3005+ const parser = new ArgListParser ( '/' ) ;
3006+ parser . nextArg ( s , 1 ) ;
3007+ let pattern = s . slice ( parser . argBeg , parser . argEnd ) ;
3008+ if ( parser . transform ) {
3009+ pattern = parser . normalizeArg ( pattern ) ;
3010+ }
3011+ pattern = pattern
3012+ . replace ( reEscapedDollarSign , '$1$$$' )
3013+ . replace ( reEscapedComma , '$1,' ) ;
3014+ parser . nextArg ( s , parser . separatorEnd ) ;
3015+ let replacement = s . slice ( parser . argBeg , parser . argEnd ) ;
3016+ if ( parser . separatorEnd === parser . separatorBeg ) { return ; }
3017+ if ( parser . transform ) {
3018+ replacement = parser . normalizeArg ( replacement ) ;
3019+ }
3020+ replacement = replacement
3021+ . replace ( reEscapedDollarSign , '$1$$' )
3022+ . replace ( reEscapedComma , '$1,' ) ;
3023+ const flags = s . slice ( parser . separatorEnd ) ;
3024+ try {
3025+ return { re : new RegExp ( pattern , flags ) , replacement } ;
3026+ } catch ( _ ) {
3027+ }
3028+ }
3029+ parseReplaceValue . reEscapedDollarSign = toEscapedCharRegex ( '$' ) ;
3030+ parseReplaceValue . reEscapedComma = toEscapedCharRegex ( ',' ) ;
3031+
29783032/******************************************************************************/
29793033
29803034export const netOptionTokenDescriptors = new Map ( [
@@ -3025,6 +3079,7 @@ export const netOptionTokenDescriptors = new Map([
30253079 /* synonym */ [ 'rewrite' , { mustAssign : true } ] ,
30263080 [ 'redirect-rule' , { mustAssign : true } ] ,
30273081 [ 'removeparam' , { } ] ,
3082+ [ 'replace' , { mustAssign : true } ] ,
30283083 /* synonym */ [ 'queryprune' , { } ] ,
30293084 [ 'script' , { canNegate : true } ] ,
30303085 [ 'shide' , { } ] ,
0 commit comments