Implement network filter option replace= · gorhill/uBlock@7c3e060 (original) (raw)
`@@ -187,6 +187,7 @@ export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
`
187
187
`export const NODE_TYPE_NET_OPTION_NAME_REDIRECT = iota++;
`
188
188
`export const NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE = iota++;
`
189
189
`export const NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM = iota++;
`
``
190
`+
export const NODE_TYPE_NET_OPTION_NAME_REPLACE = iota++;
`
190
191
`export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota++;
`
191
192
`export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota++;
`
192
193
`export const NODE_TYPE_NET_OPTION_NAME_TO = iota++;
`
`@@ -265,6 +266,7 @@ export const nodeTypeFromOptionName = new Map([
`
265
266
`/* synonym */ [ 'rewrite', NODE_TYPE_NET_OPTION_NAME_REDIRECT ],
`
266
267
`[ 'redirect-rule', NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE ],
`
267
268
`[ 'removeparam', NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ],
`
``
269
`+
[ 'replace', NODE_TYPE_NET_OPTION_NAME_REPLACE ],
`
268
270
`/* synonym */ [ 'queryprune', NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ],
`
269
271
`[ 'script', NODE_TYPE_NET_OPTION_NAME_SCRIPT ],
`
270
272
`[ 'shide', NODE_TYPE_NET_OPTION_NAME_SHIDE ],
`
`@@ -597,9 +599,14 @@ const exCharCodeAt = (s, i) => {
`
597
599
`return pos >= 0 ? s.charCodeAt(pos) : -1;
`
598
600
`};
`
599
601
``
``
602
`+
const toEscapedCharRegex = c => {
`
``
603
`+
const safe = c.replace(/[.*+?^${}()|[]\]/g, '\$&');
`
``
604
`` +
return new RegExp(((?:^|[^\\\\])(?:\\\\\\\\)*)\\\\${safe}, 'g');
``
``
605
`+
};
`
``
606
+
600
607
`/******************************************************************************/
`
601
608
``
602
``
`-
class argListParser {
`
``
609
`+
class ArgListParser {
`
603
610
`constructor(separatorChar = ',', mustQuote = false) {
`
604
611
`this.separatorChar = this.actualSeparatorChar = separatorChar;
`
605
612
`this.separatorCode = this.actualSeparatorCode = separatorChar.charCodeAt(0);
`
`@@ -612,10 +619,10 @@ class argListParser {
`
612
619
`this.reWhitespaceStart = /^\s+/;
`
613
620
`this.reWhitespaceEnd = /\s+$/;
`
614
621
`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);
`
619
626
`` this.unescapedSeparator = $1${this.separatorChar};
``
620
627
`}
`
621
628
`nextArg(pattern, beg = 0) {
`
`@@ -871,7 +878,7 @@ export class AstFilterParser {
`
871
878
`this.rePlainEntity = /^(?:[\da-z][\da-z_-]*.)+*$/;
`
872
879
`this.reHostsSink = /^[\w%.:[]-]+\s+/;
`
873
880
`this.reHostsRedirect = /(?:0.0.0.0|broadcasthost|local|localhost(?:.localdomain)?|ip6-\w+)(?:[^\w.-]|$)/;
`
874
``
`-
this.reNetOptionComma = /,(?!\d*})/g;
`
``
881
`+
this.reNetOptionComma = /,(?:~?[13a-z-]+(?:=.*?)?|_+)(?:,|$)/;
`
875
882
`this.rePointlessLeftAnchor = /^||?*+/;
`
876
883
`this.reIsTokenChar = /^[%0-9A-Za-z]/;
`
877
884
`this.rePointlessLeadingWildcards = /^(*+)[^%0-9A-Za-z\u{a0}-\u{10FFFF}]/u;
`
`@@ -898,7 +905,7 @@ export class AstFilterParser {
`
898
905
`this.reGoodRegexToken = /[^\x01%0-9A-Za-z][%0-9A-Za-z]{7,}|[^\x01%0-9A-Za-z][%0-9A-Za-z]{1,6}[^\x01%0-9A-Za-z]/;
`
899
906
`this.reBadCSP = /(?:=|;)\s*report-(?:to|uri)\b/;
`
900
907
`this.reNoopOption = /^_+$/;
`
901
``
`-
this.scriptletArgListParser = new argListParser(',');
`
``
908
`+
this.scriptletArgListParser = new ArgListParser(',');
`
902
909
`}
`
903
910
``
904
911
`parse(raw) {
`
`@@ -1414,6 +1421,7 @@ export class AstFilterParser {
`
1414
1421
`break;
`
1415
1422
`case NODE_TYPE_NET_OPTION_NAME_REDIRECT:
`
1416
1423
`case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
`
``
1424
`+
case NODE_TYPE_NET_OPTION_NAME_REPLACE:
`
1417
1425
`case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
`
1418
1426
`realBad = isNegated || (isException || hasValue) === false ||
`
1419
1427
`modifierType !== 0;
`
`@@ -1474,6 +1482,20 @@ export class AstFilterParser {
`
1474
1482
`realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount;
`
1475
1483
`break;
`
1476
1484
`}
`
``
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
`+
}
`
1477
1499
`case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
`
1478
1500
`realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount;
`
1479
1501
`if ( realBad ) { break; }
`
`@@ -1959,9 +1981,8 @@ export class AstFilterParser {
`
1959
1981
`}
`
1960
1982
``
1961
1983
`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;
`
1965
1986
`}
`
1966
1987
``
1967
1988
`parseNetOption(parent) {
`
`@@ -2975,6 +2996,39 @@ export function parseHeaderValue(arg) {
`
2975
2996
`return out;
`
2976
2997
`}
`
2977
2998
``
``
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
+
2978
3032
`/******************************************************************************/
`
2979
3033
``
2980
3034
`export const netOptionTokenDescriptors = new Map([
`
`@@ -3025,6 +3079,7 @@ export const netOptionTokenDescriptors = new Map([
`
3025
3079
`/* synonym */ [ 'rewrite', { mustAssign: true } ],
`
3026
3080
`[ 'redirect-rule', { mustAssign: true } ],
`
3027
3081
`[ 'removeparam', { } ],
`
``
3082
`+
[ 'replace', { mustAssign: true } ],
`
3028
3083
`/* synonym */ [ 'queryprune', { } ],
`
3029
3084
`[ 'script', { canNegate: true } ],
`
3030
3085
`[ 'shide', { } ],
`