@adguard/agtree 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/agtree.cjs +1018 -408
- package/dist/agtree.d.ts +169 -42
- package/dist/agtree.esm.js +1018 -410
- package/dist/agtree.iife.min.js +5 -5
- package/dist/agtree.umd.min.js +5 -5
- package/dist/build.txt +1 -1
- package/package.json +3 -2
package/dist/agtree.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* AGTree v1.1.
|
|
2
|
+
* AGTree v1.1.6 (build date: Fri, 22 Sep 2023 13:09:45 GMT)
|
|
3
3
|
* (c) 2023 AdGuard Software Ltd.
|
|
4
4
|
* Released under the MIT license
|
|
5
5
|
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
|
|
@@ -281,7 +281,7 @@ const NEGATION_MARKER = '~';
|
|
|
281
281
|
/**
|
|
282
282
|
* The wildcard symbol — `*`.
|
|
283
283
|
*/
|
|
284
|
-
const WILDCARD
|
|
284
|
+
const WILDCARD = ASTERISK;
|
|
285
285
|
/**
|
|
286
286
|
* Classic domain separator.
|
|
287
287
|
*
|
|
@@ -2880,7 +2880,7 @@ class ModifierParser {
|
|
|
2880
2880
|
const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
|
|
2881
2881
|
// Modifier name can't be empty
|
|
2882
2882
|
if (modifierNameStart === modifierEnd) {
|
|
2883
|
-
throw new AdblockSyntaxError('Modifier name
|
|
2883
|
+
throw new AdblockSyntaxError('Modifier name cannot be empty', locRange(loc, 0, raw.length));
|
|
2884
2884
|
}
|
|
2885
2885
|
let modifier;
|
|
2886
2886
|
let value;
|
|
@@ -2904,7 +2904,7 @@ class ModifierParser {
|
|
|
2904
2904
|
};
|
|
2905
2905
|
// Value can't be empty
|
|
2906
2906
|
if (assignmentIndex + 1 === modifierEnd) {
|
|
2907
|
-
throw new AdblockSyntaxError('Modifier value
|
|
2907
|
+
throw new AdblockSyntaxError('Modifier value cannot be empty', locRange(loc, 0, raw.length));
|
|
2908
2908
|
}
|
|
2909
2909
|
// Skip whitespace after the assignment operator
|
|
2910
2910
|
const valueStart = StringUtils.skipWS(raw, assignmentIndex + MODIFIER_ASSIGN_OPERATOR.length);
|
|
@@ -3202,8 +3202,29 @@ const FORBIDDEN_CSS_FUNCTIONS = new Set([
|
|
|
3202
3202
|
'url',
|
|
3203
3203
|
]);
|
|
3204
3204
|
|
|
3205
|
+
/**
|
|
3206
|
+
* @file Clone related utilities
|
|
3207
|
+
*
|
|
3208
|
+
* We should keep clone related functions in this file. Thus, we just provide
|
|
3209
|
+
* a simple interface for cloning values, we use it across the AGTree project,
|
|
3210
|
+
* and the implementation "under the hood" can be improved later, if needed.
|
|
3211
|
+
*/
|
|
3212
|
+
/**
|
|
3213
|
+
* Clones an input value to avoid side effects. Use it only in justified cases,
|
|
3214
|
+
* because it can impact performance negatively.
|
|
3215
|
+
*
|
|
3216
|
+
* @param value Value to clone
|
|
3217
|
+
* @returns Cloned value
|
|
3218
|
+
*/
|
|
3219
|
+
function clone(value) {
|
|
3220
|
+
// TODO: Replace cloneDeep with a more efficient implementation
|
|
3221
|
+
return cloneDeep(value);
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3205
3224
|
/**
|
|
3206
3225
|
* @file Additional / helper functions for ECSSTree / CSSTree.
|
|
3226
|
+
*
|
|
3227
|
+
* @note There are no tests for some functions, but during the AGTree optimization we remove them anyway.
|
|
3207
3228
|
*/
|
|
3208
3229
|
/**
|
|
3209
3230
|
* Common CSSTree parsing options.
|
|
@@ -3339,10 +3360,10 @@ class CssTree {
|
|
|
3339
3360
|
ast = CssTree.parse(selectorList, exports.CssTreeParserContext.selectorList);
|
|
3340
3361
|
}
|
|
3341
3362
|
else {
|
|
3342
|
-
ast =
|
|
3363
|
+
ast = clone(selectorList);
|
|
3343
3364
|
}
|
|
3344
3365
|
const nodes = [];
|
|
3345
|
-
// TODO:
|
|
3366
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3346
3367
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3347
3368
|
ecssTree.walk(ast, (node) => {
|
|
3348
3369
|
if (CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) {
|
|
@@ -3371,9 +3392,9 @@ class CssTree {
|
|
|
3371
3392
|
ast = CssTree.parse(selectorList, exports.CssTreeParserContext.selectorList);
|
|
3372
3393
|
}
|
|
3373
3394
|
else {
|
|
3374
|
-
ast =
|
|
3395
|
+
ast = selectorList;
|
|
3375
3396
|
}
|
|
3376
|
-
// TODO:
|
|
3397
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3377
3398
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3378
3399
|
return ecssTree.find(ast, (node) => CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) !== null;
|
|
3379
3400
|
}
|
|
@@ -3410,14 +3431,14 @@ class CssTree {
|
|
|
3410
3431
|
ast = CssTree.parse(declarationList, exports.CssTreeParserContext.declarationList);
|
|
3411
3432
|
}
|
|
3412
3433
|
else {
|
|
3413
|
-
ast =
|
|
3434
|
+
ast = clone(declarationList);
|
|
3414
3435
|
}
|
|
3415
3436
|
const nodes = [];
|
|
3416
3437
|
// While walking the AST we should skip the nested functions,
|
|
3417
3438
|
// for example skip url()s in cross-fade(url(), url()), since
|
|
3418
3439
|
// cross-fade() itself is already a forbidden function
|
|
3419
3440
|
let inForbiddenFunction = false;
|
|
3420
|
-
// TODO:
|
|
3441
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3421
3442
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3422
3443
|
ecssTree.walk(ast, {
|
|
3423
3444
|
enter: (node) => {
|
|
@@ -3455,9 +3476,9 @@ class CssTree {
|
|
|
3455
3476
|
ast = CssTree.parse(declarationList, exports.CssTreeParserContext.declarationList);
|
|
3456
3477
|
}
|
|
3457
3478
|
else {
|
|
3458
|
-
ast =
|
|
3479
|
+
ast = clone(declarationList);
|
|
3459
3480
|
}
|
|
3460
|
-
// TODO:
|
|
3481
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3461
3482
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3462
3483
|
return ecssTree.find(ast, (node) => CssTree.isForbiddenFunction(node, forbiddenFunctions)) !== null;
|
|
3463
3484
|
}
|
|
@@ -3689,6 +3710,180 @@ class CssTree {
|
|
|
3689
3710
|
});
|
|
3690
3711
|
return result.trim();
|
|
3691
3712
|
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Generates string representation of the selector list.
|
|
3715
|
+
*
|
|
3716
|
+
* @param ast SelectorList AST
|
|
3717
|
+
* @returns String representation of the selector list
|
|
3718
|
+
*/
|
|
3719
|
+
static generateSelectorListPlain(ast) {
|
|
3720
|
+
const result = [];
|
|
3721
|
+
if (!ast.children || ast.children.length === 0) {
|
|
3722
|
+
throw new Error('Selector list cannot be empty');
|
|
3723
|
+
}
|
|
3724
|
+
ast.children.forEach((selector, index, nodeList) => {
|
|
3725
|
+
if (selector.type !== exports.CssTreeNodeType.Selector) {
|
|
3726
|
+
throw new Error(`Unexpected node type: ${selector.type}`);
|
|
3727
|
+
}
|
|
3728
|
+
result.push(this.generateSelectorPlain(selector));
|
|
3729
|
+
// If there is a next node, add a comma and a space after the selector
|
|
3730
|
+
if (nodeList[index + 1]) {
|
|
3731
|
+
result.push(COMMA, SPACE);
|
|
3732
|
+
}
|
|
3733
|
+
});
|
|
3734
|
+
return result.join(EMPTY);
|
|
3735
|
+
}
|
|
3736
|
+
/**
|
|
3737
|
+
* Selector generation based on CSSTree's AST. This is necessary because CSSTree
|
|
3738
|
+
* only adds spaces in some edge cases.
|
|
3739
|
+
*
|
|
3740
|
+
* @param ast CSS Tree AST
|
|
3741
|
+
* @returns CSS selector as string
|
|
3742
|
+
*/
|
|
3743
|
+
static generateSelectorPlain(ast) {
|
|
3744
|
+
let result = EMPTY;
|
|
3745
|
+
let inAttributeSelector = false;
|
|
3746
|
+
let depth = 0;
|
|
3747
|
+
let selectorListDepth = -1;
|
|
3748
|
+
let prevNode = ast;
|
|
3749
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3750
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3751
|
+
ecssTree.walk(ast, {
|
|
3752
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3753
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3754
|
+
enter: (node) => {
|
|
3755
|
+
depth += 1;
|
|
3756
|
+
// Skip attribute selector / selector list children
|
|
3757
|
+
if (inAttributeSelector || selectorListDepth > -1) {
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
switch (node.type) {
|
|
3761
|
+
// "Trivial" nodes
|
|
3762
|
+
case exports.CssTreeNodeType.TypeSelector:
|
|
3763
|
+
result += node.name;
|
|
3764
|
+
break;
|
|
3765
|
+
case exports.CssTreeNodeType.ClassSelector:
|
|
3766
|
+
result += DOT;
|
|
3767
|
+
result += node.name;
|
|
3768
|
+
break;
|
|
3769
|
+
case exports.CssTreeNodeType.IdSelector:
|
|
3770
|
+
result += HASHMARK;
|
|
3771
|
+
result += node.name;
|
|
3772
|
+
break;
|
|
3773
|
+
case exports.CssTreeNodeType.Identifier:
|
|
3774
|
+
result += node.name;
|
|
3775
|
+
break;
|
|
3776
|
+
case exports.CssTreeNodeType.Raw:
|
|
3777
|
+
result += node.value;
|
|
3778
|
+
break;
|
|
3779
|
+
// "Advanced" nodes
|
|
3780
|
+
case exports.CssTreeNodeType.Nth:
|
|
3781
|
+
// Default generation enough
|
|
3782
|
+
result += ecssTree.generate(node);
|
|
3783
|
+
break;
|
|
3784
|
+
// For example :not([id], [name])
|
|
3785
|
+
case exports.CssTreeNodeType.SelectorList:
|
|
3786
|
+
// eslint-disable-next-line no-case-declarations
|
|
3787
|
+
const selectors = [];
|
|
3788
|
+
node.children.forEach((selector) => {
|
|
3789
|
+
if (selector.type === exports.CssTreeNodeType.Selector) {
|
|
3790
|
+
selectors.push(CssTree.generateSelectorPlain(selector));
|
|
3791
|
+
}
|
|
3792
|
+
else if (selector.type === exports.CssTreeNodeType.Raw) {
|
|
3793
|
+
selectors.push(selector.value);
|
|
3794
|
+
}
|
|
3795
|
+
});
|
|
3796
|
+
// Join selector lists
|
|
3797
|
+
result += selectors.join(COMMA + SPACE);
|
|
3798
|
+
// Skip nodes here
|
|
3799
|
+
selectorListDepth = depth;
|
|
3800
|
+
break;
|
|
3801
|
+
case exports.CssTreeNodeType.Combinator:
|
|
3802
|
+
if (node.name === SPACE) {
|
|
3803
|
+
result += node.name;
|
|
3804
|
+
break;
|
|
3805
|
+
}
|
|
3806
|
+
// Prevent this case (unnecessary space): has( > .something)
|
|
3807
|
+
if (prevNode.type !== exports.CssTreeNodeType.Selector) {
|
|
3808
|
+
result += SPACE;
|
|
3809
|
+
}
|
|
3810
|
+
result += node.name;
|
|
3811
|
+
result += SPACE;
|
|
3812
|
+
break;
|
|
3813
|
+
case exports.CssTreeNodeType.AttributeSelector:
|
|
3814
|
+
result += OPEN_SQUARE_BRACKET;
|
|
3815
|
+
// Identifier name
|
|
3816
|
+
if (node.name) {
|
|
3817
|
+
result += node.name.name;
|
|
3818
|
+
}
|
|
3819
|
+
// Matcher operator, eg =
|
|
3820
|
+
if (node.matcher) {
|
|
3821
|
+
result += node.matcher;
|
|
3822
|
+
// Value can be String, Identifier or null
|
|
3823
|
+
if (node.value !== null) {
|
|
3824
|
+
// String node
|
|
3825
|
+
if (node.value.type === exports.CssTreeNodeType.String) {
|
|
3826
|
+
result += ecssTree.generate(node.value);
|
|
3827
|
+
}
|
|
3828
|
+
else if (node.value.type === exports.CssTreeNodeType.Identifier) {
|
|
3829
|
+
// Identifier node
|
|
3830
|
+
result += node.value.name;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
// Flags
|
|
3835
|
+
if (node.flags) {
|
|
3836
|
+
// Space before flags
|
|
3837
|
+
result += SPACE;
|
|
3838
|
+
result += node.flags;
|
|
3839
|
+
}
|
|
3840
|
+
result += CLOSE_SQUARE_BRACKET;
|
|
3841
|
+
inAttributeSelector = true;
|
|
3842
|
+
break;
|
|
3843
|
+
case exports.CssTreeNodeType.PseudoElementSelector:
|
|
3844
|
+
result += COLON;
|
|
3845
|
+
result += COLON;
|
|
3846
|
+
result += node.name;
|
|
3847
|
+
if (node.children !== null) {
|
|
3848
|
+
result += OPEN_PARENTHESIS;
|
|
3849
|
+
}
|
|
3850
|
+
break;
|
|
3851
|
+
case exports.CssTreeNodeType.PseudoClassSelector:
|
|
3852
|
+
result += COLON;
|
|
3853
|
+
result += node.name;
|
|
3854
|
+
if (node.children !== null) {
|
|
3855
|
+
result += OPEN_PARENTHESIS;
|
|
3856
|
+
}
|
|
3857
|
+
break;
|
|
3858
|
+
}
|
|
3859
|
+
prevNode = node;
|
|
3860
|
+
},
|
|
3861
|
+
leave: (node) => {
|
|
3862
|
+
depth -= 1;
|
|
3863
|
+
if (node.type === exports.CssTreeNodeType.SelectorList && depth + 1 === selectorListDepth) {
|
|
3864
|
+
selectorListDepth = -1;
|
|
3865
|
+
}
|
|
3866
|
+
if (selectorListDepth > -1) {
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
if (node.type === exports.CssTreeNodeType.AttributeSelector) {
|
|
3870
|
+
inAttributeSelector = false;
|
|
3871
|
+
}
|
|
3872
|
+
if (inAttributeSelector) {
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
switch (node.type) {
|
|
3876
|
+
case exports.CssTreeNodeType.PseudoElementSelector:
|
|
3877
|
+
case exports.CssTreeNodeType.PseudoClassSelector:
|
|
3878
|
+
if (node.children) {
|
|
3879
|
+
result += CLOSE_PARENTHESIS;
|
|
3880
|
+
}
|
|
3881
|
+
break;
|
|
3882
|
+
}
|
|
3883
|
+
},
|
|
3884
|
+
});
|
|
3885
|
+
return result.trim();
|
|
3886
|
+
}
|
|
3692
3887
|
/**
|
|
3693
3888
|
* Block generation based on CSSTree's AST. This is necessary because CSSTree only adds spaces in some edge cases.
|
|
3694
3889
|
*
|
|
@@ -3872,6 +4067,29 @@ class CssTree {
|
|
|
3872
4067
|
});
|
|
3873
4068
|
return result;
|
|
3874
4069
|
}
|
|
4070
|
+
/**
|
|
4071
|
+
* Helper function to generate a raw string from a function selector's children
|
|
4072
|
+
*
|
|
4073
|
+
* @param node Function node
|
|
4074
|
+
* @returns Generated function value
|
|
4075
|
+
* @example `responseheader(name)` -> `name`
|
|
4076
|
+
*/
|
|
4077
|
+
static generateFunctionPlainValue(node) {
|
|
4078
|
+
const result = [];
|
|
4079
|
+
node.children?.forEach((child) => {
|
|
4080
|
+
switch (child.type) {
|
|
4081
|
+
case exports.CssTreeNodeType.Raw:
|
|
4082
|
+
result.push(child.value);
|
|
4083
|
+
break;
|
|
4084
|
+
default:
|
|
4085
|
+
// Fallback to CSSTree's default generate function
|
|
4086
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4087
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4088
|
+
result.push(ecssTree.generate(child));
|
|
4089
|
+
}
|
|
4090
|
+
});
|
|
4091
|
+
return result.join(EMPTY);
|
|
4092
|
+
}
|
|
3875
4093
|
}
|
|
3876
4094
|
|
|
3877
4095
|
/**
|
|
@@ -3919,7 +4137,7 @@ class ElementHidingBodyParser {
|
|
|
3919
4137
|
* @throws If the AST is invalid
|
|
3920
4138
|
*/
|
|
3921
4139
|
static generate(ast) {
|
|
3922
|
-
return CssTree.
|
|
4140
|
+
return CssTree.generateSelectorListPlain(ast.selectorList);
|
|
3923
4141
|
}
|
|
3924
4142
|
}
|
|
3925
4143
|
|
|
@@ -4163,7 +4381,7 @@ class CssInjectionBodyParser {
|
|
|
4163
4381
|
if (mediaQueryList || declarationList || remove) {
|
|
4164
4382
|
throw new AdblockSyntaxError(
|
|
4165
4383
|
// eslint-disable-next-line max-len
|
|
4166
|
-
'Invalid selector, regular selector elements
|
|
4384
|
+
'Invalid selector, regular selector elements cannot be used after special pseudo-classes', {
|
|
4167
4385
|
start: node.loc?.start ?? loc,
|
|
4168
4386
|
end: shiftLoc(loc, raw.length),
|
|
4169
4387
|
});
|
|
@@ -4852,7 +5070,7 @@ function createModifierListNode(modifiers = []) {
|
|
|
4852
5070
|
const result = {
|
|
4853
5071
|
type: 'ModifierList',
|
|
4854
5072
|
// We need to clone the modifiers to avoid side effects
|
|
4855
|
-
children:
|
|
5073
|
+
children: modifiers.length ? clone(modifiers) : [],
|
|
4856
5074
|
};
|
|
4857
5075
|
return result;
|
|
4858
5076
|
}
|
|
@@ -4892,8 +5110,9 @@ function hasUboModifierIndicator(rawSelectorList) {
|
|
|
4892
5110
|
* @returns Linked list based selector
|
|
4893
5111
|
*/
|
|
4894
5112
|
function convertSelectorToLinkedList(selector) {
|
|
5113
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4895
5114
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4896
|
-
return ecssTree.fromPlainObject(
|
|
5115
|
+
return ecssTree.fromPlainObject(clone(selector));
|
|
4897
5116
|
}
|
|
4898
5117
|
/**
|
|
4899
5118
|
* Helper function that always returns the linked list version of the
|
|
@@ -4903,8 +5122,9 @@ function convertSelectorToLinkedList(selector) {
|
|
|
4903
5122
|
* @returns Linked list based selector list
|
|
4904
5123
|
*/
|
|
4905
5124
|
function convertSelectorListToLinkedList(selectorList) {
|
|
5125
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4906
5126
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4907
|
-
return ecssTree.fromPlainObject(
|
|
5127
|
+
return ecssTree.fromPlainObject(clone(selectorList));
|
|
4908
5128
|
}
|
|
4909
5129
|
/**
|
|
4910
5130
|
* Helper function for checking and removing bounding combinators
|
|
@@ -5989,7 +6209,8 @@ class FilterListParser {
|
|
|
5989
6209
|
*/
|
|
5990
6210
|
static generate(ast, preferRaw = false) {
|
|
5991
6211
|
let result = EMPTY;
|
|
5992
|
-
for (
|
|
6212
|
+
for (let i = 0; i < ast.children.length; i += 1) {
|
|
6213
|
+
const rule = ast.children[i];
|
|
5993
6214
|
if (preferRaw && rule.raws?.text) {
|
|
5994
6215
|
result += rule.raws.text;
|
|
5995
6216
|
}
|
|
@@ -6006,6 +6227,11 @@ class FilterListParser {
|
|
|
6006
6227
|
case 'lf':
|
|
6007
6228
|
result += LF;
|
|
6008
6229
|
break;
|
|
6230
|
+
default:
|
|
6231
|
+
if (i !== ast.children.length - 1) {
|
|
6232
|
+
result += LF;
|
|
6233
|
+
}
|
|
6234
|
+
break;
|
|
6009
6235
|
}
|
|
6010
6236
|
}
|
|
6011
6237
|
return result;
|
|
@@ -7560,14 +7786,14 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
|
|
|
7560
7786
|
* @example
|
|
7561
7787
|
* `example.*` — matches with any TLD, e.g. `example.org`, `example.com`, etc.
|
|
7562
7788
|
*/
|
|
7563
|
-
const WILDCARD_TLD = DOT + WILDCARD
|
|
7789
|
+
const WILDCARD_TLD = DOT + WILDCARD;
|
|
7564
7790
|
/**
|
|
7565
7791
|
* Marker for a wildcard subdomain — `*.`.
|
|
7566
7792
|
*
|
|
7567
7793
|
* @example
|
|
7568
7794
|
* `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
|
|
7569
7795
|
*/
|
|
7570
|
-
const WILDCARD_SUBDOMAIN = WILDCARD
|
|
7796
|
+
const WILDCARD_SUBDOMAIN = WILDCARD + DOT;
|
|
7571
7797
|
class DomainUtils {
|
|
7572
7798
|
/**
|
|
7573
7799
|
* Check if the input is a valid domain or hostname.
|
|
@@ -7578,7 +7804,7 @@ class DomainUtils {
|
|
|
7578
7804
|
static isValidDomainOrHostname(domain) {
|
|
7579
7805
|
let domainToCheck = domain;
|
|
7580
7806
|
// Wildcard-only domain, typically a generic rule
|
|
7581
|
-
if (domainToCheck === WILDCARD
|
|
7807
|
+
if (domainToCheck === WILDCARD) {
|
|
7582
7808
|
return true;
|
|
7583
7809
|
}
|
|
7584
7810
|
// https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
|
|
@@ -7798,7 +8024,7 @@ const isValidAppNameChunk = (chunk) => {
|
|
|
7798
8024
|
const isValidAppModifierValue = (value) => {
|
|
7799
8025
|
// $app modifier does not support wildcard tld
|
|
7800
8026
|
// https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
|
|
7801
|
-
if (value.includes(WILDCARD
|
|
8027
|
+
if (value.includes(WILDCARD)) {
|
|
7802
8028
|
return false;
|
|
7803
8029
|
}
|
|
7804
8030
|
return value
|
|
@@ -7863,7 +8089,7 @@ const isValidDenyAllowModifierValue = (value) => {
|
|
|
7863
8089
|
// $denyallow modifier does not support wildcard tld
|
|
7864
8090
|
// https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
|
|
7865
8091
|
// but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
|
|
7866
|
-
if (value.includes(WILDCARD
|
|
8092
|
+
if (value.includes(WILDCARD)) {
|
|
7867
8093
|
return false;
|
|
7868
8094
|
}
|
|
7869
8095
|
// TODO: add cache for domains validation
|
|
@@ -8160,7 +8386,7 @@ const validatePermissionAllowlist = (allowlist, directive, modifierName) => {
|
|
|
8160
8386
|
// `*` is one of available permissions tokens
|
|
8161
8387
|
// e.g. 'fullscreen=*'
|
|
8162
8388
|
// https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization
|
|
8163
|
-
if (allowlist === WILDCARD
|
|
8389
|
+
if (allowlist === WILDCARD
|
|
8164
8390
|
// e.g. 'autoplay=()'
|
|
8165
8391
|
|| allowlist === EMPTY_PERMISSIONS_ALLOWLIST) {
|
|
8166
8392
|
return { valid: true };
|
|
@@ -8424,7 +8650,7 @@ class ModifierValidator {
|
|
|
8424
8650
|
* @returns Result of modifier validation.
|
|
8425
8651
|
*/
|
|
8426
8652
|
validate = (syntax, rawModifier, isException = false) => {
|
|
8427
|
-
const modifier =
|
|
8653
|
+
const modifier = clone(rawModifier);
|
|
8428
8654
|
// special case: handle noop modifier which may be used as multiple underscores (not just one)
|
|
8429
8655
|
// https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
|
|
8430
8656
|
if (modifier.modifier.value.startsWith(UNDERSCORE)) {
|
|
@@ -8503,7 +8729,9 @@ class ConverterBase {
|
|
|
8503
8729
|
* Converts some data to AdGuard format
|
|
8504
8730
|
*
|
|
8505
8731
|
* @param data Data to convert
|
|
8506
|
-
* @returns
|
|
8732
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8733
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8734
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8507
8735
|
* @throws If the data is invalid or incompatible
|
|
8508
8736
|
*/
|
|
8509
8737
|
static convertToAdg(data) {
|
|
@@ -8513,7 +8741,9 @@ class ConverterBase {
|
|
|
8513
8741
|
* Converts some data to Adblock Plus format
|
|
8514
8742
|
*
|
|
8515
8743
|
* @param data Data to convert
|
|
8516
|
-
* @returns
|
|
8744
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8745
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8746
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8517
8747
|
* @throws If the data is invalid or incompatible
|
|
8518
8748
|
*/
|
|
8519
8749
|
static convertToAbp(data) {
|
|
@@ -8523,7 +8753,9 @@ class ConverterBase {
|
|
|
8523
8753
|
* Converts some data to uBlock Origin format
|
|
8524
8754
|
*
|
|
8525
8755
|
* @param data Data to convert
|
|
8526
|
-
* @returns
|
|
8756
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8757
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8758
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8527
8759
|
* @throws If the data is invalid or incompatible
|
|
8528
8760
|
*/
|
|
8529
8761
|
static convertToUbo(data) {
|
|
@@ -8547,7 +8779,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8547
8779
|
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
8548
8780
|
*
|
|
8549
8781
|
* @param rule Rule node to convert
|
|
8550
|
-
* @returns
|
|
8782
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8783
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8784
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8551
8785
|
* @throws If the rule is invalid or cannot be converted
|
|
8552
8786
|
*/
|
|
8553
8787
|
static convertToAdg(rule) {
|
|
@@ -8557,7 +8791,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8557
8791
|
* Converts an adblock filtering rule to Adblock Plus format, if possible.
|
|
8558
8792
|
*
|
|
8559
8793
|
* @param rule Rule node to convert
|
|
8560
|
-
* @returns
|
|
8794
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8795
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8796
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8561
8797
|
* @throws If the rule is invalid or cannot be converted
|
|
8562
8798
|
*/
|
|
8563
8799
|
static convertToAbp(rule) {
|
|
@@ -8567,7 +8803,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8567
8803
|
* Converts an adblock filtering rule to uBlock Origin format, if possible.
|
|
8568
8804
|
*
|
|
8569
8805
|
* @param rule Rule node to convert
|
|
8570
|
-
* @returns
|
|
8806
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8807
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8808
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8571
8809
|
* @throws If the rule is invalid or cannot be converted
|
|
8572
8810
|
*/
|
|
8573
8811
|
static convertToUbo(rule) {
|
|
@@ -8575,6 +8813,37 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8575
8813
|
}
|
|
8576
8814
|
}
|
|
8577
8815
|
|
|
8816
|
+
/**
|
|
8817
|
+
* @file Conversion result interface and helper functions
|
|
8818
|
+
*/
|
|
8819
|
+
/**
|
|
8820
|
+
* Helper function to create a generic conversion result.
|
|
8821
|
+
*
|
|
8822
|
+
* @param result Conversion result
|
|
8823
|
+
* @param isConverted Indicates whether the input item was converted
|
|
8824
|
+
* @template T Type of the item to convert
|
|
8825
|
+
* @template U Type of the conversion result (defaults to `T`, but can be `T[]` as well)
|
|
8826
|
+
* @returns Generic conversion result
|
|
8827
|
+
*/
|
|
8828
|
+
// eslint-disable-next-line max-len
|
|
8829
|
+
function createConversionResult(result, isConverted) {
|
|
8830
|
+
return {
|
|
8831
|
+
result,
|
|
8832
|
+
isConverted,
|
|
8833
|
+
};
|
|
8834
|
+
}
|
|
8835
|
+
/**
|
|
8836
|
+
* Helper function to create a node conversion result.
|
|
8837
|
+
*
|
|
8838
|
+
* @param nodes Array of nodes
|
|
8839
|
+
* @param isConverted Indicates whether the input item was converted
|
|
8840
|
+
* @template T Type of the node (extends `Node`)
|
|
8841
|
+
* @returns Node conversion result
|
|
8842
|
+
*/
|
|
8843
|
+
function createNodeConversionResult(nodes, isConverted) {
|
|
8844
|
+
return createConversionResult(nodes, isConverted);
|
|
8845
|
+
}
|
|
8846
|
+
|
|
8578
8847
|
/**
|
|
8579
8848
|
* @file Comment rule converter
|
|
8580
8849
|
*/
|
|
@@ -8588,27 +8857,30 @@ class CommentRuleConverter extends RuleConverterBase {
|
|
|
8588
8857
|
* Converts a comment rule to AdGuard format, if possible.
|
|
8589
8858
|
*
|
|
8590
8859
|
* @param rule Rule node to convert
|
|
8591
|
-
* @returns
|
|
8860
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8861
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8862
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8592
8863
|
* @throws If the rule is invalid or cannot be converted
|
|
8593
8864
|
*/
|
|
8594
8865
|
static convertToAdg(rule) {
|
|
8595
|
-
// Clone the provided AST node to avoid side effects
|
|
8596
|
-
const ruleNode = cloneDeep(rule);
|
|
8597
8866
|
// TODO: Add support for other comment types, if needed
|
|
8598
8867
|
// Main task is # -> ! conversion
|
|
8599
|
-
switch (
|
|
8868
|
+
switch (rule.type) {
|
|
8600
8869
|
case exports.CommentRuleType.CommentRule:
|
|
8601
|
-
//
|
|
8602
|
-
if (
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8870
|
+
// Check if the rule needs to be converted
|
|
8871
|
+
if (rule.type === exports.CommentRuleType.CommentRule && rule.marker.value === exports.CommentMarker.Hashmark) {
|
|
8872
|
+
// Add a ! to the beginning of the comment
|
|
8873
|
+
// TODO: Replace with custom clone method
|
|
8874
|
+
const ruleClone = clone(rule);
|
|
8875
|
+
ruleClone.marker.value = exports.CommentMarker.Regular;
|
|
8876
|
+
// Add the hashmark to the beginning of the comment text
|
|
8877
|
+
ruleClone.text.value = `${SPACE}${exports.CommentMarker.Hashmark}${ruleClone.text.value}`;
|
|
8878
|
+
return createNodeConversionResult([ruleClone], true);
|
|
8607
8879
|
}
|
|
8608
|
-
return [
|
|
8880
|
+
return createNodeConversionResult([rule], false);
|
|
8609
8881
|
// Leave any other comment rule as is
|
|
8610
8882
|
default:
|
|
8611
|
-
return [
|
|
8883
|
+
return createNodeConversionResult([rule], false);
|
|
8612
8884
|
}
|
|
8613
8885
|
}
|
|
8614
8886
|
}
|
|
@@ -8778,6 +9050,58 @@ class RegExpUtils {
|
|
|
8778
9050
|
}
|
|
8779
9051
|
}
|
|
8780
9052
|
|
|
9053
|
+
/**
|
|
9054
|
+
* @file Custom clone functions for AST nodes, this is probably the most efficient way to clone AST nodes.
|
|
9055
|
+
* @todo Maybe move them to parser classes as 'clone' methods
|
|
9056
|
+
*/
|
|
9057
|
+
/**
|
|
9058
|
+
* Clones a scriptlet rule node.
|
|
9059
|
+
*
|
|
9060
|
+
* @param node Node to clone
|
|
9061
|
+
* @returns Cloned node
|
|
9062
|
+
*/
|
|
9063
|
+
function cloneScriptletRuleNode(node) {
|
|
9064
|
+
return {
|
|
9065
|
+
type: node.type,
|
|
9066
|
+
children: node.children.map((child) => ({ ...child })),
|
|
9067
|
+
};
|
|
9068
|
+
}
|
|
9069
|
+
/**
|
|
9070
|
+
* Clones a domain list node.
|
|
9071
|
+
*
|
|
9072
|
+
* @param node Node to clone
|
|
9073
|
+
* @returns Cloned node
|
|
9074
|
+
*/
|
|
9075
|
+
function cloneDomainListNode(node) {
|
|
9076
|
+
return {
|
|
9077
|
+
type: node.type,
|
|
9078
|
+
separator: node.separator,
|
|
9079
|
+
children: node.children.map((domain) => ({ ...domain })),
|
|
9080
|
+
};
|
|
9081
|
+
}
|
|
9082
|
+
/**
|
|
9083
|
+
* Clones a modifier list node.
|
|
9084
|
+
*
|
|
9085
|
+
* @param node Node to clone
|
|
9086
|
+
* @returns Cloned node
|
|
9087
|
+
*/
|
|
9088
|
+
function cloneModifierListNode(node) {
|
|
9089
|
+
return {
|
|
9090
|
+
type: node.type,
|
|
9091
|
+
children: node.children.map((modifier) => {
|
|
9092
|
+
const res = {
|
|
9093
|
+
type: modifier.type,
|
|
9094
|
+
exception: modifier.exception,
|
|
9095
|
+
modifier: { ...modifier.modifier },
|
|
9096
|
+
};
|
|
9097
|
+
if (modifier.value) {
|
|
9098
|
+
res.value = { ...modifier.value };
|
|
9099
|
+
}
|
|
9100
|
+
return res;
|
|
9101
|
+
}),
|
|
9102
|
+
};
|
|
9103
|
+
}
|
|
9104
|
+
|
|
8781
9105
|
/**
|
|
8782
9106
|
* @file HTML filtering rule converter
|
|
8783
9107
|
*/
|
|
@@ -8790,16 +9114,22 @@ class RegExpUtils {
|
|
|
8790
9114
|
*
|
|
8791
9115
|
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
|
|
8792
9116
|
*/
|
|
8793
|
-
const
|
|
8794
|
-
const
|
|
9117
|
+
const ADG_HTML_DEFAULT_MAX_LENGTH = 8192;
|
|
9118
|
+
const ADG_HTML_CONVERSION_MAX_LENGTH = ADG_HTML_DEFAULT_MAX_LENGTH * 32;
|
|
8795
9119
|
const NOT_SPECIFIED = -1;
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
9120
|
+
var PseudoClasses$1;
|
|
9121
|
+
(function (PseudoClasses) {
|
|
9122
|
+
PseudoClasses["Contains"] = "contains";
|
|
9123
|
+
PseudoClasses["HasText"] = "has-text";
|
|
9124
|
+
PseudoClasses["MinTextLength"] = "min-text-length";
|
|
9125
|
+
})(PseudoClasses$1 || (PseudoClasses$1 = {}));
|
|
9126
|
+
var AttributeSelectors;
|
|
9127
|
+
(function (AttributeSelectors) {
|
|
9128
|
+
AttributeSelectors["MaxLength"] = "max-length";
|
|
9129
|
+
AttributeSelectors["MinLength"] = "min-length";
|
|
9130
|
+
AttributeSelectors["TagContent"] = "tag-content";
|
|
9131
|
+
AttributeSelectors["Wildcard"] = "wildcard";
|
|
9132
|
+
})(AttributeSelectors || (AttributeSelectors = {}));
|
|
8803
9133
|
/**
|
|
8804
9134
|
* HTML filtering rule converter class
|
|
8805
9135
|
*
|
|
@@ -8822,16 +9152,23 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8822
9152
|
* ```
|
|
8823
9153
|
*
|
|
8824
9154
|
* @param rule Rule node to convert
|
|
8825
|
-
* @returns
|
|
9155
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9156
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9157
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8826
9158
|
* @throws If the rule is invalid or cannot be converted
|
|
8827
9159
|
*/
|
|
8828
9160
|
static convertToAdg(rule) {
|
|
8829
|
-
//
|
|
8830
|
-
|
|
9161
|
+
// Ignore AdGuard rules
|
|
9162
|
+
if (rule.syntax === exports.AdblockSyntax.Adg) {
|
|
9163
|
+
return createNodeConversionResult([rule], false);
|
|
9164
|
+
}
|
|
9165
|
+
if (rule.syntax === exports.AdblockSyntax.Abp) {
|
|
9166
|
+
throw new RuleConversionError('Invalid rule, ABP does not support HTML filtering rules');
|
|
9167
|
+
}
|
|
8831
9168
|
// Prepare the conversion result
|
|
8832
9169
|
const conversionResult = [];
|
|
8833
9170
|
// Iterate over selector list
|
|
8834
|
-
for (const selector of
|
|
9171
|
+
for (const selector of rule.body.body.children) {
|
|
8835
9172
|
// Check selector, just in case
|
|
8836
9173
|
if (selector.type !== exports.CssTreeNodeType.Selector) {
|
|
8837
9174
|
throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
|
|
@@ -8858,24 +9195,24 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8858
9195
|
throw new RuleConversionError('Tag selector should be the first child, if present');
|
|
8859
9196
|
}
|
|
8860
9197
|
// Simply store the tag selector
|
|
8861
|
-
convertedSelector.children.push(
|
|
9198
|
+
convertedSelector.children.push(clone(node));
|
|
8862
9199
|
break;
|
|
8863
9200
|
case exports.CssTreeNodeType.AttributeSelector:
|
|
8864
9201
|
// Check if the attribute selector is a special AdGuard attribute
|
|
8865
9202
|
switch (node.name.name) {
|
|
8866
|
-
case
|
|
9203
|
+
case AttributeSelectors.MinLength:
|
|
8867
9204
|
minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
8868
9205
|
break;
|
|
8869
|
-
case
|
|
9206
|
+
case AttributeSelectors.MaxLength:
|
|
8870
9207
|
maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
8871
9208
|
break;
|
|
8872
|
-
case
|
|
8873
|
-
case
|
|
9209
|
+
case AttributeSelectors.TagContent:
|
|
9210
|
+
case AttributeSelectors.Wildcard:
|
|
8874
9211
|
CssTree.assertAttributeSelectorHasStringValue(node);
|
|
8875
|
-
convertedSelector.children.push(
|
|
9212
|
+
convertedSelector.children.push(clone(node));
|
|
8876
9213
|
break;
|
|
8877
9214
|
default:
|
|
8878
|
-
convertedSelector.children.push(
|
|
9215
|
+
convertedSelector.children.push(clone(node));
|
|
8879
9216
|
}
|
|
8880
9217
|
break;
|
|
8881
9218
|
case exports.CssTreeNodeType.PseudoClassSelector:
|
|
@@ -8889,18 +9226,18 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8889
9226
|
}
|
|
8890
9227
|
// Process the pseudo class based on its name
|
|
8891
9228
|
switch (node.name) {
|
|
8892
|
-
case
|
|
8893
|
-
case
|
|
9229
|
+
case PseudoClasses$1.HasText:
|
|
9230
|
+
case PseudoClasses$1.Contains:
|
|
8894
9231
|
// Check if the argument is a RegExp
|
|
8895
9232
|
if (RegExpUtils.isRegexPattern(arg.value)) {
|
|
8896
9233
|
// TODO: Add some support for RegExp patterns later
|
|
8897
9234
|
// Need to find a way to convert some RegExp patterns to glob patterns
|
|
8898
9235
|
throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
|
|
8899
9236
|
}
|
|
8900
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
9237
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.TagContent, arg.value));
|
|
8901
9238
|
break;
|
|
8902
9239
|
// https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
|
|
8903
|
-
case
|
|
9240
|
+
case PseudoClasses$1.MinTextLength:
|
|
8904
9241
|
minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
|
|
8905
9242
|
break;
|
|
8906
9243
|
default:
|
|
@@ -8912,10 +9249,10 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8912
9249
|
}
|
|
8913
9250
|
}
|
|
8914
9251
|
if (minLength !== NOT_SPECIFIED) {
|
|
8915
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
9252
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MinLength, String(minLength)));
|
|
8916
9253
|
}
|
|
8917
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
8918
|
-
?
|
|
9254
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MaxLength, String(maxLength === NOT_SPECIFIED
|
|
9255
|
+
? ADG_HTML_CONVERSION_MAX_LENGTH
|
|
8919
9256
|
: maxLength)));
|
|
8920
9257
|
// Create the converted rule
|
|
8921
9258
|
conversionResult.push({
|
|
@@ -8925,7 +9262,7 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8925
9262
|
// Convert the separator based on the exception status
|
|
8926
9263
|
separator: {
|
|
8927
9264
|
type: 'Value',
|
|
8928
|
-
value:
|
|
9265
|
+
value: rule.exception
|
|
8929
9266
|
? exports.CosmeticRuleSeparator.AdgHtmlFilteringException
|
|
8930
9267
|
: exports.CosmeticRuleSeparator.AdgHtmlFiltering,
|
|
8931
9268
|
},
|
|
@@ -8940,11 +9277,11 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8940
9277
|
}],
|
|
8941
9278
|
},
|
|
8942
9279
|
},
|
|
8943
|
-
exception:
|
|
8944
|
-
domains:
|
|
9280
|
+
exception: rule.exception,
|
|
9281
|
+
domains: cloneDomainListNode(rule.domains),
|
|
8945
9282
|
});
|
|
8946
9283
|
}
|
|
8947
|
-
return conversionResult;
|
|
9284
|
+
return createNodeConversionResult(conversionResult, true);
|
|
8948
9285
|
}
|
|
8949
9286
|
}
|
|
8950
9287
|
|
|
@@ -8965,96 +9302,38 @@ function getScriptletName(scriptletNode) {
|
|
|
8965
9302
|
return scriptletNode.children[0].value;
|
|
8966
9303
|
}
|
|
8967
9304
|
/**
|
|
8968
|
-
* Set name of the scriptlet
|
|
9305
|
+
* Set name of the scriptlet.
|
|
9306
|
+
* Modifies input `scriptletNode` if needed.
|
|
8969
9307
|
*
|
|
8970
9308
|
* @param scriptletNode Scriptlet node to set name of
|
|
8971
9309
|
* @param name Name to set
|
|
8972
|
-
* @returns Scriptlet node with the specified name
|
|
8973
|
-
* @throws If the scriptlet is empty
|
|
8974
9310
|
*/
|
|
8975
9311
|
function setScriptletName(scriptletNode, name) {
|
|
8976
|
-
if (scriptletNode.children.length
|
|
8977
|
-
|
|
9312
|
+
if (scriptletNode.children.length > 0) {
|
|
9313
|
+
// eslint-disable-next-line no-param-reassign
|
|
9314
|
+
scriptletNode.children[0].value = name;
|
|
8978
9315
|
}
|
|
8979
|
-
const scriptletNodeClone = cloneDeep(scriptletNode);
|
|
8980
|
-
scriptletNodeClone.children[0].value = name;
|
|
8981
|
-
return scriptletNodeClone;
|
|
8982
9316
|
}
|
|
8983
9317
|
/**
|
|
8984
9318
|
* Set quote type of the scriptlet parameters
|
|
8985
9319
|
*
|
|
8986
9320
|
* @param scriptletNode Scriptlet node to set quote type of
|
|
8987
9321
|
* @param quoteType Preferred quote type
|
|
8988
|
-
* @returns Scriptlet node with the specified quote type
|
|
8989
9322
|
*/
|
|
8990
9323
|
function setScriptletQuoteType(scriptletNode, quoteType) {
|
|
8991
|
-
if (scriptletNode.children.length
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
|
|
8996
|
-
scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
|
|
8997
|
-
}
|
|
8998
|
-
return scriptletNodeClone;
|
|
8999
|
-
}
|
|
9000
|
-
|
|
9001
|
-
/**
|
|
9002
|
-
* @file Scriptlet conversions from ABP and uBO to ADG
|
|
9003
|
-
*/
|
|
9004
|
-
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
9005
|
-
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
9006
|
-
/**
|
|
9007
|
-
* Helper class for converting scriptlets from ABP and uBO to ADG
|
|
9008
|
-
*/
|
|
9009
|
-
class AdgScriptletConverter {
|
|
9010
|
-
/**
|
|
9011
|
-
* Helper function to convert scriptlets to ADG. We implement the core
|
|
9012
|
-
* logic here to avoid code duplication.
|
|
9013
|
-
*
|
|
9014
|
-
* @param scriptletNode Scriptlet parameter list node to convert
|
|
9015
|
-
* @param prefix Prefix to add to the scriptlet name
|
|
9016
|
-
* @returns Converted scriptlet parameter list node
|
|
9017
|
-
*/
|
|
9018
|
-
static convertToAdg(scriptletNode, prefix) {
|
|
9019
|
-
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
9020
|
-
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), exports.QuoteType.None);
|
|
9021
|
-
// Clone the node to avoid any side effects
|
|
9022
|
-
let result = cloneDeep(scriptletNode);
|
|
9023
|
-
// Only add prefix if it's not already there
|
|
9024
|
-
if (!scriptletName.startsWith(prefix)) {
|
|
9025
|
-
result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
|
|
9324
|
+
if (scriptletNode.children.length > 0) {
|
|
9325
|
+
for (let i = 0; i < scriptletNode.children.length; i += 1) {
|
|
9326
|
+
// eslint-disable-next-line no-param-reassign
|
|
9327
|
+
scriptletNode.children[i].value = QuoteUtils.setStringQuoteType(scriptletNode.children[i].value, quoteType);
|
|
9026
9328
|
}
|
|
9027
|
-
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
9028
|
-
result = setScriptletQuoteType(result, exports.QuoteType.Single);
|
|
9029
|
-
return result;
|
|
9030
9329
|
}
|
|
9031
|
-
/**
|
|
9032
|
-
* Converts an ABP snippet node to ADG scriptlet node, if possible.
|
|
9033
|
-
*
|
|
9034
|
-
* @param scriptletNode Scriptlet node to convert
|
|
9035
|
-
* @returns Converted scriptlet node
|
|
9036
|
-
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
9037
|
-
* @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
|
|
9038
|
-
*/
|
|
9039
|
-
static convertFromAbp = (scriptletNode) => {
|
|
9040
|
-
return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
|
|
9041
|
-
};
|
|
9042
|
-
/**
|
|
9043
|
-
* Convert a uBO scriptlet node to ADG scriptlet node, if possible.
|
|
9044
|
-
*
|
|
9045
|
-
* @param scriptletNode Scriptlet node to convert
|
|
9046
|
-
* @returns Converted scriptlet node
|
|
9047
|
-
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
9048
|
-
* @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
|
|
9049
|
-
*/
|
|
9050
|
-
static convertFromUbo = (scriptletNode) => {
|
|
9051
|
-
return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
|
|
9052
|
-
};
|
|
9053
9330
|
}
|
|
9054
9331
|
|
|
9055
9332
|
/**
|
|
9056
9333
|
* @file Scriptlet injection rule converter
|
|
9057
9334
|
*/
|
|
9335
|
+
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
9336
|
+
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
9058
9337
|
/**
|
|
9059
9338
|
* Scriptlet injection rule converter class
|
|
9060
9339
|
*
|
|
@@ -9065,38 +9344,91 @@ class ScriptletRuleConverter extends RuleConverterBase {
|
|
|
9065
9344
|
* Converts a scriptlet injection rule to AdGuard format, if possible.
|
|
9066
9345
|
*
|
|
9067
9346
|
* @param rule Rule node to convert
|
|
9068
|
-
* @returns
|
|
9347
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9348
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9349
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9069
9350
|
* @throws If the rule is invalid or cannot be converted
|
|
9070
9351
|
*/
|
|
9071
9352
|
static convertToAdg(rule) {
|
|
9072
|
-
//
|
|
9073
|
-
|
|
9353
|
+
// Ignore AdGuard rules
|
|
9354
|
+
if (rule.syntax === exports.AdblockSyntax.Adg) {
|
|
9355
|
+
return createNodeConversionResult([rule], false);
|
|
9356
|
+
}
|
|
9357
|
+
const separator = rule.separator.value;
|
|
9358
|
+
let convertedSeparator = separator;
|
|
9359
|
+
convertedSeparator = rule.exception
|
|
9360
|
+
? exports.CosmeticRuleSeparator.AdgJsInjectionException
|
|
9361
|
+
: exports.CosmeticRuleSeparator.AdgJsInjection;
|
|
9074
9362
|
const convertedScriptlets = [];
|
|
9075
|
-
for (const scriptlet of
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9363
|
+
for (const scriptlet of rule.body.children) {
|
|
9364
|
+
// Clone the node to avoid any side effects
|
|
9365
|
+
const scriptletClone = cloneScriptletRuleNode(scriptlet);
|
|
9366
|
+
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
9367
|
+
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), exports.QuoteType.None);
|
|
9368
|
+
// Add prefix if it's not already there
|
|
9369
|
+
let prefix;
|
|
9370
|
+
switch (rule.syntax) {
|
|
9371
|
+
case exports.AdblockSyntax.Abp:
|
|
9372
|
+
prefix = ABP_SCRIPTLET_PREFIX;
|
|
9373
|
+
break;
|
|
9374
|
+
case exports.AdblockSyntax.Ubo:
|
|
9375
|
+
prefix = UBO_SCRIPTLET_PREFIX;
|
|
9376
|
+
break;
|
|
9377
|
+
default:
|
|
9378
|
+
prefix = EMPTY;
|
|
9081
9379
|
}
|
|
9082
|
-
|
|
9083
|
-
|
|
9380
|
+
if (!scriptletName.startsWith(prefix)) {
|
|
9381
|
+
setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
|
|
9084
9382
|
}
|
|
9383
|
+
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
9384
|
+
setScriptletQuoteType(scriptletClone, exports.QuoteType.Single);
|
|
9385
|
+
convertedScriptlets.push(scriptletClone);
|
|
9085
9386
|
}
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
return convertedScriptlets.map((scriptlet) => {
|
|
9091
|
-
return {
|
|
9092
|
-
...ruleNode,
|
|
9387
|
+
return createNodeConversionResult(convertedScriptlets.map((scriptlet) => {
|
|
9388
|
+
const res = {
|
|
9389
|
+
category: rule.category,
|
|
9390
|
+
type: rule.type,
|
|
9093
9391
|
syntax: exports.AdblockSyntax.Adg,
|
|
9392
|
+
exception: rule.exception,
|
|
9393
|
+
domains: cloneDomainListNode(rule.domains),
|
|
9394
|
+
separator: {
|
|
9395
|
+
type: 'Value',
|
|
9396
|
+
value: convertedSeparator,
|
|
9397
|
+
},
|
|
9094
9398
|
body: {
|
|
9095
|
-
|
|
9399
|
+
type: rule.body.type,
|
|
9096
9400
|
children: [scriptlet],
|
|
9097
9401
|
},
|
|
9098
9402
|
};
|
|
9099
|
-
|
|
9403
|
+
if (rule.modifiers) {
|
|
9404
|
+
res.modifiers = cloneModifierListNode(rule.modifiers);
|
|
9405
|
+
}
|
|
9406
|
+
return res;
|
|
9407
|
+
}), true);
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
|
|
9411
|
+
/**
|
|
9412
|
+
* A very simple map extension that allows to store multiple values for the same key
|
|
9413
|
+
* by storing them in an array.
|
|
9414
|
+
*
|
|
9415
|
+
* @todo Add more methods if needed
|
|
9416
|
+
*/
|
|
9417
|
+
class MultiValueMap extends Map {
|
|
9418
|
+
/**
|
|
9419
|
+
* Adds a value to the map. If the key already exists, the value will be appended to the existing array,
|
|
9420
|
+
* otherwise a new array will be created for the key.
|
|
9421
|
+
*
|
|
9422
|
+
* @param key Key to add
|
|
9423
|
+
* @param values Value(s) to add
|
|
9424
|
+
*/
|
|
9425
|
+
add(key, ...values) {
|
|
9426
|
+
let currentValues = super.get(key);
|
|
9427
|
+
if (isUndefined(currentValues)) {
|
|
9428
|
+
currentValues = [];
|
|
9429
|
+
super.set(key, values);
|
|
9430
|
+
}
|
|
9431
|
+
currentValues.push(...values);
|
|
9100
9432
|
}
|
|
9101
9433
|
}
|
|
9102
9434
|
|
|
@@ -9122,69 +9454,115 @@ class AdgCosmeticRuleModifierConverter {
|
|
|
9122
9454
|
* Converts a uBO cosmetic rule modifier list to ADG, if possible.
|
|
9123
9455
|
*
|
|
9124
9456
|
* @param modifierList Cosmetic rule modifier list node to convert
|
|
9125
|
-
* @returns
|
|
9457
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9458
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9459
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9126
9460
|
* @throws If the modifier list cannot be converted
|
|
9127
9461
|
* @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
|
|
9128
9462
|
*/
|
|
9129
|
-
static convertFromUbo
|
|
9130
|
-
const
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
//
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
|
|
9148
|
-
: modifierValue));
|
|
9149
|
-
break;
|
|
9150
|
-
default:
|
|
9151
|
-
// Leave the modifier as-is
|
|
9152
|
-
convertedModifierList.children.push(modifier);
|
|
9463
|
+
static convertFromUbo(modifierList) {
|
|
9464
|
+
const conversionMap = new MultiValueMap();
|
|
9465
|
+
modifierList.children.forEach((modifier, index) => {
|
|
9466
|
+
// :matches-path
|
|
9467
|
+
if (modifier.modifier.value === UBO_MATCHES_PATH_OPERATOR) {
|
|
9468
|
+
if (!modifier.value) {
|
|
9469
|
+
throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
|
|
9470
|
+
}
|
|
9471
|
+
const value = RegExpUtils.isRegexPattern(modifier.value.value)
|
|
9472
|
+
? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
|
|
9473
|
+
: modifier.value.value;
|
|
9474
|
+
// Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
|
|
9475
|
+
conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
|
|
9476
|
+
// We should negate the regexp if the modifier is an exception
|
|
9477
|
+
modifier.exception
|
|
9478
|
+
// eslint-disable-next-line max-len
|
|
9479
|
+
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}`
|
|
9480
|
+
: value));
|
|
9153
9481
|
}
|
|
9154
|
-
}
|
|
9155
|
-
|
|
9156
|
-
|
|
9482
|
+
});
|
|
9483
|
+
// Check if we have any converted modifiers
|
|
9484
|
+
if (conversionMap.size) {
|
|
9485
|
+
const modifierListClone = clone(modifierList);
|
|
9486
|
+
// Replace the original modifiers with the converted ones
|
|
9487
|
+
modifierListClone.children = modifierListClone.children.map((modifier, index) => {
|
|
9488
|
+
const convertedModifier = conversionMap.get(index);
|
|
9489
|
+
return convertedModifier ?? modifier;
|
|
9490
|
+
}).flat();
|
|
9491
|
+
return createConversionResult(modifierListClone, true);
|
|
9492
|
+
}
|
|
9493
|
+
// Otherwise, just return the original modifier list
|
|
9494
|
+
return createConversionResult(modifierList, false);
|
|
9495
|
+
}
|
|
9157
9496
|
}
|
|
9158
9497
|
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9498
|
+
var PseudoClasses;
|
|
9499
|
+
(function (PseudoClasses) {
|
|
9500
|
+
PseudoClasses["AbpContains"] = "-abp-contains";
|
|
9501
|
+
PseudoClasses["AbpHas"] = "-abp-has";
|
|
9502
|
+
PseudoClasses["Contains"] = "contains";
|
|
9503
|
+
PseudoClasses["Has"] = "has";
|
|
9504
|
+
PseudoClasses["HasText"] = "has-text";
|
|
9505
|
+
PseudoClasses["MatchesCss"] = "matches-css";
|
|
9506
|
+
PseudoClasses["MatchesCssAfter"] = "matches-css-after";
|
|
9507
|
+
PseudoClasses["MatchesCssBefore"] = "matches-css-before";
|
|
9508
|
+
PseudoClasses["Not"] = "not";
|
|
9509
|
+
})(PseudoClasses || (PseudoClasses = {}));
|
|
9510
|
+
var PseudoElements;
|
|
9511
|
+
(function (PseudoElements) {
|
|
9512
|
+
PseudoElements["After"] = "after";
|
|
9513
|
+
PseudoElements["Before"] = "before";
|
|
9514
|
+
})(PseudoElements || (PseudoElements = {}));
|
|
9515
|
+
const PSEUDO_ELEMENT_NAMES = new Set([
|
|
9516
|
+
PseudoElements.After,
|
|
9517
|
+
PseudoElements.Before,
|
|
9518
|
+
]);
|
|
9519
|
+
const LEGACY_MATCHES_CSS_NAMES = new Set([
|
|
9520
|
+
PseudoClasses.MatchesCssAfter,
|
|
9521
|
+
PseudoClasses.MatchesCssBefore,
|
|
9522
|
+
]);
|
|
9523
|
+
const LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES = new Set([
|
|
9524
|
+
PseudoClasses.Not,
|
|
9525
|
+
PseudoClasses.MatchesCssBefore,
|
|
9526
|
+
PseudoClasses.MatchesCssAfter,
|
|
9527
|
+
]);
|
|
9528
|
+
const CSS_CONVERSION_INDICATOR_PSEUDO_NAMES = new Set([
|
|
9529
|
+
PseudoClasses.AbpContains,
|
|
9530
|
+
PseudoClasses.AbpHas,
|
|
9531
|
+
PseudoClasses.HasText,
|
|
9532
|
+
]);
|
|
9172
9533
|
/**
|
|
9173
9534
|
* Converts some pseudo-classes to pseudo-elements. For example:
|
|
9174
9535
|
* - `:before` → `::before`
|
|
9175
9536
|
*
|
|
9176
9537
|
* @param selectorList Selector list to convert
|
|
9177
|
-
* @returns
|
|
9538
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9539
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9540
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9178
9541
|
*/
|
|
9179
9542
|
function convertToPseudoElements(selectorList) {
|
|
9180
|
-
//
|
|
9181
|
-
const
|
|
9543
|
+
// Check conversion indications before doing any heavy work
|
|
9544
|
+
const hasIndicator = ecssTree.find(
|
|
9545
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9546
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9547
|
+
selectorList, (node) => node.type === exports.CssTreeNodeType.PseudoClassSelector && PSEUDO_ELEMENT_NAMES.has(node.name));
|
|
9548
|
+
if (!hasIndicator) {
|
|
9549
|
+
return createConversionResult(selectorList, false);
|
|
9550
|
+
}
|
|
9551
|
+
// Make a clone of the selector list to avoid modifying the original one,
|
|
9552
|
+
// then convert & return the cloned version
|
|
9553
|
+
const selectorListClone = clone(selectorList);
|
|
9554
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9555
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9182
9556
|
ecssTree.walk(selectorListClone, {
|
|
9557
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9558
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9183
9559
|
leave: (node) => {
|
|
9184
9560
|
if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
|
|
9185
|
-
//
|
|
9186
|
-
//
|
|
9187
|
-
|
|
9561
|
+
// If the pseudo-class is `:before` or `:after`, then we should
|
|
9562
|
+
// convert the node type to pseudo-element:
|
|
9563
|
+
// :after → ::after
|
|
9564
|
+
// :before → ::before
|
|
9565
|
+
if (PSEUDO_ELEMENT_NAMES.has(node.name)) {
|
|
9188
9566
|
Object.assign(node, {
|
|
9189
9567
|
...node,
|
|
9190
9568
|
type: exports.CssTreeNodeType.PseudoElementSelector,
|
|
@@ -9193,7 +9571,7 @@ function convertToPseudoElements(selectorList) {
|
|
|
9193
9571
|
}
|
|
9194
9572
|
},
|
|
9195
9573
|
});
|
|
9196
|
-
return selectorListClone;
|
|
9574
|
+
return createConversionResult(selectorListClone, true);
|
|
9197
9575
|
}
|
|
9198
9576
|
/**
|
|
9199
9577
|
* Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
|
|
@@ -9202,33 +9580,36 @@ function convertToPseudoElements(selectorList) {
|
|
|
9202
9580
|
* - `:matches-css-after(...)` → `:matches-css(after, ...)`
|
|
9203
9581
|
*
|
|
9204
9582
|
* @param node Node to convert
|
|
9583
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9584
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9585
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9205
9586
|
* @throws If the node is invalid
|
|
9206
9587
|
*/
|
|
9207
9588
|
function convertLegacyMatchesCss(node) {
|
|
9208
|
-
|
|
9209
|
-
if (
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9589
|
+
// Check conversion indications before doing any heavy work
|
|
9590
|
+
if (node.type !== exports.CssTreeNodeType.PseudoClassSelector || !LEGACY_MATCHES_CSS_NAMES.has(node.name)) {
|
|
9591
|
+
return createConversionResult(node, false);
|
|
9592
|
+
}
|
|
9593
|
+
const nodeClone = clone(node);
|
|
9594
|
+
if (!nodeClone.children || nodeClone.children.length < 1) {
|
|
9595
|
+
throw new Error(`Invalid ${node.name} pseudo-class: missing argument`);
|
|
9596
|
+
}
|
|
9597
|
+
// Rename the pseudo-class
|
|
9598
|
+
nodeClone.name = PseudoClasses.MatchesCss;
|
|
9599
|
+
// Remove the 'matches-css-' prefix to get the direction
|
|
9600
|
+
const direction = node.name.substring(PseudoClasses.MatchesCss.length + 1);
|
|
9601
|
+
// Add the direction to the first raw argument
|
|
9602
|
+
const arg = nodeClone.children[0];
|
|
9603
|
+
// Check argument
|
|
9604
|
+
if (!arg) {
|
|
9605
|
+
throw new Error(`Invalid ${node.name} pseudo-class: argument shouldn't be null`);
|
|
9606
|
+
}
|
|
9607
|
+
if (arg.type !== exports.CssTreeNodeType.Raw) {
|
|
9608
|
+
throw new Error(`Invalid ${node.name} pseudo-class: unexpected argument type`);
|
|
9609
|
+
}
|
|
9610
|
+
// Add the direction as the first argument
|
|
9611
|
+
arg.value = `${direction},${arg.value}`;
|
|
9612
|
+
return createConversionResult(nodeClone, true);
|
|
9232
9613
|
}
|
|
9233
9614
|
/**
|
|
9234
9615
|
* Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
|
|
@@ -9238,16 +9619,40 @@ function convertLegacyMatchesCss(node) {
|
|
|
9238
9619
|
* - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
|
|
9239
9620
|
*
|
|
9240
9621
|
* @param selectorList Selector list AST to convert
|
|
9241
|
-
* @returns
|
|
9622
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9623
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9624
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9242
9625
|
*/
|
|
9243
9626
|
function convertFromLegacyExtendedCss(selectorList) {
|
|
9244
|
-
//
|
|
9245
|
-
const
|
|
9627
|
+
// Check conversion indications before doing any heavy work
|
|
9628
|
+
const hasIndicator = ecssTree.find(
|
|
9629
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9630
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9631
|
+
selectorList, (node) => {
|
|
9632
|
+
if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
|
|
9633
|
+
return LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES.has(node.name);
|
|
9634
|
+
}
|
|
9635
|
+
if (node.type === exports.CssTreeNodeType.AttributeSelector) {
|
|
9636
|
+
return node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX);
|
|
9637
|
+
}
|
|
9638
|
+
return false;
|
|
9639
|
+
});
|
|
9640
|
+
if (!hasIndicator) {
|
|
9641
|
+
return createConversionResult(selectorList, false);
|
|
9642
|
+
}
|
|
9643
|
+
const selectorListClone = clone(selectorList);
|
|
9644
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9645
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9246
9646
|
ecssTree.walk(selectorListClone, {
|
|
9647
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9648
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9247
9649
|
leave: (node) => {
|
|
9248
9650
|
// :matches-css-before(arg) → :matches-css(before,arg)
|
|
9249
9651
|
// :matches-css-after(arg) → :matches-css(after,arg)
|
|
9250
|
-
convertLegacyMatchesCss(node);
|
|
9652
|
+
const convertedLegacyExtCss = convertLegacyMatchesCss(node);
|
|
9653
|
+
if (convertedLegacyExtCss.isConverted) {
|
|
9654
|
+
Object.assign(node, convertedLegacyExtCss.result);
|
|
9655
|
+
}
|
|
9251
9656
|
// [-ext-name=...] → :name(...)
|
|
9252
9657
|
// [-ext-name='...'] → :name(...)
|
|
9253
9658
|
// [-ext-name="..."] → :name(...)
|
|
@@ -9261,7 +9666,7 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9261
9666
|
// Remove the '-ext-' prefix to get the pseudo-class name
|
|
9262
9667
|
const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
|
|
9263
9668
|
// Prepare the children list for the pseudo-class node
|
|
9264
|
-
const children =
|
|
9669
|
+
const children = [];
|
|
9265
9670
|
// TODO: Change String node to Raw node to drop the quotes.
|
|
9266
9671
|
// The structure of the node is the same, just the type
|
|
9267
9672
|
// is different and generate() will generate the quotes
|
|
@@ -9274,7 +9679,7 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9274
9679
|
// For example, if the input is [-ext-has="> .selector"], then
|
|
9275
9680
|
// we need to parse "> .selector" as a selector instead of string
|
|
9276
9681
|
// it as a raw value
|
|
9277
|
-
if ([
|
|
9682
|
+
if ([PseudoClasses.Has, PseudoClasses.Not].includes(name)) {
|
|
9278
9683
|
// Get the value of the attribute selector
|
|
9279
9684
|
const { value } = node;
|
|
9280
9685
|
// If the value is an identifier, then simply push it to the
|
|
@@ -9284,10 +9689,12 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9284
9689
|
}
|
|
9285
9690
|
else if (value.type === exports.CssTreeNodeType.String) {
|
|
9286
9691
|
// Parse the value as a selector
|
|
9287
|
-
const parsedChildren = CssTree.
|
|
9692
|
+
const parsedChildren = CssTree.parsePlain(value.value, exports.CssTreeParserContext.selectorList);
|
|
9288
9693
|
// Don't forget convert the parsed AST again, because
|
|
9289
9694
|
// it was a raw string before
|
|
9290
|
-
|
|
9695
|
+
const convertedChildren = convertFromLegacyExtendedCss(parsedChildren);
|
|
9696
|
+
// Push the converted children to the list
|
|
9697
|
+
children.push(convertedChildren.result);
|
|
9291
9698
|
}
|
|
9292
9699
|
}
|
|
9293
9700
|
else {
|
|
@@ -9314,14 +9721,12 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9314
9721
|
children,
|
|
9315
9722
|
};
|
|
9316
9723
|
// Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
|
|
9317
|
-
convertLegacyMatchesCss(pseudoNode);
|
|
9318
|
-
|
|
9319
|
-
// keep the reference to the original node
|
|
9320
|
-
Object.assign(node, pseudoNode);
|
|
9724
|
+
const convertedPseudoNode = convertLegacyMatchesCss(pseudoNode);
|
|
9725
|
+
Object.assign(node, convertedPseudoNode.isConverted ? convertedPseudoNode.result : pseudoNode);
|
|
9321
9726
|
}
|
|
9322
9727
|
},
|
|
9323
9728
|
});
|
|
9324
|
-
return selectorListClone;
|
|
9729
|
+
return createConversionResult(selectorListClone, true);
|
|
9325
9730
|
}
|
|
9326
9731
|
/**
|
|
9327
9732
|
* CSS selector converter
|
|
@@ -9333,32 +9738,51 @@ class CssSelectorConverter extends ConverterBase {
|
|
|
9333
9738
|
* Converts Extended CSS elements to AdGuard-compatible ones
|
|
9334
9739
|
*
|
|
9335
9740
|
* @param selectorList Selector list to convert
|
|
9336
|
-
* @returns
|
|
9741
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9742
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9743
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9337
9744
|
* @throws If the rule is invalid or incompatible
|
|
9338
9745
|
*/
|
|
9339
9746
|
static convertToAdg(selectorList) {
|
|
9340
9747
|
// First, convert
|
|
9341
9748
|
// - legacy Extended CSS selectors to the modern Extended CSS syntax and
|
|
9342
9749
|
// - some pseudo-classes to pseudo-elements
|
|
9343
|
-
const
|
|
9750
|
+
const legacyExtCssConverted = convertFromLegacyExtendedCss(selectorList);
|
|
9751
|
+
const pseudoElementsConverted = convertToPseudoElements(legacyExtCssConverted.result);
|
|
9752
|
+
const hasIndicator = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted || ecssTree.find(
|
|
9753
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9754
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9755
|
+
selectorList,
|
|
9756
|
+
// eslint-disable-next-line max-len
|
|
9757
|
+
(node) => node.type === exports.CssTreeNodeType.PseudoClassSelector && CSS_CONVERSION_INDICATOR_PSEUDO_NAMES.has(node.name));
|
|
9758
|
+
if (!hasIndicator) {
|
|
9759
|
+
return createConversionResult(selectorList, false);
|
|
9760
|
+
}
|
|
9761
|
+
const selectorListClone = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted
|
|
9762
|
+
? pseudoElementsConverted.result
|
|
9763
|
+
: clone(selectorList);
|
|
9344
9764
|
// Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
|
|
9765
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9766
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9345
9767
|
ecssTree.walk(selectorListClone, {
|
|
9768
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9769
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9346
9770
|
leave: (node) => {
|
|
9347
9771
|
if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
|
|
9348
9772
|
// :-abp-contains(...) → :contains(...)
|
|
9349
9773
|
// :has-text(...) → :contains(...)
|
|
9350
|
-
if (node.name ===
|
|
9351
|
-
CssTree.renamePseudoClass(node,
|
|
9774
|
+
if (node.name === PseudoClasses.AbpContains || node.name === PseudoClasses.HasText) {
|
|
9775
|
+
CssTree.renamePseudoClass(node, PseudoClasses.Contains);
|
|
9352
9776
|
}
|
|
9353
9777
|
// :-abp-has(...) → :has(...)
|
|
9354
|
-
if (node.name ===
|
|
9355
|
-
CssTree.renamePseudoClass(node,
|
|
9778
|
+
if (node.name === PseudoClasses.AbpHas) {
|
|
9779
|
+
CssTree.renamePseudoClass(node, PseudoClasses.Has);
|
|
9356
9780
|
}
|
|
9357
9781
|
// TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
|
|
9358
9782
|
}
|
|
9359
9783
|
},
|
|
9360
9784
|
});
|
|
9361
|
-
return selectorListClone;
|
|
9785
|
+
return createConversionResult(selectorListClone, true);
|
|
9362
9786
|
}
|
|
9363
9787
|
}
|
|
9364
9788
|
|
|
@@ -9375,27 +9799,39 @@ class CssInjectionRuleConverter extends RuleConverterBase {
|
|
|
9375
9799
|
* Converts a CSS injection rule to AdGuard format, if possible.
|
|
9376
9800
|
*
|
|
9377
9801
|
* @param rule Rule node to convert
|
|
9378
|
-
* @returns
|
|
9802
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9803
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9804
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9379
9805
|
* @throws If the rule is invalid or cannot be converted
|
|
9380
9806
|
*/
|
|
9381
9807
|
static convertToAdg(rule) {
|
|
9382
|
-
|
|
9383
|
-
|
|
9808
|
+
const separator = rule.separator.value;
|
|
9809
|
+
let convertedSeparator = separator;
|
|
9384
9810
|
// Change the separator if the rule contains ExtendedCSS selectors
|
|
9385
|
-
if (CssTree.hasAnySelectorExtendedCssNode(
|
|
9386
|
-
|
|
9811
|
+
if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList) || rule.body.remove) {
|
|
9812
|
+
convertedSeparator = rule.exception
|
|
9387
9813
|
? exports.CosmeticRuleSeparator.AdgExtendedCssInjectionException
|
|
9388
9814
|
: exports.CosmeticRuleSeparator.AdgExtendedCssInjection;
|
|
9389
9815
|
}
|
|
9390
9816
|
else {
|
|
9391
|
-
|
|
9817
|
+
convertedSeparator = rule.exception
|
|
9392
9818
|
? exports.CosmeticRuleSeparator.AdgCssInjectionException
|
|
9393
9819
|
: exports.CosmeticRuleSeparator.AdgCssInjection;
|
|
9394
9820
|
}
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9821
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
|
|
9822
|
+
// Check if the rule needs to be converted
|
|
9823
|
+
if (!(rule.syntax === exports.AdblockSyntax.Common || rule.syntax === exports.AdblockSyntax.Adg)
|
|
9824
|
+
|| separator !== convertedSeparator
|
|
9825
|
+
|| convertedSelectorList.isConverted) {
|
|
9826
|
+
// TODO: Replace with custom clone method
|
|
9827
|
+
const ruleClone = clone(rule);
|
|
9828
|
+
ruleClone.syntax = exports.AdblockSyntax.Adg;
|
|
9829
|
+
ruleClone.separator.value = convertedSeparator;
|
|
9830
|
+
ruleClone.body.selectorList = convertedSelectorList.result;
|
|
9831
|
+
return createNodeConversionResult([ruleClone], true);
|
|
9832
|
+
}
|
|
9833
|
+
// Otherwise, return the original rule
|
|
9834
|
+
return createNodeConversionResult([rule], false);
|
|
9399
9835
|
}
|
|
9400
9836
|
}
|
|
9401
9837
|
|
|
@@ -9412,27 +9848,39 @@ class ElementHidingRuleConverter extends RuleConverterBase {
|
|
|
9412
9848
|
* Converts an element hiding rule to AdGuard format, if possible.
|
|
9413
9849
|
*
|
|
9414
9850
|
* @param rule Rule node to convert
|
|
9415
|
-
* @returns
|
|
9851
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9852
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9853
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9416
9854
|
* @throws If the rule is invalid or cannot be converted
|
|
9417
9855
|
*/
|
|
9418
9856
|
static convertToAdg(rule) {
|
|
9419
|
-
|
|
9420
|
-
|
|
9857
|
+
const separator = rule.separator.value;
|
|
9858
|
+
let convertedSeparator = separator;
|
|
9421
9859
|
// Change the separator if the rule contains ExtendedCSS selectors
|
|
9422
|
-
if (CssTree.hasAnySelectorExtendedCssNode(
|
|
9423
|
-
|
|
9860
|
+
if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList)) {
|
|
9861
|
+
convertedSeparator = rule.exception
|
|
9424
9862
|
? exports.CosmeticRuleSeparator.ExtendedElementHidingException
|
|
9425
9863
|
: exports.CosmeticRuleSeparator.ExtendedElementHiding;
|
|
9426
9864
|
}
|
|
9427
9865
|
else {
|
|
9428
|
-
|
|
9866
|
+
convertedSeparator = rule.exception
|
|
9429
9867
|
? exports.CosmeticRuleSeparator.ElementHidingException
|
|
9430
9868
|
: exports.CosmeticRuleSeparator.ElementHiding;
|
|
9431
9869
|
}
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9870
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
|
|
9871
|
+
// Check if the rule needs to be converted
|
|
9872
|
+
if (!(rule.syntax === exports.AdblockSyntax.Common || rule.syntax === exports.AdblockSyntax.Adg)
|
|
9873
|
+
|| separator !== convertedSeparator
|
|
9874
|
+
|| convertedSelectorList.isConverted) {
|
|
9875
|
+
// TODO: Replace with custom clone method
|
|
9876
|
+
const ruleClone = clone(rule);
|
|
9877
|
+
ruleClone.syntax = exports.AdblockSyntax.Adg;
|
|
9878
|
+
ruleClone.separator.value = convertedSeparator;
|
|
9879
|
+
ruleClone.body.selectorList = convertedSelectorList.result;
|
|
9880
|
+
return createNodeConversionResult([ruleClone], true);
|
|
9881
|
+
}
|
|
9882
|
+
// Otherwise, return the original rule
|
|
9883
|
+
return createNodeConversionResult([rule], false);
|
|
9436
9884
|
}
|
|
9437
9885
|
}
|
|
9438
9886
|
|
|
@@ -9460,7 +9908,7 @@ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false
|
|
|
9460
9908
|
},
|
|
9461
9909
|
};
|
|
9462
9910
|
if (!isUndefined(modifiers)) {
|
|
9463
|
-
result.modifiers =
|
|
9911
|
+
result.modifiers = clone(modifiers);
|
|
9464
9912
|
}
|
|
9465
9913
|
return result;
|
|
9466
9914
|
}
|
|
@@ -9480,32 +9928,37 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
|
9480
9928
|
* Converts a header removal rule to AdGuard syntax, if possible.
|
|
9481
9929
|
*
|
|
9482
9930
|
* @param rule Rule node to convert
|
|
9483
|
-
* @returns
|
|
9931
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9932
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9933
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9484
9934
|
* @throws If the rule is invalid or cannot be converted
|
|
9935
|
+
* @example
|
|
9936
|
+
* If the input rule is:
|
|
9937
|
+
* ```adblock
|
|
9938
|
+
* example.com##^responseheader(header-name)
|
|
9939
|
+
* ```
|
|
9940
|
+
* The output will be:
|
|
9941
|
+
* ```adblock
|
|
9942
|
+
* ||example.com^$removeheader=header-name
|
|
9943
|
+
* ```
|
|
9485
9944
|
*/
|
|
9486
9945
|
static convertToAdg(rule) {
|
|
9487
|
-
// Clone the provided AST node to avoid side effects
|
|
9488
|
-
const ruleNode = cloneDeep(rule);
|
|
9489
9946
|
// TODO: Add support for ABP syntax once it starts supporting header removal rules
|
|
9490
|
-
//
|
|
9491
|
-
if (
|
|
9492
|
-
||
|
|
9493
|
-
||
|
|
9494
|
-
||
|
|
9495
|
-
|
|
9947
|
+
// Leave the rule as is if it's not a header removal rule
|
|
9948
|
+
if (rule.category !== exports.RuleCategory.Cosmetic
|
|
9949
|
+
|| rule.type !== exports.CosmeticRuleType.HtmlFilteringRule
|
|
9950
|
+
|| rule.body.body.type !== exports.CssTreeNodeType.Function
|
|
9951
|
+
|| rule.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
|
|
9952
|
+
return createNodeConversionResult([rule], false);
|
|
9496
9953
|
}
|
|
9497
9954
|
// Prepare network rule pattern
|
|
9498
|
-
|
|
9499
|
-
if (
|
|
9955
|
+
const pattern = [];
|
|
9956
|
+
if (rule.domains.children.length === 1) {
|
|
9500
9957
|
// If the rule has only one domain, we can use a simple network rule pattern:
|
|
9501
9958
|
// ||single-domain-from-the-rule^
|
|
9502
|
-
pattern
|
|
9503
|
-
ADBLOCK_URL_START,
|
|
9504
|
-
ruleNode.domains.children[0].value,
|
|
9505
|
-
ADBLOCK_URL_SEPARATOR,
|
|
9506
|
-
].join(EMPTY);
|
|
9959
|
+
pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
|
|
9507
9960
|
}
|
|
9508
|
-
else if (
|
|
9961
|
+
else if (rule.domains.children.length > 1) {
|
|
9509
9962
|
// TODO: Add support for multiple domains, for example:
|
|
9510
9963
|
// example.com,example.org,example.net##^responseheader(header-name)
|
|
9511
9964
|
// We should consider allowing $domain with $removeheader modifier,
|
|
@@ -9515,13 +9968,13 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
|
9515
9968
|
}
|
|
9516
9969
|
// Prepare network rule modifiers
|
|
9517
9970
|
const modifiers = createModifierListNode();
|
|
9518
|
-
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.
|
|
9971
|
+
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionPlainValue(rule.body.body)));
|
|
9519
9972
|
// Construct the network rule
|
|
9520
|
-
return [
|
|
9521
|
-
createNetworkRuleNode(pattern, modifiers,
|
|
9973
|
+
return createNodeConversionResult([
|
|
9974
|
+
createNetworkRuleNode(pattern.join(EMPTY), modifiers,
|
|
9522
9975
|
// Copy the exception flag
|
|
9523
|
-
|
|
9524
|
-
];
|
|
9976
|
+
rule.exception, exports.AdblockSyntax.Adg),
|
|
9977
|
+
], true);
|
|
9525
9978
|
}
|
|
9526
9979
|
}
|
|
9527
9980
|
|
|
@@ -9538,48 +9991,69 @@ class CosmeticRuleConverter extends RuleConverterBase {
|
|
|
9538
9991
|
* Converts a cosmetic rule to AdGuard syntax, if possible.
|
|
9539
9992
|
*
|
|
9540
9993
|
* @param rule Rule node to convert
|
|
9541
|
-
* @returns
|
|
9994
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9995
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9996
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9542
9997
|
* @throws If the rule is invalid or cannot be converted
|
|
9543
9998
|
*/
|
|
9544
9999
|
static convertToAdg(rule) {
|
|
9545
|
-
|
|
9546
|
-
const ruleNode = cloneDeep(rule);
|
|
9547
|
-
// Convert cosmetic rule modifiers
|
|
9548
|
-
if (ruleNode.modifiers) {
|
|
9549
|
-
if (ruleNode.syntax === exports.AdblockSyntax.Ubo) {
|
|
9550
|
-
// uBO doesn't support this rule:
|
|
9551
|
-
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
9552
|
-
if (ruleNode.type === exports.CosmeticRuleType.ScriptletInjectionRule) {
|
|
9553
|
-
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
9554
|
-
}
|
|
9555
|
-
ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
|
|
9556
|
-
}
|
|
9557
|
-
else if (ruleNode.syntax === exports.AdblockSyntax.Abp) {
|
|
9558
|
-
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
9559
|
-
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
9560
|
-
}
|
|
9561
|
-
}
|
|
10000
|
+
let subconverterResult;
|
|
9562
10001
|
// Convert cosmetic rule based on its type
|
|
9563
|
-
switch (
|
|
10002
|
+
switch (rule.type) {
|
|
9564
10003
|
case exports.CosmeticRuleType.ElementHidingRule:
|
|
9565
|
-
|
|
10004
|
+
subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
|
|
10005
|
+
break;
|
|
9566
10006
|
case exports.CosmeticRuleType.ScriptletInjectionRule:
|
|
9567
|
-
|
|
10007
|
+
subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
|
|
10008
|
+
break;
|
|
9568
10009
|
case exports.CosmeticRuleType.CssInjectionRule:
|
|
9569
|
-
|
|
10010
|
+
subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
|
|
10011
|
+
break;
|
|
9570
10012
|
case exports.CosmeticRuleType.HtmlFilteringRule:
|
|
9571
10013
|
// Handle special case: uBO response header filtering rule
|
|
9572
|
-
if (
|
|
9573
|
-
&&
|
|
9574
|
-
|
|
10014
|
+
if (rule.body.body.type === exports.CssTreeNodeType.Function
|
|
10015
|
+
&& rule.body.body.name === UBO_RESPONSEHEADER_MARKER) {
|
|
10016
|
+
subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
|
|
9575
10017
|
}
|
|
9576
|
-
|
|
9577
|
-
|
|
10018
|
+
else {
|
|
10019
|
+
subconverterResult = HtmlRuleConverter.convertToAdg(rule);
|
|
10020
|
+
}
|
|
10021
|
+
break;
|
|
10022
|
+
// Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
|
|
9578
10023
|
case exports.CosmeticRuleType.JsInjectionRule:
|
|
9579
|
-
|
|
10024
|
+
subconverterResult = createNodeConversionResult([rule], false);
|
|
10025
|
+
break;
|
|
9580
10026
|
default:
|
|
9581
10027
|
throw new RuleConversionError('Unsupported cosmetic rule type');
|
|
9582
10028
|
}
|
|
10029
|
+
let convertedModifiers;
|
|
10030
|
+
// Convert cosmetic rule modifiers, if any
|
|
10031
|
+
if (rule.modifiers) {
|
|
10032
|
+
if (rule.syntax === exports.AdblockSyntax.Ubo) {
|
|
10033
|
+
// uBO doesn't support this rule:
|
|
10034
|
+
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
10035
|
+
if (rule.type === exports.CosmeticRuleType.ScriptletInjectionRule) {
|
|
10036
|
+
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
10037
|
+
}
|
|
10038
|
+
convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
|
|
10039
|
+
}
|
|
10040
|
+
else if (rule.syntax === exports.AdblockSyntax.Abp) {
|
|
10041
|
+
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
10042
|
+
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
10043
|
+
}
|
|
10044
|
+
}
|
|
10045
|
+
if ((subconverterResult.result.length > 1 || subconverterResult.isConverted)
|
|
10046
|
+
|| (convertedModifiers && convertedModifiers.isConverted)) {
|
|
10047
|
+
// Add modifier list to the subconverter result rules
|
|
10048
|
+
subconverterResult.result.forEach((subconverterRule) => {
|
|
10049
|
+
if (convertedModifiers && subconverterRule.category === exports.RuleCategory.Cosmetic) {
|
|
10050
|
+
// eslint-disable-next-line no-param-reassign
|
|
10051
|
+
subconverterRule.modifiers = convertedModifiers.result;
|
|
10052
|
+
}
|
|
10053
|
+
});
|
|
10054
|
+
return subconverterResult;
|
|
10055
|
+
}
|
|
10056
|
+
return createNodeConversionResult([rule], false);
|
|
9583
10057
|
}
|
|
9584
10058
|
}
|
|
9585
10059
|
|
|
@@ -9651,17 +10125,16 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9651
10125
|
* Converts a network rule modifier list to AdGuard format, if possible.
|
|
9652
10126
|
*
|
|
9653
10127
|
* @param modifierList Network rule modifier list node to convert
|
|
9654
|
-
* @returns
|
|
10128
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10129
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
10130
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9655
10131
|
* @throws If the conversion is not possible
|
|
9656
10132
|
*/
|
|
9657
10133
|
static convertToAdg(modifierList) {
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
const cspValues = [];
|
|
9663
|
-
modifierListNode.children.forEach((modifierNode) => {
|
|
9664
|
-
// Handle regular modifiers conversion and $csp modifiers collection
|
|
10134
|
+
const conversionMap = new MultiValueMap();
|
|
10135
|
+
// Special case: $csp modifier
|
|
10136
|
+
let cspCount = 0;
|
|
10137
|
+
modifierList.children.forEach((modifierNode, index) => {
|
|
9665
10138
|
const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
|
|
9666
10139
|
if (modifierConversions) {
|
|
9667
10140
|
for (const modifierConversion of modifierConversions) {
|
|
@@ -9674,17 +10147,14 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9674
10147
|
const value = modifierConversion.value
|
|
9675
10148
|
? modifierConversion.value(modifierNode.value?.value)
|
|
9676
10149
|
: modifierNode.value?.value;
|
|
9677
|
-
if
|
|
9678
|
-
|
|
9679
|
-
|
|
10150
|
+
// Check if the name or the value is different from the original modifier
|
|
10151
|
+
// If so, add the converted modifier to the list
|
|
10152
|
+
if (name !== modifierNode.modifier.value || value !== modifierNode.value?.value) {
|
|
10153
|
+
conversionMap.add(index, createModifierNode(name, value, exception));
|
|
9680
10154
|
}
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
|
|
9685
|
-
if (!existingModifier) {
|
|
9686
|
-
convertedModifierList.children.push(createModifierNode(name, value, exception));
|
|
9687
|
-
}
|
|
10155
|
+
// Special case: $csp modifier
|
|
10156
|
+
if (name === CSP_MODIFIER) {
|
|
10157
|
+
cspCount += 1;
|
|
9688
10158
|
}
|
|
9689
10159
|
}
|
|
9690
10160
|
return;
|
|
@@ -9707,26 +10177,52 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9707
10177
|
// Try to convert the redirect resource name to ADG format
|
|
9708
10178
|
// This function returns undefined if the resource name is unknown
|
|
9709
10179
|
const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
|
|
9710
|
-
|
|
9711
|
-
// If
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
&& m.exception === modifierNode.exception
|
|
9720
|
-
&& m.value?.value === modifierNode.value?.value);
|
|
9721
|
-
if (!existingModifier) {
|
|
9722
|
-
convertedModifierList.children.push(modifierNode);
|
|
10180
|
+
// Check if the modifier name or the redirect resource name is different from the original modifier
|
|
10181
|
+
// If so, add the converted modifier to the list
|
|
10182
|
+
if (modifierName !== modifierNode.modifier.value
|
|
10183
|
+
|| (convertedRedirectResource !== undefined && convertedRedirectResource !== redirectResource)) {
|
|
10184
|
+
conversionMap.add(index, createModifierNode(modifierName,
|
|
10185
|
+
// If the redirect resource name is unknown, fall back to the original one
|
|
10186
|
+
// Later, the validator will throw an error if the resource name is invalid
|
|
10187
|
+
convertedRedirectResource || redirectResource, modifierNode.exception));
|
|
10188
|
+
}
|
|
9723
10189
|
}
|
|
9724
10190
|
});
|
|
9725
|
-
//
|
|
9726
|
-
if (
|
|
9727
|
-
|
|
10191
|
+
// Prepare the result if there are any converted modifiers or $csp modifiers
|
|
10192
|
+
if (conversionMap.size || cspCount) {
|
|
10193
|
+
const modifierListClone = cloneModifierListNode(modifierList);
|
|
10194
|
+
// Replace the original modifiers with the converted ones
|
|
10195
|
+
// One modifier may be replaced with multiple modifiers, so we need to flatten the array
|
|
10196
|
+
modifierListClone.children = modifierListClone.children.map((modifierNode, index) => {
|
|
10197
|
+
const conversionRecord = conversionMap.get(index);
|
|
10198
|
+
if (conversionRecord) {
|
|
10199
|
+
return conversionRecord;
|
|
10200
|
+
}
|
|
10201
|
+
return modifierNode;
|
|
10202
|
+
}).flat();
|
|
10203
|
+
// Special case: $csp modifier: merge multiple $csp modifiers into one
|
|
10204
|
+
// and put it at the end of the modifier list
|
|
10205
|
+
if (cspCount) {
|
|
10206
|
+
const cspValues = [];
|
|
10207
|
+
modifierListClone.children = modifierListClone.children.filter((modifierNode) => {
|
|
10208
|
+
if (modifierNode.modifier.value === CSP_MODIFIER) {
|
|
10209
|
+
if (!modifierNode.value?.value) {
|
|
10210
|
+
throw new RuleConversionError('$csp modifier value is missing');
|
|
10211
|
+
}
|
|
10212
|
+
cspValues.push(modifierNode.value?.value);
|
|
10213
|
+
return false;
|
|
10214
|
+
}
|
|
10215
|
+
return true;
|
|
10216
|
+
});
|
|
10217
|
+
modifierListClone.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
|
|
10218
|
+
}
|
|
10219
|
+
// Before returning the result, remove duplicated modifiers
|
|
10220
|
+
modifierListClone.children = modifierListClone.children.filter((modifierNode, index, self) => self.findIndex((m) => m.modifier.value === modifierNode.modifier.value
|
|
10221
|
+
&& m.exception === modifierNode.exception
|
|
10222
|
+
&& m.value?.value === modifierNode.value?.value) === index);
|
|
10223
|
+
return createConversionResult(modifierListClone, true);
|
|
9728
10224
|
}
|
|
9729
|
-
return
|
|
10225
|
+
return createConversionResult(modifierList, false);
|
|
9730
10226
|
}
|
|
9731
10227
|
}
|
|
9732
10228
|
|
|
@@ -9743,17 +10239,35 @@ class NetworkRuleConverter extends RuleConverterBase {
|
|
|
9743
10239
|
* Converts a network rule to AdGuard format, if possible.
|
|
9744
10240
|
*
|
|
9745
10241
|
* @param rule Rule node to convert
|
|
9746
|
-
* @returns
|
|
10242
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
10243
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10244
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9747
10245
|
* @throws If the rule is invalid or cannot be converted
|
|
9748
10246
|
*/
|
|
9749
10247
|
static convertToAdg(rule) {
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
10248
|
+
if (rule.modifiers) {
|
|
10249
|
+
const modifiers = NetworkRuleModifierListConverter.convertToAdg(rule.modifiers);
|
|
10250
|
+
// If the object reference is different, it means that the modifiers were converted
|
|
10251
|
+
// In this case, we should clone the entire rule and replace the modifiers with the converted ones
|
|
10252
|
+
if (modifiers.isConverted) {
|
|
10253
|
+
return {
|
|
10254
|
+
result: [{
|
|
10255
|
+
category: exports.RuleCategory.Network,
|
|
10256
|
+
type: 'NetworkRule',
|
|
10257
|
+
syntax: rule.syntax,
|
|
10258
|
+
exception: rule.exception,
|
|
10259
|
+
pattern: {
|
|
10260
|
+
type: 'Value',
|
|
10261
|
+
value: rule.pattern.value,
|
|
10262
|
+
},
|
|
10263
|
+
modifiers: modifiers.result,
|
|
10264
|
+
}],
|
|
10265
|
+
isConverted: true,
|
|
10266
|
+
};
|
|
10267
|
+
}
|
|
9755
10268
|
}
|
|
9756
|
-
return
|
|
10269
|
+
// If the modifiers were not converted, return the original rule
|
|
10270
|
+
return createNodeConversionResult([rule], false);
|
|
9757
10271
|
}
|
|
9758
10272
|
}
|
|
9759
10273
|
|
|
@@ -9774,48 +10288,27 @@ class RuleConverter extends RuleConverterBase {
|
|
|
9774
10288
|
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
9775
10289
|
*
|
|
9776
10290
|
* @param rule Rule node to convert
|
|
9777
|
-
* @returns
|
|
10291
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
10292
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10293
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9778
10294
|
* @throws If the rule is invalid or cannot be converted
|
|
9779
10295
|
*/
|
|
9780
10296
|
static convertToAdg(rule) {
|
|
9781
|
-
// Clone the provided AST node to avoid side effects
|
|
9782
|
-
const ruleNode = cloneDeep(rule);
|
|
9783
10297
|
// Delegate conversion to the corresponding sub-converter
|
|
9784
10298
|
// based on the rule category
|
|
9785
|
-
switch (
|
|
10299
|
+
switch (rule.category) {
|
|
9786
10300
|
case exports.RuleCategory.Comment:
|
|
9787
|
-
return CommentRuleConverter.convertToAdg(
|
|
10301
|
+
return CommentRuleConverter.convertToAdg(rule);
|
|
9788
10302
|
case exports.RuleCategory.Cosmetic:
|
|
9789
|
-
return CosmeticRuleConverter.convertToAdg(
|
|
10303
|
+
return CosmeticRuleConverter.convertToAdg(rule);
|
|
9790
10304
|
case exports.RuleCategory.Network:
|
|
9791
|
-
return NetworkRuleConverter.convertToAdg(
|
|
10305
|
+
return NetworkRuleConverter.convertToAdg(rule);
|
|
9792
10306
|
default:
|
|
9793
|
-
throw new RuleConversionError(`Unknown rule category: ${
|
|
10307
|
+
throw new RuleConversionError(`Unknown rule category: ${rule.category}`);
|
|
9794
10308
|
}
|
|
9795
10309
|
}
|
|
9796
10310
|
}
|
|
9797
10311
|
|
|
9798
|
-
/**
|
|
9799
|
-
* @file Utility functions for working with filter list nodes
|
|
9800
|
-
*/
|
|
9801
|
-
/**
|
|
9802
|
-
* Creates a filter list node
|
|
9803
|
-
*
|
|
9804
|
-
* @param rules Rules to put in the list (optional, defaults to an empty list)
|
|
9805
|
-
* @returns Filter list node
|
|
9806
|
-
*/
|
|
9807
|
-
function createFilterListNode(rules = []) {
|
|
9808
|
-
const result = {
|
|
9809
|
-
type: 'FilterList',
|
|
9810
|
-
children: [],
|
|
9811
|
-
};
|
|
9812
|
-
// We need to clone the rules to avoid side effects
|
|
9813
|
-
if (rules.length > 0) {
|
|
9814
|
-
result.children = cloneDeep(rules);
|
|
9815
|
-
}
|
|
9816
|
-
return result;
|
|
9817
|
-
}
|
|
9818
|
-
|
|
9819
10312
|
/**
|
|
9820
10313
|
* @file Adblock filter list converter
|
|
9821
10314
|
*/
|
|
@@ -9834,18 +10327,133 @@ class FilterListConverter extends ConverterBase {
|
|
|
9834
10327
|
* Converts an adblock filter list to AdGuard format, if possible.
|
|
9835
10328
|
*
|
|
9836
10329
|
* @param filterListNode Filter list node to convert
|
|
9837
|
-
* @
|
|
9838
|
-
*
|
|
10330
|
+
* @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
|
|
10331
|
+
* invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
|
|
10332
|
+
* Defaults to `true`.
|
|
10333
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10334
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
10335
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
10336
|
+
* @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
|
|
10337
|
+
*/
|
|
10338
|
+
static convertToAdg(filterListNode, tolerant = true) {
|
|
10339
|
+
// Prepare a map to store the converted rules by their index in the filter list
|
|
10340
|
+
const conversionMap = new MultiValueMap();
|
|
10341
|
+
// Iterate over the filtering rules and convert them one by one, then add them to the result (one conversion may
|
|
10342
|
+
// result in multiple rules)
|
|
10343
|
+
for (let i = 0; i < filterListNode.children.length; i += 1) {
|
|
10344
|
+
try {
|
|
10345
|
+
const convertedRules = RuleConverter.convertToAdg(filterListNode.children[i]);
|
|
10346
|
+
// Add the converted rules to the map if they were converted
|
|
10347
|
+
if (convertedRules.isConverted) {
|
|
10348
|
+
conversionMap.add(i, ...convertedRules.result);
|
|
10349
|
+
}
|
|
10350
|
+
}
|
|
10351
|
+
catch (error) {
|
|
10352
|
+
// If the tolerant mode is disabled, we should throw an error, this will fail the whole filter list
|
|
10353
|
+
// conversion.
|
|
10354
|
+
// Otherwise, we just ignore the error and leave the rule as is
|
|
10355
|
+
if (!tolerant) {
|
|
10356
|
+
throw error;
|
|
10357
|
+
}
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
// If the conversion map is empty, it means that no rules were converted, so we can return the original filter
|
|
10361
|
+
// list
|
|
10362
|
+
if (conversionMap.size === 0) {
|
|
10363
|
+
return createConversionResult(filterListNode, false);
|
|
10364
|
+
}
|
|
10365
|
+
// Otherwise, create a new filter list node with the converted rules
|
|
10366
|
+
const convertedFilterList = {
|
|
10367
|
+
type: 'FilterList',
|
|
10368
|
+
children: [],
|
|
10369
|
+
};
|
|
10370
|
+
// Iterate over the original rules again and add them to the converted filter list, replacing the converted
|
|
10371
|
+
// rules with the new ones at the specified indexes
|
|
10372
|
+
for (let i = 0; i < filterListNode.children.length; i += 1) {
|
|
10373
|
+
const rules = conversionMap.get(i);
|
|
10374
|
+
if (rules) {
|
|
10375
|
+
convertedFilterList.children.push(...rules);
|
|
10376
|
+
}
|
|
10377
|
+
else {
|
|
10378
|
+
// We clone the unconverted rules to avoid mutating the original filter list if we return the converted
|
|
10379
|
+
// one
|
|
10380
|
+
convertedFilterList.children.push(clone(filterListNode.children[i]));
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
return createConversionResult(convertedFilterList, true);
|
|
10384
|
+
}
|
|
10385
|
+
}
|
|
10386
|
+
|
|
10387
|
+
/**
|
|
10388
|
+
* @file Filter list converter for raw filter lists
|
|
10389
|
+
*
|
|
10390
|
+
* Technically, this is a wrapper around `FilterListConverter` that works with nodes instead of strings.
|
|
10391
|
+
*/
|
|
10392
|
+
/**
|
|
10393
|
+
* Adblock filter list converter class.
|
|
10394
|
+
*
|
|
10395
|
+
* You can use this class to convert string-based filter lists, since most of the converters work with nodes.
|
|
10396
|
+
* This class just provides an extra layer on top of the {@link FilterListConverter} and calls the parser/serializer
|
|
10397
|
+
* before/after the conversion internally.
|
|
10398
|
+
*
|
|
10399
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
10400
|
+
*/
|
|
10401
|
+
class RawFilterListConverter extends ConverterBase {
|
|
10402
|
+
/**
|
|
10403
|
+
* Converts an adblock filter list text to AdGuard format, if possible.
|
|
10404
|
+
*
|
|
10405
|
+
* @param rawFilterList Raw filter list text to convert
|
|
10406
|
+
* @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
|
|
10407
|
+
* invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
|
|
10408
|
+
* Defaults to `true`.
|
|
10409
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10410
|
+
* the array of converted filter list text, and its `isConverted` flag indicates whether the original rule was
|
|
10411
|
+
* converted. If the rule was not converted, the original filter list text will be returned
|
|
10412
|
+
* @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
|
|
9839
10413
|
*/
|
|
9840
|
-
static convertToAdg(
|
|
9841
|
-
const
|
|
9842
|
-
//
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
const convertedRules = RuleConverter.convertToAdg(ruleNode);
|
|
9846
|
-
result.children.push(...convertedRules);
|
|
10414
|
+
static convertToAdg(rawFilterList, tolerant = true) {
|
|
10415
|
+
const conversionResult = FilterListConverter.convertToAdg(FilterListParser.parse(rawFilterList, tolerant), tolerant);
|
|
10416
|
+
// If the filter list was not converted, return the original text
|
|
10417
|
+
if (!conversionResult.isConverted) {
|
|
10418
|
+
return createConversionResult(rawFilterList, false);
|
|
9847
10419
|
}
|
|
9848
|
-
return result
|
|
10420
|
+
// Otherwise, serialize the filter list and return the result
|
|
10421
|
+
return createConversionResult(FilterListParser.generate(conversionResult.result), true);
|
|
10422
|
+
}
|
|
10423
|
+
}
|
|
10424
|
+
|
|
10425
|
+
/**
|
|
10426
|
+
* @file Rule converter for raw rules
|
|
10427
|
+
*
|
|
10428
|
+
* Technically, this is a wrapper around `RuleConverter` that works with nodes instead of strings.
|
|
10429
|
+
*/
|
|
10430
|
+
/**
|
|
10431
|
+
* Adblock filtering rule converter class.
|
|
10432
|
+
*
|
|
10433
|
+
* You can use this class to convert string-based adblock rules, since most of the converters work with nodes.
|
|
10434
|
+
* This class just provides an extra layer on top of the {@link RuleConverter} and calls the parser/serializer
|
|
10435
|
+
* before/after the conversion internally.
|
|
10436
|
+
*
|
|
10437
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
10438
|
+
*/
|
|
10439
|
+
class RawRuleConverter extends ConverterBase {
|
|
10440
|
+
/**
|
|
10441
|
+
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
10442
|
+
*
|
|
10443
|
+
* @param rawRule Raw rule text to convert
|
|
10444
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10445
|
+
* the array of converted rule texts, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10446
|
+
* If the rule was not converted, the original rule text will be returned
|
|
10447
|
+
* @throws If the rule is invalid or cannot be converted
|
|
10448
|
+
*/
|
|
10449
|
+
static convertToAdg(rawRule) {
|
|
10450
|
+
const conversionResult = RuleConverter.convertToAdg(RuleParser.parse(rawRule));
|
|
10451
|
+
// If the rule was not converted, return the original rule text
|
|
10452
|
+
if (!conversionResult.isConverted) {
|
|
10453
|
+
return createConversionResult([rawRule], false);
|
|
10454
|
+
}
|
|
10455
|
+
// Otherwise, serialize the converted rule nodes
|
|
10456
|
+
return createConversionResult(conversionResult.result.map(RuleParser.generate), true);
|
|
9849
10457
|
}
|
|
9850
10458
|
}
|
|
9851
10459
|
|
|
@@ -9927,7 +10535,7 @@ class LogicalExpressionUtils {
|
|
|
9927
10535
|
}
|
|
9928
10536
|
}
|
|
9929
10537
|
|
|
9930
|
-
const version$1 = "1.1.
|
|
10538
|
+
const version$1 = "1.1.6";
|
|
9931
10539
|
|
|
9932
10540
|
/**
|
|
9933
10541
|
* @file AGTree version
|
|
@@ -9988,6 +10596,8 @@ exports.PREPROCESSOR_MARKER = PREPROCESSOR_MARKER;
|
|
|
9988
10596
|
exports.ParameterListParser = ParameterListParser;
|
|
9989
10597
|
exports.PreProcessorCommentRuleParser = PreProcessorCommentRuleParser;
|
|
9990
10598
|
exports.QuoteUtils = QuoteUtils;
|
|
10599
|
+
exports.RawFilterListConverter = RawFilterListConverter;
|
|
10600
|
+
exports.RawRuleConverter = RawRuleConverter;
|
|
9991
10601
|
exports.RegExpUtils = RegExpUtils;
|
|
9992
10602
|
exports.RuleConversionError = RuleConversionError;
|
|
9993
10603
|
exports.RuleConverter = RuleConverter;
|