@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.esm.js
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
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import valid from 'semver/functions/valid.js';
|
|
8
8
|
import coerce from 'semver/functions/coerce.js';
|
|
9
9
|
import JSON5 from 'json5';
|
|
10
|
-
import { walk, parse, toPlainObject, find, generate,
|
|
10
|
+
import { walk, parse, toPlainObject, find, generate, List, fromPlainObject } from '@adguard/ecss-tree';
|
|
11
11
|
import * as ecssTree from '@adguard/ecss-tree';
|
|
12
12
|
export { ecssTree as ECSSTree };
|
|
13
13
|
import cloneDeep from 'clone-deep';
|
|
@@ -262,7 +262,7 @@ const NEGATION_MARKER = '~';
|
|
|
262
262
|
/**
|
|
263
263
|
* The wildcard symbol — `*`.
|
|
264
264
|
*/
|
|
265
|
-
const WILDCARD
|
|
265
|
+
const WILDCARD = ASTERISK;
|
|
266
266
|
/**
|
|
267
267
|
* Classic domain separator.
|
|
268
268
|
*
|
|
@@ -2861,7 +2861,7 @@ class ModifierParser {
|
|
|
2861
2861
|
const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
|
|
2862
2862
|
// Modifier name can't be empty
|
|
2863
2863
|
if (modifierNameStart === modifierEnd) {
|
|
2864
|
-
throw new AdblockSyntaxError('Modifier name
|
|
2864
|
+
throw new AdblockSyntaxError('Modifier name cannot be empty', locRange(loc, 0, raw.length));
|
|
2865
2865
|
}
|
|
2866
2866
|
let modifier;
|
|
2867
2867
|
let value;
|
|
@@ -2885,7 +2885,7 @@ class ModifierParser {
|
|
|
2885
2885
|
};
|
|
2886
2886
|
// Value can't be empty
|
|
2887
2887
|
if (assignmentIndex + 1 === modifierEnd) {
|
|
2888
|
-
throw new AdblockSyntaxError('Modifier value
|
|
2888
|
+
throw new AdblockSyntaxError('Modifier value cannot be empty', locRange(loc, 0, raw.length));
|
|
2889
2889
|
}
|
|
2890
2890
|
// Skip whitespace after the assignment operator
|
|
2891
2891
|
const valueStart = StringUtils.skipWS(raw, assignmentIndex + MODIFIER_ASSIGN_OPERATOR.length);
|
|
@@ -3183,8 +3183,29 @@ const FORBIDDEN_CSS_FUNCTIONS = new Set([
|
|
|
3183
3183
|
'url',
|
|
3184
3184
|
]);
|
|
3185
3185
|
|
|
3186
|
+
/**
|
|
3187
|
+
* @file Clone related utilities
|
|
3188
|
+
*
|
|
3189
|
+
* We should keep clone related functions in this file. Thus, we just provide
|
|
3190
|
+
* a simple interface for cloning values, we use it across the AGTree project,
|
|
3191
|
+
* and the implementation "under the hood" can be improved later, if needed.
|
|
3192
|
+
*/
|
|
3193
|
+
/**
|
|
3194
|
+
* Clones an input value to avoid side effects. Use it only in justified cases,
|
|
3195
|
+
* because it can impact performance negatively.
|
|
3196
|
+
*
|
|
3197
|
+
* @param value Value to clone
|
|
3198
|
+
* @returns Cloned value
|
|
3199
|
+
*/
|
|
3200
|
+
function clone(value) {
|
|
3201
|
+
// TODO: Replace cloneDeep with a more efficient implementation
|
|
3202
|
+
return cloneDeep(value);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3186
3205
|
/**
|
|
3187
3206
|
* @file Additional / helper functions for ECSSTree / CSSTree.
|
|
3207
|
+
*
|
|
3208
|
+
* @note There are no tests for some functions, but during the AGTree optimization we remove them anyway.
|
|
3188
3209
|
*/
|
|
3189
3210
|
/**
|
|
3190
3211
|
* Common CSSTree parsing options.
|
|
@@ -3320,10 +3341,10 @@ class CssTree {
|
|
|
3320
3341
|
ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
|
|
3321
3342
|
}
|
|
3322
3343
|
else {
|
|
3323
|
-
ast =
|
|
3344
|
+
ast = clone(selectorList);
|
|
3324
3345
|
}
|
|
3325
3346
|
const nodes = [];
|
|
3326
|
-
// TODO:
|
|
3347
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3327
3348
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3328
3349
|
walk(ast, (node) => {
|
|
3329
3350
|
if (CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) {
|
|
@@ -3352,9 +3373,9 @@ class CssTree {
|
|
|
3352
3373
|
ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
|
|
3353
3374
|
}
|
|
3354
3375
|
else {
|
|
3355
|
-
ast =
|
|
3376
|
+
ast = selectorList;
|
|
3356
3377
|
}
|
|
3357
|
-
// TODO:
|
|
3378
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3358
3379
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3359
3380
|
return find(ast, (node) => CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) !== null;
|
|
3360
3381
|
}
|
|
@@ -3391,14 +3412,14 @@ class CssTree {
|
|
|
3391
3412
|
ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
|
|
3392
3413
|
}
|
|
3393
3414
|
else {
|
|
3394
|
-
ast =
|
|
3415
|
+
ast = clone(declarationList);
|
|
3395
3416
|
}
|
|
3396
3417
|
const nodes = [];
|
|
3397
3418
|
// While walking the AST we should skip the nested functions,
|
|
3398
3419
|
// for example skip url()s in cross-fade(url(), url()), since
|
|
3399
3420
|
// cross-fade() itself is already a forbidden function
|
|
3400
3421
|
let inForbiddenFunction = false;
|
|
3401
|
-
// TODO:
|
|
3422
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3402
3423
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3403
3424
|
walk(ast, {
|
|
3404
3425
|
enter: (node) => {
|
|
@@ -3436,9 +3457,9 @@ class CssTree {
|
|
|
3436
3457
|
ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
|
|
3437
3458
|
}
|
|
3438
3459
|
else {
|
|
3439
|
-
ast =
|
|
3460
|
+
ast = clone(declarationList);
|
|
3440
3461
|
}
|
|
3441
|
-
// TODO:
|
|
3462
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3442
3463
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3443
3464
|
return find(ast, (node) => CssTree.isForbiddenFunction(node, forbiddenFunctions)) !== null;
|
|
3444
3465
|
}
|
|
@@ -3670,6 +3691,180 @@ class CssTree {
|
|
|
3670
3691
|
});
|
|
3671
3692
|
return result.trim();
|
|
3672
3693
|
}
|
|
3694
|
+
/**
|
|
3695
|
+
* Generates string representation of the selector list.
|
|
3696
|
+
*
|
|
3697
|
+
* @param ast SelectorList AST
|
|
3698
|
+
* @returns String representation of the selector list
|
|
3699
|
+
*/
|
|
3700
|
+
static generateSelectorListPlain(ast) {
|
|
3701
|
+
const result = [];
|
|
3702
|
+
if (!ast.children || ast.children.length === 0) {
|
|
3703
|
+
throw new Error('Selector list cannot be empty');
|
|
3704
|
+
}
|
|
3705
|
+
ast.children.forEach((selector, index, nodeList) => {
|
|
3706
|
+
if (selector.type !== CssTreeNodeType.Selector) {
|
|
3707
|
+
throw new Error(`Unexpected node type: ${selector.type}`);
|
|
3708
|
+
}
|
|
3709
|
+
result.push(this.generateSelectorPlain(selector));
|
|
3710
|
+
// If there is a next node, add a comma and a space after the selector
|
|
3711
|
+
if (nodeList[index + 1]) {
|
|
3712
|
+
result.push(COMMA, SPACE);
|
|
3713
|
+
}
|
|
3714
|
+
});
|
|
3715
|
+
return result.join(EMPTY);
|
|
3716
|
+
}
|
|
3717
|
+
/**
|
|
3718
|
+
* Selector generation based on CSSTree's AST. This is necessary because CSSTree
|
|
3719
|
+
* only adds spaces in some edge cases.
|
|
3720
|
+
*
|
|
3721
|
+
* @param ast CSS Tree AST
|
|
3722
|
+
* @returns CSS selector as string
|
|
3723
|
+
*/
|
|
3724
|
+
static generateSelectorPlain(ast) {
|
|
3725
|
+
let result = EMPTY;
|
|
3726
|
+
let inAttributeSelector = false;
|
|
3727
|
+
let depth = 0;
|
|
3728
|
+
let selectorListDepth = -1;
|
|
3729
|
+
let prevNode = ast;
|
|
3730
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3731
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3732
|
+
walk(ast, {
|
|
3733
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
3734
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3735
|
+
enter: (node) => {
|
|
3736
|
+
depth += 1;
|
|
3737
|
+
// Skip attribute selector / selector list children
|
|
3738
|
+
if (inAttributeSelector || selectorListDepth > -1) {
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
switch (node.type) {
|
|
3742
|
+
// "Trivial" nodes
|
|
3743
|
+
case CssTreeNodeType.TypeSelector:
|
|
3744
|
+
result += node.name;
|
|
3745
|
+
break;
|
|
3746
|
+
case CssTreeNodeType.ClassSelector:
|
|
3747
|
+
result += DOT;
|
|
3748
|
+
result += node.name;
|
|
3749
|
+
break;
|
|
3750
|
+
case CssTreeNodeType.IdSelector:
|
|
3751
|
+
result += HASHMARK;
|
|
3752
|
+
result += node.name;
|
|
3753
|
+
break;
|
|
3754
|
+
case CssTreeNodeType.Identifier:
|
|
3755
|
+
result += node.name;
|
|
3756
|
+
break;
|
|
3757
|
+
case CssTreeNodeType.Raw:
|
|
3758
|
+
result += node.value;
|
|
3759
|
+
break;
|
|
3760
|
+
// "Advanced" nodes
|
|
3761
|
+
case CssTreeNodeType.Nth:
|
|
3762
|
+
// Default generation enough
|
|
3763
|
+
result += generate(node);
|
|
3764
|
+
break;
|
|
3765
|
+
// For example :not([id], [name])
|
|
3766
|
+
case CssTreeNodeType.SelectorList:
|
|
3767
|
+
// eslint-disable-next-line no-case-declarations
|
|
3768
|
+
const selectors = [];
|
|
3769
|
+
node.children.forEach((selector) => {
|
|
3770
|
+
if (selector.type === CssTreeNodeType.Selector) {
|
|
3771
|
+
selectors.push(CssTree.generateSelectorPlain(selector));
|
|
3772
|
+
}
|
|
3773
|
+
else if (selector.type === CssTreeNodeType.Raw) {
|
|
3774
|
+
selectors.push(selector.value);
|
|
3775
|
+
}
|
|
3776
|
+
});
|
|
3777
|
+
// Join selector lists
|
|
3778
|
+
result += selectors.join(COMMA + SPACE);
|
|
3779
|
+
// Skip nodes here
|
|
3780
|
+
selectorListDepth = depth;
|
|
3781
|
+
break;
|
|
3782
|
+
case CssTreeNodeType.Combinator:
|
|
3783
|
+
if (node.name === SPACE) {
|
|
3784
|
+
result += node.name;
|
|
3785
|
+
break;
|
|
3786
|
+
}
|
|
3787
|
+
// Prevent this case (unnecessary space): has( > .something)
|
|
3788
|
+
if (prevNode.type !== CssTreeNodeType.Selector) {
|
|
3789
|
+
result += SPACE;
|
|
3790
|
+
}
|
|
3791
|
+
result += node.name;
|
|
3792
|
+
result += SPACE;
|
|
3793
|
+
break;
|
|
3794
|
+
case CssTreeNodeType.AttributeSelector:
|
|
3795
|
+
result += OPEN_SQUARE_BRACKET;
|
|
3796
|
+
// Identifier name
|
|
3797
|
+
if (node.name) {
|
|
3798
|
+
result += node.name.name;
|
|
3799
|
+
}
|
|
3800
|
+
// Matcher operator, eg =
|
|
3801
|
+
if (node.matcher) {
|
|
3802
|
+
result += node.matcher;
|
|
3803
|
+
// Value can be String, Identifier or null
|
|
3804
|
+
if (node.value !== null) {
|
|
3805
|
+
// String node
|
|
3806
|
+
if (node.value.type === CssTreeNodeType.String) {
|
|
3807
|
+
result += generate(node.value);
|
|
3808
|
+
}
|
|
3809
|
+
else if (node.value.type === CssTreeNodeType.Identifier) {
|
|
3810
|
+
// Identifier node
|
|
3811
|
+
result += node.value.name;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
// Flags
|
|
3816
|
+
if (node.flags) {
|
|
3817
|
+
// Space before flags
|
|
3818
|
+
result += SPACE;
|
|
3819
|
+
result += node.flags;
|
|
3820
|
+
}
|
|
3821
|
+
result += CLOSE_SQUARE_BRACKET;
|
|
3822
|
+
inAttributeSelector = true;
|
|
3823
|
+
break;
|
|
3824
|
+
case CssTreeNodeType.PseudoElementSelector:
|
|
3825
|
+
result += COLON;
|
|
3826
|
+
result += COLON;
|
|
3827
|
+
result += node.name;
|
|
3828
|
+
if (node.children !== null) {
|
|
3829
|
+
result += OPEN_PARENTHESIS;
|
|
3830
|
+
}
|
|
3831
|
+
break;
|
|
3832
|
+
case CssTreeNodeType.PseudoClassSelector:
|
|
3833
|
+
result += COLON;
|
|
3834
|
+
result += node.name;
|
|
3835
|
+
if (node.children !== null) {
|
|
3836
|
+
result += OPEN_PARENTHESIS;
|
|
3837
|
+
}
|
|
3838
|
+
break;
|
|
3839
|
+
}
|
|
3840
|
+
prevNode = node;
|
|
3841
|
+
},
|
|
3842
|
+
leave: (node) => {
|
|
3843
|
+
depth -= 1;
|
|
3844
|
+
if (node.type === CssTreeNodeType.SelectorList && depth + 1 === selectorListDepth) {
|
|
3845
|
+
selectorListDepth = -1;
|
|
3846
|
+
}
|
|
3847
|
+
if (selectorListDepth > -1) {
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
if (node.type === CssTreeNodeType.AttributeSelector) {
|
|
3851
|
+
inAttributeSelector = false;
|
|
3852
|
+
}
|
|
3853
|
+
if (inAttributeSelector) {
|
|
3854
|
+
return;
|
|
3855
|
+
}
|
|
3856
|
+
switch (node.type) {
|
|
3857
|
+
case CssTreeNodeType.PseudoElementSelector:
|
|
3858
|
+
case CssTreeNodeType.PseudoClassSelector:
|
|
3859
|
+
if (node.children) {
|
|
3860
|
+
result += CLOSE_PARENTHESIS;
|
|
3861
|
+
}
|
|
3862
|
+
break;
|
|
3863
|
+
}
|
|
3864
|
+
},
|
|
3865
|
+
});
|
|
3866
|
+
return result.trim();
|
|
3867
|
+
}
|
|
3673
3868
|
/**
|
|
3674
3869
|
* Block generation based on CSSTree's AST. This is necessary because CSSTree only adds spaces in some edge cases.
|
|
3675
3870
|
*
|
|
@@ -3853,6 +4048,29 @@ class CssTree {
|
|
|
3853
4048
|
});
|
|
3854
4049
|
return result;
|
|
3855
4050
|
}
|
|
4051
|
+
/**
|
|
4052
|
+
* Helper function to generate a raw string from a function selector's children
|
|
4053
|
+
*
|
|
4054
|
+
* @param node Function node
|
|
4055
|
+
* @returns Generated function value
|
|
4056
|
+
* @example `responseheader(name)` -> `name`
|
|
4057
|
+
*/
|
|
4058
|
+
static generateFunctionPlainValue(node) {
|
|
4059
|
+
const result = [];
|
|
4060
|
+
node.children?.forEach((child) => {
|
|
4061
|
+
switch (child.type) {
|
|
4062
|
+
case CssTreeNodeType.Raw:
|
|
4063
|
+
result.push(child.value);
|
|
4064
|
+
break;
|
|
4065
|
+
default:
|
|
4066
|
+
// Fallback to CSSTree's default generate function
|
|
4067
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4068
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4069
|
+
result.push(generate(child));
|
|
4070
|
+
}
|
|
4071
|
+
});
|
|
4072
|
+
return result.join(EMPTY);
|
|
4073
|
+
}
|
|
3856
4074
|
}
|
|
3857
4075
|
|
|
3858
4076
|
/**
|
|
@@ -3900,7 +4118,7 @@ class ElementHidingBodyParser {
|
|
|
3900
4118
|
* @throws If the AST is invalid
|
|
3901
4119
|
*/
|
|
3902
4120
|
static generate(ast) {
|
|
3903
|
-
return CssTree.
|
|
4121
|
+
return CssTree.generateSelectorListPlain(ast.selectorList);
|
|
3904
4122
|
}
|
|
3905
4123
|
}
|
|
3906
4124
|
|
|
@@ -4144,7 +4362,7 @@ class CssInjectionBodyParser {
|
|
|
4144
4362
|
if (mediaQueryList || declarationList || remove) {
|
|
4145
4363
|
throw new AdblockSyntaxError(
|
|
4146
4364
|
// eslint-disable-next-line max-len
|
|
4147
|
-
'Invalid selector, regular selector elements
|
|
4365
|
+
'Invalid selector, regular selector elements cannot be used after special pseudo-classes', {
|
|
4148
4366
|
start: node.loc?.start ?? loc,
|
|
4149
4367
|
end: shiftLoc(loc, raw.length),
|
|
4150
4368
|
});
|
|
@@ -4833,7 +5051,7 @@ function createModifierListNode(modifiers = []) {
|
|
|
4833
5051
|
const result = {
|
|
4834
5052
|
type: 'ModifierList',
|
|
4835
5053
|
// We need to clone the modifiers to avoid side effects
|
|
4836
|
-
children:
|
|
5054
|
+
children: modifiers.length ? clone(modifiers) : [],
|
|
4837
5055
|
};
|
|
4838
5056
|
return result;
|
|
4839
5057
|
}
|
|
@@ -4873,8 +5091,9 @@ function hasUboModifierIndicator(rawSelectorList) {
|
|
|
4873
5091
|
* @returns Linked list based selector
|
|
4874
5092
|
*/
|
|
4875
5093
|
function convertSelectorToLinkedList(selector) {
|
|
5094
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4876
5095
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4877
|
-
return fromPlainObject(
|
|
5096
|
+
return fromPlainObject(clone(selector));
|
|
4878
5097
|
}
|
|
4879
5098
|
/**
|
|
4880
5099
|
* Helper function that always returns the linked list version of the
|
|
@@ -4884,8 +5103,9 @@ function convertSelectorToLinkedList(selector) {
|
|
|
4884
5103
|
* @returns Linked list based selector list
|
|
4885
5104
|
*/
|
|
4886
5105
|
function convertSelectorListToLinkedList(selectorList) {
|
|
5106
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
4887
5107
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4888
|
-
return fromPlainObject(
|
|
5108
|
+
return fromPlainObject(clone(selectorList));
|
|
4889
5109
|
}
|
|
4890
5110
|
/**
|
|
4891
5111
|
* Helper function for checking and removing bounding combinators
|
|
@@ -5970,7 +6190,8 @@ class FilterListParser {
|
|
|
5970
6190
|
*/
|
|
5971
6191
|
static generate(ast, preferRaw = false) {
|
|
5972
6192
|
let result = EMPTY;
|
|
5973
|
-
for (
|
|
6193
|
+
for (let i = 0; i < ast.children.length; i += 1) {
|
|
6194
|
+
const rule = ast.children[i];
|
|
5974
6195
|
if (preferRaw && rule.raws?.text) {
|
|
5975
6196
|
result += rule.raws.text;
|
|
5976
6197
|
}
|
|
@@ -5987,6 +6208,11 @@ class FilterListParser {
|
|
|
5987
6208
|
case 'lf':
|
|
5988
6209
|
result += LF;
|
|
5989
6210
|
break;
|
|
6211
|
+
default:
|
|
6212
|
+
if (i !== ast.children.length - 1) {
|
|
6213
|
+
result += LF;
|
|
6214
|
+
}
|
|
6215
|
+
break;
|
|
5990
6216
|
}
|
|
5991
6217
|
}
|
|
5992
6218
|
return result;
|
|
@@ -7541,14 +7767,14 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
|
|
|
7541
7767
|
* @example
|
|
7542
7768
|
* `example.*` — matches with any TLD, e.g. `example.org`, `example.com`, etc.
|
|
7543
7769
|
*/
|
|
7544
|
-
const WILDCARD_TLD = DOT + WILDCARD
|
|
7770
|
+
const WILDCARD_TLD = DOT + WILDCARD;
|
|
7545
7771
|
/**
|
|
7546
7772
|
* Marker for a wildcard subdomain — `*.`.
|
|
7547
7773
|
*
|
|
7548
7774
|
* @example
|
|
7549
7775
|
* `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
|
|
7550
7776
|
*/
|
|
7551
|
-
const WILDCARD_SUBDOMAIN = WILDCARD
|
|
7777
|
+
const WILDCARD_SUBDOMAIN = WILDCARD + DOT;
|
|
7552
7778
|
class DomainUtils {
|
|
7553
7779
|
/**
|
|
7554
7780
|
* Check if the input is a valid domain or hostname.
|
|
@@ -7559,7 +7785,7 @@ class DomainUtils {
|
|
|
7559
7785
|
static isValidDomainOrHostname(domain) {
|
|
7560
7786
|
let domainToCheck = domain;
|
|
7561
7787
|
// Wildcard-only domain, typically a generic rule
|
|
7562
|
-
if (domainToCheck === WILDCARD
|
|
7788
|
+
if (domainToCheck === WILDCARD) {
|
|
7563
7789
|
return true;
|
|
7564
7790
|
}
|
|
7565
7791
|
// https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
|
|
@@ -7779,7 +8005,7 @@ const isValidAppNameChunk = (chunk) => {
|
|
|
7779
8005
|
const isValidAppModifierValue = (value) => {
|
|
7780
8006
|
// $app modifier does not support wildcard tld
|
|
7781
8007
|
// https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
|
|
7782
|
-
if (value.includes(WILDCARD
|
|
8008
|
+
if (value.includes(WILDCARD)) {
|
|
7783
8009
|
return false;
|
|
7784
8010
|
}
|
|
7785
8011
|
return value
|
|
@@ -7844,7 +8070,7 @@ const isValidDenyAllowModifierValue = (value) => {
|
|
|
7844
8070
|
// $denyallow modifier does not support wildcard tld
|
|
7845
8071
|
// https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
|
|
7846
8072
|
// but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
|
|
7847
|
-
if (value.includes(WILDCARD
|
|
8073
|
+
if (value.includes(WILDCARD)) {
|
|
7848
8074
|
return false;
|
|
7849
8075
|
}
|
|
7850
8076
|
// TODO: add cache for domains validation
|
|
@@ -8141,7 +8367,7 @@ const validatePermissionAllowlist = (allowlist, directive, modifierName) => {
|
|
|
8141
8367
|
// `*` is one of available permissions tokens
|
|
8142
8368
|
// e.g. 'fullscreen=*'
|
|
8143
8369
|
// https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization
|
|
8144
|
-
if (allowlist === WILDCARD
|
|
8370
|
+
if (allowlist === WILDCARD
|
|
8145
8371
|
// e.g. 'autoplay=()'
|
|
8146
8372
|
|| allowlist === EMPTY_PERMISSIONS_ALLOWLIST) {
|
|
8147
8373
|
return { valid: true };
|
|
@@ -8405,7 +8631,7 @@ class ModifierValidator {
|
|
|
8405
8631
|
* @returns Result of modifier validation.
|
|
8406
8632
|
*/
|
|
8407
8633
|
validate = (syntax, rawModifier, isException = false) => {
|
|
8408
|
-
const modifier =
|
|
8634
|
+
const modifier = clone(rawModifier);
|
|
8409
8635
|
// special case: handle noop modifier which may be used as multiple underscores (not just one)
|
|
8410
8636
|
// https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
|
|
8411
8637
|
if (modifier.modifier.value.startsWith(UNDERSCORE)) {
|
|
@@ -8484,7 +8710,9 @@ class ConverterBase {
|
|
|
8484
8710
|
* Converts some data to AdGuard format
|
|
8485
8711
|
*
|
|
8486
8712
|
* @param data Data to convert
|
|
8487
|
-
* @returns
|
|
8713
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8714
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8715
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8488
8716
|
* @throws If the data is invalid or incompatible
|
|
8489
8717
|
*/
|
|
8490
8718
|
static convertToAdg(data) {
|
|
@@ -8494,7 +8722,9 @@ class ConverterBase {
|
|
|
8494
8722
|
* Converts some data to Adblock Plus format
|
|
8495
8723
|
*
|
|
8496
8724
|
* @param data Data to convert
|
|
8497
|
-
* @returns
|
|
8725
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8726
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8727
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8498
8728
|
* @throws If the data is invalid or incompatible
|
|
8499
8729
|
*/
|
|
8500
8730
|
static convertToAbp(data) {
|
|
@@ -8504,7 +8734,9 @@ class ConverterBase {
|
|
|
8504
8734
|
* Converts some data to uBlock Origin format
|
|
8505
8735
|
*
|
|
8506
8736
|
* @param data Data to convert
|
|
8507
|
-
* @returns
|
|
8737
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
8738
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
8739
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
8508
8740
|
* @throws If the data is invalid or incompatible
|
|
8509
8741
|
*/
|
|
8510
8742
|
static convertToUbo(data) {
|
|
@@ -8528,7 +8760,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8528
8760
|
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
8529
8761
|
*
|
|
8530
8762
|
* @param rule Rule node to convert
|
|
8531
|
-
* @returns
|
|
8763
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8764
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8765
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8532
8766
|
* @throws If the rule is invalid or cannot be converted
|
|
8533
8767
|
*/
|
|
8534
8768
|
static convertToAdg(rule) {
|
|
@@ -8538,7 +8772,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8538
8772
|
* Converts an adblock filtering rule to Adblock Plus format, if possible.
|
|
8539
8773
|
*
|
|
8540
8774
|
* @param rule Rule node to convert
|
|
8541
|
-
* @returns
|
|
8775
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8776
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8777
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8542
8778
|
* @throws If the rule is invalid or cannot be converted
|
|
8543
8779
|
*/
|
|
8544
8780
|
static convertToAbp(rule) {
|
|
@@ -8548,7 +8784,9 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8548
8784
|
* Converts an adblock filtering rule to uBlock Origin format, if possible.
|
|
8549
8785
|
*
|
|
8550
8786
|
* @param rule Rule node to convert
|
|
8551
|
-
* @returns
|
|
8787
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8788
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8789
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8552
8790
|
* @throws If the rule is invalid or cannot be converted
|
|
8553
8791
|
*/
|
|
8554
8792
|
static convertToUbo(rule) {
|
|
@@ -8556,6 +8794,37 @@ class RuleConverterBase extends ConverterBase {
|
|
|
8556
8794
|
}
|
|
8557
8795
|
}
|
|
8558
8796
|
|
|
8797
|
+
/**
|
|
8798
|
+
* @file Conversion result interface and helper functions
|
|
8799
|
+
*/
|
|
8800
|
+
/**
|
|
8801
|
+
* Helper function to create a generic conversion result.
|
|
8802
|
+
*
|
|
8803
|
+
* @param result Conversion result
|
|
8804
|
+
* @param isConverted Indicates whether the input item was converted
|
|
8805
|
+
* @template T Type of the item to convert
|
|
8806
|
+
* @template U Type of the conversion result (defaults to `T`, but can be `T[]` as well)
|
|
8807
|
+
* @returns Generic conversion result
|
|
8808
|
+
*/
|
|
8809
|
+
// eslint-disable-next-line max-len
|
|
8810
|
+
function createConversionResult(result, isConverted) {
|
|
8811
|
+
return {
|
|
8812
|
+
result,
|
|
8813
|
+
isConverted,
|
|
8814
|
+
};
|
|
8815
|
+
}
|
|
8816
|
+
/**
|
|
8817
|
+
* Helper function to create a node conversion result.
|
|
8818
|
+
*
|
|
8819
|
+
* @param nodes Array of nodes
|
|
8820
|
+
* @param isConverted Indicates whether the input item was converted
|
|
8821
|
+
* @template T Type of the node (extends `Node`)
|
|
8822
|
+
* @returns Node conversion result
|
|
8823
|
+
*/
|
|
8824
|
+
function createNodeConversionResult(nodes, isConverted) {
|
|
8825
|
+
return createConversionResult(nodes, isConverted);
|
|
8826
|
+
}
|
|
8827
|
+
|
|
8559
8828
|
/**
|
|
8560
8829
|
* @file Comment rule converter
|
|
8561
8830
|
*/
|
|
@@ -8569,27 +8838,30 @@ class CommentRuleConverter extends RuleConverterBase {
|
|
|
8569
8838
|
* Converts a comment rule to AdGuard format, if possible.
|
|
8570
8839
|
*
|
|
8571
8840
|
* @param rule Rule node to convert
|
|
8572
|
-
* @returns
|
|
8841
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
8842
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
8843
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8573
8844
|
* @throws If the rule is invalid or cannot be converted
|
|
8574
8845
|
*/
|
|
8575
8846
|
static convertToAdg(rule) {
|
|
8576
|
-
// Clone the provided AST node to avoid side effects
|
|
8577
|
-
const ruleNode = cloneDeep(rule);
|
|
8578
8847
|
// TODO: Add support for other comment types, if needed
|
|
8579
8848
|
// Main task is # -> ! conversion
|
|
8580
|
-
switch (
|
|
8849
|
+
switch (rule.type) {
|
|
8581
8850
|
case CommentRuleType.CommentRule:
|
|
8582
|
-
//
|
|
8583
|
-
if (
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8851
|
+
// Check if the rule needs to be converted
|
|
8852
|
+
if (rule.type === CommentRuleType.CommentRule && rule.marker.value === CommentMarker.Hashmark) {
|
|
8853
|
+
// Add a ! to the beginning of the comment
|
|
8854
|
+
// TODO: Replace with custom clone method
|
|
8855
|
+
const ruleClone = clone(rule);
|
|
8856
|
+
ruleClone.marker.value = CommentMarker.Regular;
|
|
8857
|
+
// Add the hashmark to the beginning of the comment text
|
|
8858
|
+
ruleClone.text.value = `${SPACE}${CommentMarker.Hashmark}${ruleClone.text.value}`;
|
|
8859
|
+
return createNodeConversionResult([ruleClone], true);
|
|
8588
8860
|
}
|
|
8589
|
-
return [
|
|
8861
|
+
return createNodeConversionResult([rule], false);
|
|
8590
8862
|
// Leave any other comment rule as is
|
|
8591
8863
|
default:
|
|
8592
|
-
return [
|
|
8864
|
+
return createNodeConversionResult([rule], false);
|
|
8593
8865
|
}
|
|
8594
8866
|
}
|
|
8595
8867
|
}
|
|
@@ -8759,6 +9031,58 @@ class RegExpUtils {
|
|
|
8759
9031
|
}
|
|
8760
9032
|
}
|
|
8761
9033
|
|
|
9034
|
+
/**
|
|
9035
|
+
* @file Custom clone functions for AST nodes, this is probably the most efficient way to clone AST nodes.
|
|
9036
|
+
* @todo Maybe move them to parser classes as 'clone' methods
|
|
9037
|
+
*/
|
|
9038
|
+
/**
|
|
9039
|
+
* Clones a scriptlet rule node.
|
|
9040
|
+
*
|
|
9041
|
+
* @param node Node to clone
|
|
9042
|
+
* @returns Cloned node
|
|
9043
|
+
*/
|
|
9044
|
+
function cloneScriptletRuleNode(node) {
|
|
9045
|
+
return {
|
|
9046
|
+
type: node.type,
|
|
9047
|
+
children: node.children.map((child) => ({ ...child })),
|
|
9048
|
+
};
|
|
9049
|
+
}
|
|
9050
|
+
/**
|
|
9051
|
+
* Clones a domain list node.
|
|
9052
|
+
*
|
|
9053
|
+
* @param node Node to clone
|
|
9054
|
+
* @returns Cloned node
|
|
9055
|
+
*/
|
|
9056
|
+
function cloneDomainListNode(node) {
|
|
9057
|
+
return {
|
|
9058
|
+
type: node.type,
|
|
9059
|
+
separator: node.separator,
|
|
9060
|
+
children: node.children.map((domain) => ({ ...domain })),
|
|
9061
|
+
};
|
|
9062
|
+
}
|
|
9063
|
+
/**
|
|
9064
|
+
* Clones a modifier list node.
|
|
9065
|
+
*
|
|
9066
|
+
* @param node Node to clone
|
|
9067
|
+
* @returns Cloned node
|
|
9068
|
+
*/
|
|
9069
|
+
function cloneModifierListNode(node) {
|
|
9070
|
+
return {
|
|
9071
|
+
type: node.type,
|
|
9072
|
+
children: node.children.map((modifier) => {
|
|
9073
|
+
const res = {
|
|
9074
|
+
type: modifier.type,
|
|
9075
|
+
exception: modifier.exception,
|
|
9076
|
+
modifier: { ...modifier.modifier },
|
|
9077
|
+
};
|
|
9078
|
+
if (modifier.value) {
|
|
9079
|
+
res.value = { ...modifier.value };
|
|
9080
|
+
}
|
|
9081
|
+
return res;
|
|
9082
|
+
}),
|
|
9083
|
+
};
|
|
9084
|
+
}
|
|
9085
|
+
|
|
8762
9086
|
/**
|
|
8763
9087
|
* @file HTML filtering rule converter
|
|
8764
9088
|
*/
|
|
@@ -8771,16 +9095,22 @@ class RegExpUtils {
|
|
|
8771
9095
|
*
|
|
8772
9096
|
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
|
|
8773
9097
|
*/
|
|
8774
|
-
const
|
|
8775
|
-
const
|
|
9098
|
+
const ADG_HTML_DEFAULT_MAX_LENGTH = 8192;
|
|
9099
|
+
const ADG_HTML_CONVERSION_MAX_LENGTH = ADG_HTML_DEFAULT_MAX_LENGTH * 32;
|
|
8776
9100
|
const NOT_SPECIFIED = -1;
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
9101
|
+
var PseudoClasses$1;
|
|
9102
|
+
(function (PseudoClasses) {
|
|
9103
|
+
PseudoClasses["Contains"] = "contains";
|
|
9104
|
+
PseudoClasses["HasText"] = "has-text";
|
|
9105
|
+
PseudoClasses["MinTextLength"] = "min-text-length";
|
|
9106
|
+
})(PseudoClasses$1 || (PseudoClasses$1 = {}));
|
|
9107
|
+
var AttributeSelectors;
|
|
9108
|
+
(function (AttributeSelectors) {
|
|
9109
|
+
AttributeSelectors["MaxLength"] = "max-length";
|
|
9110
|
+
AttributeSelectors["MinLength"] = "min-length";
|
|
9111
|
+
AttributeSelectors["TagContent"] = "tag-content";
|
|
9112
|
+
AttributeSelectors["Wildcard"] = "wildcard";
|
|
9113
|
+
})(AttributeSelectors || (AttributeSelectors = {}));
|
|
8784
9114
|
/**
|
|
8785
9115
|
* HTML filtering rule converter class
|
|
8786
9116
|
*
|
|
@@ -8803,16 +9133,23 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8803
9133
|
* ```
|
|
8804
9134
|
*
|
|
8805
9135
|
* @param rule Rule node to convert
|
|
8806
|
-
* @returns
|
|
9136
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9137
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9138
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
8807
9139
|
* @throws If the rule is invalid or cannot be converted
|
|
8808
9140
|
*/
|
|
8809
9141
|
static convertToAdg(rule) {
|
|
8810
|
-
//
|
|
8811
|
-
|
|
9142
|
+
// Ignore AdGuard rules
|
|
9143
|
+
if (rule.syntax === AdblockSyntax.Adg) {
|
|
9144
|
+
return createNodeConversionResult([rule], false);
|
|
9145
|
+
}
|
|
9146
|
+
if (rule.syntax === AdblockSyntax.Abp) {
|
|
9147
|
+
throw new RuleConversionError('Invalid rule, ABP does not support HTML filtering rules');
|
|
9148
|
+
}
|
|
8812
9149
|
// Prepare the conversion result
|
|
8813
9150
|
const conversionResult = [];
|
|
8814
9151
|
// Iterate over selector list
|
|
8815
|
-
for (const selector of
|
|
9152
|
+
for (const selector of rule.body.body.children) {
|
|
8816
9153
|
// Check selector, just in case
|
|
8817
9154
|
if (selector.type !== CssTreeNodeType.Selector) {
|
|
8818
9155
|
throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
|
|
@@ -8839,24 +9176,24 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8839
9176
|
throw new RuleConversionError('Tag selector should be the first child, if present');
|
|
8840
9177
|
}
|
|
8841
9178
|
// Simply store the tag selector
|
|
8842
|
-
convertedSelector.children.push(
|
|
9179
|
+
convertedSelector.children.push(clone(node));
|
|
8843
9180
|
break;
|
|
8844
9181
|
case CssTreeNodeType.AttributeSelector:
|
|
8845
9182
|
// Check if the attribute selector is a special AdGuard attribute
|
|
8846
9183
|
switch (node.name.name) {
|
|
8847
|
-
case
|
|
9184
|
+
case AttributeSelectors.MinLength:
|
|
8848
9185
|
minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
8849
9186
|
break;
|
|
8850
|
-
case
|
|
9187
|
+
case AttributeSelectors.MaxLength:
|
|
8851
9188
|
maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
8852
9189
|
break;
|
|
8853
|
-
case
|
|
8854
|
-
case
|
|
9190
|
+
case AttributeSelectors.TagContent:
|
|
9191
|
+
case AttributeSelectors.Wildcard:
|
|
8855
9192
|
CssTree.assertAttributeSelectorHasStringValue(node);
|
|
8856
|
-
convertedSelector.children.push(
|
|
9193
|
+
convertedSelector.children.push(clone(node));
|
|
8857
9194
|
break;
|
|
8858
9195
|
default:
|
|
8859
|
-
convertedSelector.children.push(
|
|
9196
|
+
convertedSelector.children.push(clone(node));
|
|
8860
9197
|
}
|
|
8861
9198
|
break;
|
|
8862
9199
|
case CssTreeNodeType.PseudoClassSelector:
|
|
@@ -8870,18 +9207,18 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8870
9207
|
}
|
|
8871
9208
|
// Process the pseudo class based on its name
|
|
8872
9209
|
switch (node.name) {
|
|
8873
|
-
case
|
|
8874
|
-
case
|
|
9210
|
+
case PseudoClasses$1.HasText:
|
|
9211
|
+
case PseudoClasses$1.Contains:
|
|
8875
9212
|
// Check if the argument is a RegExp
|
|
8876
9213
|
if (RegExpUtils.isRegexPattern(arg.value)) {
|
|
8877
9214
|
// TODO: Add some support for RegExp patterns later
|
|
8878
9215
|
// Need to find a way to convert some RegExp patterns to glob patterns
|
|
8879
9216
|
throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
|
|
8880
9217
|
}
|
|
8881
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
9218
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.TagContent, arg.value));
|
|
8882
9219
|
break;
|
|
8883
9220
|
// https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
|
|
8884
|
-
case
|
|
9221
|
+
case PseudoClasses$1.MinTextLength:
|
|
8885
9222
|
minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
|
|
8886
9223
|
break;
|
|
8887
9224
|
default:
|
|
@@ -8893,10 +9230,10 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8893
9230
|
}
|
|
8894
9231
|
}
|
|
8895
9232
|
if (minLength !== NOT_SPECIFIED) {
|
|
8896
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
9233
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MinLength, String(minLength)));
|
|
8897
9234
|
}
|
|
8898
|
-
convertedSelector.children.push(CssTree.createAttributeSelectorNode(
|
|
8899
|
-
?
|
|
9235
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MaxLength, String(maxLength === NOT_SPECIFIED
|
|
9236
|
+
? ADG_HTML_CONVERSION_MAX_LENGTH
|
|
8900
9237
|
: maxLength)));
|
|
8901
9238
|
// Create the converted rule
|
|
8902
9239
|
conversionResult.push({
|
|
@@ -8906,7 +9243,7 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8906
9243
|
// Convert the separator based on the exception status
|
|
8907
9244
|
separator: {
|
|
8908
9245
|
type: 'Value',
|
|
8909
|
-
value:
|
|
9246
|
+
value: rule.exception
|
|
8910
9247
|
? CosmeticRuleSeparator.AdgHtmlFilteringException
|
|
8911
9248
|
: CosmeticRuleSeparator.AdgHtmlFiltering,
|
|
8912
9249
|
},
|
|
@@ -8921,11 +9258,11 @@ class HtmlRuleConverter extends RuleConverterBase {
|
|
|
8921
9258
|
}],
|
|
8922
9259
|
},
|
|
8923
9260
|
},
|
|
8924
|
-
exception:
|
|
8925
|
-
domains:
|
|
9261
|
+
exception: rule.exception,
|
|
9262
|
+
domains: cloneDomainListNode(rule.domains),
|
|
8926
9263
|
});
|
|
8927
9264
|
}
|
|
8928
|
-
return conversionResult;
|
|
9265
|
+
return createNodeConversionResult(conversionResult, true);
|
|
8929
9266
|
}
|
|
8930
9267
|
}
|
|
8931
9268
|
|
|
@@ -8946,96 +9283,38 @@ function getScriptletName(scriptletNode) {
|
|
|
8946
9283
|
return scriptletNode.children[0].value;
|
|
8947
9284
|
}
|
|
8948
9285
|
/**
|
|
8949
|
-
* Set name of the scriptlet
|
|
9286
|
+
* Set name of the scriptlet.
|
|
9287
|
+
* Modifies input `scriptletNode` if needed.
|
|
8950
9288
|
*
|
|
8951
9289
|
* @param scriptletNode Scriptlet node to set name of
|
|
8952
9290
|
* @param name Name to set
|
|
8953
|
-
* @returns Scriptlet node with the specified name
|
|
8954
|
-
* @throws If the scriptlet is empty
|
|
8955
9291
|
*/
|
|
8956
9292
|
function setScriptletName(scriptletNode, name) {
|
|
8957
|
-
if (scriptletNode.children.length
|
|
8958
|
-
|
|
9293
|
+
if (scriptletNode.children.length > 0) {
|
|
9294
|
+
// eslint-disable-next-line no-param-reassign
|
|
9295
|
+
scriptletNode.children[0].value = name;
|
|
8959
9296
|
}
|
|
8960
|
-
const scriptletNodeClone = cloneDeep(scriptletNode);
|
|
8961
|
-
scriptletNodeClone.children[0].value = name;
|
|
8962
|
-
return scriptletNodeClone;
|
|
8963
9297
|
}
|
|
8964
9298
|
/**
|
|
8965
9299
|
* Set quote type of the scriptlet parameters
|
|
8966
9300
|
*
|
|
8967
9301
|
* @param scriptletNode Scriptlet node to set quote type of
|
|
8968
9302
|
* @param quoteType Preferred quote type
|
|
8969
|
-
* @returns Scriptlet node with the specified quote type
|
|
8970
9303
|
*/
|
|
8971
9304
|
function setScriptletQuoteType(scriptletNode, quoteType) {
|
|
8972
|
-
if (scriptletNode.children.length
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
|
|
8977
|
-
scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
|
|
8978
|
-
}
|
|
8979
|
-
return scriptletNodeClone;
|
|
8980
|
-
}
|
|
8981
|
-
|
|
8982
|
-
/**
|
|
8983
|
-
* @file Scriptlet conversions from ABP and uBO to ADG
|
|
8984
|
-
*/
|
|
8985
|
-
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
8986
|
-
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
8987
|
-
/**
|
|
8988
|
-
* Helper class for converting scriptlets from ABP and uBO to ADG
|
|
8989
|
-
*/
|
|
8990
|
-
class AdgScriptletConverter {
|
|
8991
|
-
/**
|
|
8992
|
-
* Helper function to convert scriptlets to ADG. We implement the core
|
|
8993
|
-
* logic here to avoid code duplication.
|
|
8994
|
-
*
|
|
8995
|
-
* @param scriptletNode Scriptlet parameter list node to convert
|
|
8996
|
-
* @param prefix Prefix to add to the scriptlet name
|
|
8997
|
-
* @returns Converted scriptlet parameter list node
|
|
8998
|
-
*/
|
|
8999
|
-
static convertToAdg(scriptletNode, prefix) {
|
|
9000
|
-
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
9001
|
-
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), QuoteType.None);
|
|
9002
|
-
// Clone the node to avoid any side effects
|
|
9003
|
-
let result = cloneDeep(scriptletNode);
|
|
9004
|
-
// Only add prefix if it's not already there
|
|
9005
|
-
if (!scriptletName.startsWith(prefix)) {
|
|
9006
|
-
result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
|
|
9305
|
+
if (scriptletNode.children.length > 0) {
|
|
9306
|
+
for (let i = 0; i < scriptletNode.children.length; i += 1) {
|
|
9307
|
+
// eslint-disable-next-line no-param-reassign
|
|
9308
|
+
scriptletNode.children[i].value = QuoteUtils.setStringQuoteType(scriptletNode.children[i].value, quoteType);
|
|
9007
9309
|
}
|
|
9008
|
-
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
9009
|
-
result = setScriptletQuoteType(result, QuoteType.Single);
|
|
9010
|
-
return result;
|
|
9011
9310
|
}
|
|
9012
|
-
/**
|
|
9013
|
-
* Converts an ABP snippet node to ADG scriptlet node, if possible.
|
|
9014
|
-
*
|
|
9015
|
-
* @param scriptletNode Scriptlet node to convert
|
|
9016
|
-
* @returns Converted scriptlet node
|
|
9017
|
-
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
9018
|
-
* @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
|
|
9019
|
-
*/
|
|
9020
|
-
static convertFromAbp = (scriptletNode) => {
|
|
9021
|
-
return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
|
|
9022
|
-
};
|
|
9023
|
-
/**
|
|
9024
|
-
* Convert a uBO scriptlet node to ADG scriptlet node, if possible.
|
|
9025
|
-
*
|
|
9026
|
-
* @param scriptletNode Scriptlet node to convert
|
|
9027
|
-
* @returns Converted scriptlet node
|
|
9028
|
-
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
9029
|
-
* @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
|
|
9030
|
-
*/
|
|
9031
|
-
static convertFromUbo = (scriptletNode) => {
|
|
9032
|
-
return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
|
|
9033
|
-
};
|
|
9034
9311
|
}
|
|
9035
9312
|
|
|
9036
9313
|
/**
|
|
9037
9314
|
* @file Scriptlet injection rule converter
|
|
9038
9315
|
*/
|
|
9316
|
+
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
9317
|
+
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
9039
9318
|
/**
|
|
9040
9319
|
* Scriptlet injection rule converter class
|
|
9041
9320
|
*
|
|
@@ -9046,38 +9325,91 @@ class ScriptletRuleConverter extends RuleConverterBase {
|
|
|
9046
9325
|
* Converts a scriptlet injection rule to AdGuard format, if possible.
|
|
9047
9326
|
*
|
|
9048
9327
|
* @param rule Rule node to convert
|
|
9049
|
-
* @returns
|
|
9328
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9329
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9330
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9050
9331
|
* @throws If the rule is invalid or cannot be converted
|
|
9051
9332
|
*/
|
|
9052
9333
|
static convertToAdg(rule) {
|
|
9053
|
-
//
|
|
9054
|
-
|
|
9334
|
+
// Ignore AdGuard rules
|
|
9335
|
+
if (rule.syntax === AdblockSyntax.Adg) {
|
|
9336
|
+
return createNodeConversionResult([rule], false);
|
|
9337
|
+
}
|
|
9338
|
+
const separator = rule.separator.value;
|
|
9339
|
+
let convertedSeparator = separator;
|
|
9340
|
+
convertedSeparator = rule.exception
|
|
9341
|
+
? CosmeticRuleSeparator.AdgJsInjectionException
|
|
9342
|
+
: CosmeticRuleSeparator.AdgJsInjection;
|
|
9055
9343
|
const convertedScriptlets = [];
|
|
9056
|
-
for (const scriptlet of
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9344
|
+
for (const scriptlet of rule.body.children) {
|
|
9345
|
+
// Clone the node to avoid any side effects
|
|
9346
|
+
const scriptletClone = cloneScriptletRuleNode(scriptlet);
|
|
9347
|
+
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
9348
|
+
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
|
|
9349
|
+
// Add prefix if it's not already there
|
|
9350
|
+
let prefix;
|
|
9351
|
+
switch (rule.syntax) {
|
|
9352
|
+
case AdblockSyntax.Abp:
|
|
9353
|
+
prefix = ABP_SCRIPTLET_PREFIX;
|
|
9354
|
+
break;
|
|
9355
|
+
case AdblockSyntax.Ubo:
|
|
9356
|
+
prefix = UBO_SCRIPTLET_PREFIX;
|
|
9357
|
+
break;
|
|
9358
|
+
default:
|
|
9359
|
+
prefix = EMPTY;
|
|
9062
9360
|
}
|
|
9063
|
-
|
|
9064
|
-
|
|
9361
|
+
if (!scriptletName.startsWith(prefix)) {
|
|
9362
|
+
setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
|
|
9065
9363
|
}
|
|
9364
|
+
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
9365
|
+
setScriptletQuoteType(scriptletClone, QuoteType.Single);
|
|
9366
|
+
convertedScriptlets.push(scriptletClone);
|
|
9066
9367
|
}
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
return convertedScriptlets.map((scriptlet) => {
|
|
9072
|
-
return {
|
|
9073
|
-
...ruleNode,
|
|
9368
|
+
return createNodeConversionResult(convertedScriptlets.map((scriptlet) => {
|
|
9369
|
+
const res = {
|
|
9370
|
+
category: rule.category,
|
|
9371
|
+
type: rule.type,
|
|
9074
9372
|
syntax: AdblockSyntax.Adg,
|
|
9373
|
+
exception: rule.exception,
|
|
9374
|
+
domains: cloneDomainListNode(rule.domains),
|
|
9375
|
+
separator: {
|
|
9376
|
+
type: 'Value',
|
|
9377
|
+
value: convertedSeparator,
|
|
9378
|
+
},
|
|
9075
9379
|
body: {
|
|
9076
|
-
|
|
9380
|
+
type: rule.body.type,
|
|
9077
9381
|
children: [scriptlet],
|
|
9078
9382
|
},
|
|
9079
9383
|
};
|
|
9080
|
-
|
|
9384
|
+
if (rule.modifiers) {
|
|
9385
|
+
res.modifiers = cloneModifierListNode(rule.modifiers);
|
|
9386
|
+
}
|
|
9387
|
+
return res;
|
|
9388
|
+
}), true);
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9391
|
+
|
|
9392
|
+
/**
|
|
9393
|
+
* A very simple map extension that allows to store multiple values for the same key
|
|
9394
|
+
* by storing them in an array.
|
|
9395
|
+
*
|
|
9396
|
+
* @todo Add more methods if needed
|
|
9397
|
+
*/
|
|
9398
|
+
class MultiValueMap extends Map {
|
|
9399
|
+
/**
|
|
9400
|
+
* Adds a value to the map. If the key already exists, the value will be appended to the existing array,
|
|
9401
|
+
* otherwise a new array will be created for the key.
|
|
9402
|
+
*
|
|
9403
|
+
* @param key Key to add
|
|
9404
|
+
* @param values Value(s) to add
|
|
9405
|
+
*/
|
|
9406
|
+
add(key, ...values) {
|
|
9407
|
+
let currentValues = super.get(key);
|
|
9408
|
+
if (isUndefined(currentValues)) {
|
|
9409
|
+
currentValues = [];
|
|
9410
|
+
super.set(key, values);
|
|
9411
|
+
}
|
|
9412
|
+
currentValues.push(...values);
|
|
9081
9413
|
}
|
|
9082
9414
|
}
|
|
9083
9415
|
|
|
@@ -9103,69 +9435,115 @@ class AdgCosmeticRuleModifierConverter {
|
|
|
9103
9435
|
* Converts a uBO cosmetic rule modifier list to ADG, if possible.
|
|
9104
9436
|
*
|
|
9105
9437
|
* @param modifierList Cosmetic rule modifier list node to convert
|
|
9106
|
-
* @returns
|
|
9438
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9439
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9440
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9107
9441
|
* @throws If the modifier list cannot be converted
|
|
9108
9442
|
* @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
|
|
9109
9443
|
*/
|
|
9110
|
-
static convertFromUbo
|
|
9111
|
-
const
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
//
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
|
|
9129
|
-
: modifierValue));
|
|
9130
|
-
break;
|
|
9131
|
-
default:
|
|
9132
|
-
// Leave the modifier as-is
|
|
9133
|
-
convertedModifierList.children.push(modifier);
|
|
9444
|
+
static convertFromUbo(modifierList) {
|
|
9445
|
+
const conversionMap = new MultiValueMap();
|
|
9446
|
+
modifierList.children.forEach((modifier, index) => {
|
|
9447
|
+
// :matches-path
|
|
9448
|
+
if (modifier.modifier.value === UBO_MATCHES_PATH_OPERATOR) {
|
|
9449
|
+
if (!modifier.value) {
|
|
9450
|
+
throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
|
|
9451
|
+
}
|
|
9452
|
+
const value = RegExpUtils.isRegexPattern(modifier.value.value)
|
|
9453
|
+
? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
|
|
9454
|
+
: modifier.value.value;
|
|
9455
|
+
// Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
|
|
9456
|
+
conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
|
|
9457
|
+
// We should negate the regexp if the modifier is an exception
|
|
9458
|
+
modifier.exception
|
|
9459
|
+
// eslint-disable-next-line max-len
|
|
9460
|
+
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}`
|
|
9461
|
+
: value));
|
|
9134
9462
|
}
|
|
9135
|
-
}
|
|
9136
|
-
|
|
9137
|
-
|
|
9463
|
+
});
|
|
9464
|
+
// Check if we have any converted modifiers
|
|
9465
|
+
if (conversionMap.size) {
|
|
9466
|
+
const modifierListClone = clone(modifierList);
|
|
9467
|
+
// Replace the original modifiers with the converted ones
|
|
9468
|
+
modifierListClone.children = modifierListClone.children.map((modifier, index) => {
|
|
9469
|
+
const convertedModifier = conversionMap.get(index);
|
|
9470
|
+
return convertedModifier ?? modifier;
|
|
9471
|
+
}).flat();
|
|
9472
|
+
return createConversionResult(modifierListClone, true);
|
|
9473
|
+
}
|
|
9474
|
+
// Otherwise, just return the original modifier list
|
|
9475
|
+
return createConversionResult(modifierList, false);
|
|
9476
|
+
}
|
|
9138
9477
|
}
|
|
9139
9478
|
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9479
|
+
var PseudoClasses;
|
|
9480
|
+
(function (PseudoClasses) {
|
|
9481
|
+
PseudoClasses["AbpContains"] = "-abp-contains";
|
|
9482
|
+
PseudoClasses["AbpHas"] = "-abp-has";
|
|
9483
|
+
PseudoClasses["Contains"] = "contains";
|
|
9484
|
+
PseudoClasses["Has"] = "has";
|
|
9485
|
+
PseudoClasses["HasText"] = "has-text";
|
|
9486
|
+
PseudoClasses["MatchesCss"] = "matches-css";
|
|
9487
|
+
PseudoClasses["MatchesCssAfter"] = "matches-css-after";
|
|
9488
|
+
PseudoClasses["MatchesCssBefore"] = "matches-css-before";
|
|
9489
|
+
PseudoClasses["Not"] = "not";
|
|
9490
|
+
})(PseudoClasses || (PseudoClasses = {}));
|
|
9491
|
+
var PseudoElements;
|
|
9492
|
+
(function (PseudoElements) {
|
|
9493
|
+
PseudoElements["After"] = "after";
|
|
9494
|
+
PseudoElements["Before"] = "before";
|
|
9495
|
+
})(PseudoElements || (PseudoElements = {}));
|
|
9496
|
+
const PSEUDO_ELEMENT_NAMES = new Set([
|
|
9497
|
+
PseudoElements.After,
|
|
9498
|
+
PseudoElements.Before,
|
|
9499
|
+
]);
|
|
9500
|
+
const LEGACY_MATCHES_CSS_NAMES = new Set([
|
|
9501
|
+
PseudoClasses.MatchesCssAfter,
|
|
9502
|
+
PseudoClasses.MatchesCssBefore,
|
|
9503
|
+
]);
|
|
9504
|
+
const LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES = new Set([
|
|
9505
|
+
PseudoClasses.Not,
|
|
9506
|
+
PseudoClasses.MatchesCssBefore,
|
|
9507
|
+
PseudoClasses.MatchesCssAfter,
|
|
9508
|
+
]);
|
|
9509
|
+
const CSS_CONVERSION_INDICATOR_PSEUDO_NAMES = new Set([
|
|
9510
|
+
PseudoClasses.AbpContains,
|
|
9511
|
+
PseudoClasses.AbpHas,
|
|
9512
|
+
PseudoClasses.HasText,
|
|
9513
|
+
]);
|
|
9153
9514
|
/**
|
|
9154
9515
|
* Converts some pseudo-classes to pseudo-elements. For example:
|
|
9155
9516
|
* - `:before` → `::before`
|
|
9156
9517
|
*
|
|
9157
9518
|
* @param selectorList Selector list to convert
|
|
9158
|
-
* @returns
|
|
9519
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9520
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9521
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9159
9522
|
*/
|
|
9160
9523
|
function convertToPseudoElements(selectorList) {
|
|
9161
|
-
//
|
|
9162
|
-
const
|
|
9524
|
+
// Check conversion indications before doing any heavy work
|
|
9525
|
+
const hasIndicator = find(
|
|
9526
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9527
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9528
|
+
selectorList, (node) => node.type === CssTreeNodeType.PseudoClassSelector && PSEUDO_ELEMENT_NAMES.has(node.name));
|
|
9529
|
+
if (!hasIndicator) {
|
|
9530
|
+
return createConversionResult(selectorList, false);
|
|
9531
|
+
}
|
|
9532
|
+
// Make a clone of the selector list to avoid modifying the original one,
|
|
9533
|
+
// then convert & return the cloned version
|
|
9534
|
+
const selectorListClone = clone(selectorList);
|
|
9535
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9536
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9163
9537
|
walk(selectorListClone, {
|
|
9538
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9539
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9164
9540
|
leave: (node) => {
|
|
9165
9541
|
if (node.type === CssTreeNodeType.PseudoClassSelector) {
|
|
9166
|
-
//
|
|
9167
|
-
//
|
|
9168
|
-
|
|
9542
|
+
// If the pseudo-class is `:before` or `:after`, then we should
|
|
9543
|
+
// convert the node type to pseudo-element:
|
|
9544
|
+
// :after → ::after
|
|
9545
|
+
// :before → ::before
|
|
9546
|
+
if (PSEUDO_ELEMENT_NAMES.has(node.name)) {
|
|
9169
9547
|
Object.assign(node, {
|
|
9170
9548
|
...node,
|
|
9171
9549
|
type: CssTreeNodeType.PseudoElementSelector,
|
|
@@ -9174,7 +9552,7 @@ function convertToPseudoElements(selectorList) {
|
|
|
9174
9552
|
}
|
|
9175
9553
|
},
|
|
9176
9554
|
});
|
|
9177
|
-
return selectorListClone;
|
|
9555
|
+
return createConversionResult(selectorListClone, true);
|
|
9178
9556
|
}
|
|
9179
9557
|
/**
|
|
9180
9558
|
* Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
|
|
@@ -9183,33 +9561,36 @@ function convertToPseudoElements(selectorList) {
|
|
|
9183
9561
|
* - `:matches-css-after(...)` → `:matches-css(after, ...)`
|
|
9184
9562
|
*
|
|
9185
9563
|
* @param node Node to convert
|
|
9564
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9565
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9566
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9186
9567
|
* @throws If the node is invalid
|
|
9187
9568
|
*/
|
|
9188
9569
|
function convertLegacyMatchesCss(node) {
|
|
9189
|
-
|
|
9190
|
-
if (
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9570
|
+
// Check conversion indications before doing any heavy work
|
|
9571
|
+
if (node.type !== CssTreeNodeType.PseudoClassSelector || !LEGACY_MATCHES_CSS_NAMES.has(node.name)) {
|
|
9572
|
+
return createConversionResult(node, false);
|
|
9573
|
+
}
|
|
9574
|
+
const nodeClone = clone(node);
|
|
9575
|
+
if (!nodeClone.children || nodeClone.children.length < 1) {
|
|
9576
|
+
throw new Error(`Invalid ${node.name} pseudo-class: missing argument`);
|
|
9577
|
+
}
|
|
9578
|
+
// Rename the pseudo-class
|
|
9579
|
+
nodeClone.name = PseudoClasses.MatchesCss;
|
|
9580
|
+
// Remove the 'matches-css-' prefix to get the direction
|
|
9581
|
+
const direction = node.name.substring(PseudoClasses.MatchesCss.length + 1);
|
|
9582
|
+
// Add the direction to the first raw argument
|
|
9583
|
+
const arg = nodeClone.children[0];
|
|
9584
|
+
// Check argument
|
|
9585
|
+
if (!arg) {
|
|
9586
|
+
throw new Error(`Invalid ${node.name} pseudo-class: argument shouldn't be null`);
|
|
9587
|
+
}
|
|
9588
|
+
if (arg.type !== CssTreeNodeType.Raw) {
|
|
9589
|
+
throw new Error(`Invalid ${node.name} pseudo-class: unexpected argument type`);
|
|
9590
|
+
}
|
|
9591
|
+
// Add the direction as the first argument
|
|
9592
|
+
arg.value = `${direction},${arg.value}`;
|
|
9593
|
+
return createConversionResult(nodeClone, true);
|
|
9213
9594
|
}
|
|
9214
9595
|
/**
|
|
9215
9596
|
* Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
|
|
@@ -9219,16 +9600,40 @@ function convertLegacyMatchesCss(node) {
|
|
|
9219
9600
|
* - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
|
|
9220
9601
|
*
|
|
9221
9602
|
* @param selectorList Selector list AST to convert
|
|
9222
|
-
* @returns
|
|
9603
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9604
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9605
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9223
9606
|
*/
|
|
9224
9607
|
function convertFromLegacyExtendedCss(selectorList) {
|
|
9225
|
-
//
|
|
9226
|
-
const
|
|
9608
|
+
// Check conversion indications before doing any heavy work
|
|
9609
|
+
const hasIndicator = find(
|
|
9610
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9611
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9612
|
+
selectorList, (node) => {
|
|
9613
|
+
if (node.type === CssTreeNodeType.PseudoClassSelector) {
|
|
9614
|
+
return LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES.has(node.name);
|
|
9615
|
+
}
|
|
9616
|
+
if (node.type === CssTreeNodeType.AttributeSelector) {
|
|
9617
|
+
return node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX);
|
|
9618
|
+
}
|
|
9619
|
+
return false;
|
|
9620
|
+
});
|
|
9621
|
+
if (!hasIndicator) {
|
|
9622
|
+
return createConversionResult(selectorList, false);
|
|
9623
|
+
}
|
|
9624
|
+
const selectorListClone = clone(selectorList);
|
|
9625
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9626
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9227
9627
|
walk(selectorListClone, {
|
|
9628
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9629
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9228
9630
|
leave: (node) => {
|
|
9229
9631
|
// :matches-css-before(arg) → :matches-css(before,arg)
|
|
9230
9632
|
// :matches-css-after(arg) → :matches-css(after,arg)
|
|
9231
|
-
convertLegacyMatchesCss(node);
|
|
9633
|
+
const convertedLegacyExtCss = convertLegacyMatchesCss(node);
|
|
9634
|
+
if (convertedLegacyExtCss.isConverted) {
|
|
9635
|
+
Object.assign(node, convertedLegacyExtCss.result);
|
|
9636
|
+
}
|
|
9232
9637
|
// [-ext-name=...] → :name(...)
|
|
9233
9638
|
// [-ext-name='...'] → :name(...)
|
|
9234
9639
|
// [-ext-name="..."] → :name(...)
|
|
@@ -9242,7 +9647,7 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9242
9647
|
// Remove the '-ext-' prefix to get the pseudo-class name
|
|
9243
9648
|
const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
|
|
9244
9649
|
// Prepare the children list for the pseudo-class node
|
|
9245
|
-
const children =
|
|
9650
|
+
const children = [];
|
|
9246
9651
|
// TODO: Change String node to Raw node to drop the quotes.
|
|
9247
9652
|
// The structure of the node is the same, just the type
|
|
9248
9653
|
// is different and generate() will generate the quotes
|
|
@@ -9255,7 +9660,7 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9255
9660
|
// For example, if the input is [-ext-has="> .selector"], then
|
|
9256
9661
|
// we need to parse "> .selector" as a selector instead of string
|
|
9257
9662
|
// it as a raw value
|
|
9258
|
-
if ([
|
|
9663
|
+
if ([PseudoClasses.Has, PseudoClasses.Not].includes(name)) {
|
|
9259
9664
|
// Get the value of the attribute selector
|
|
9260
9665
|
const { value } = node;
|
|
9261
9666
|
// If the value is an identifier, then simply push it to the
|
|
@@ -9265,10 +9670,12 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9265
9670
|
}
|
|
9266
9671
|
else if (value.type === CssTreeNodeType.String) {
|
|
9267
9672
|
// Parse the value as a selector
|
|
9268
|
-
const parsedChildren = CssTree.
|
|
9673
|
+
const parsedChildren = CssTree.parsePlain(value.value, CssTreeParserContext.selectorList);
|
|
9269
9674
|
// Don't forget convert the parsed AST again, because
|
|
9270
9675
|
// it was a raw string before
|
|
9271
|
-
|
|
9676
|
+
const convertedChildren = convertFromLegacyExtendedCss(parsedChildren);
|
|
9677
|
+
// Push the converted children to the list
|
|
9678
|
+
children.push(convertedChildren.result);
|
|
9272
9679
|
}
|
|
9273
9680
|
}
|
|
9274
9681
|
else {
|
|
@@ -9295,14 +9702,12 @@ function convertFromLegacyExtendedCss(selectorList) {
|
|
|
9295
9702
|
children,
|
|
9296
9703
|
};
|
|
9297
9704
|
// Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
|
|
9298
|
-
convertLegacyMatchesCss(pseudoNode);
|
|
9299
|
-
|
|
9300
|
-
// keep the reference to the original node
|
|
9301
|
-
Object.assign(node, pseudoNode);
|
|
9705
|
+
const convertedPseudoNode = convertLegacyMatchesCss(pseudoNode);
|
|
9706
|
+
Object.assign(node, convertedPseudoNode.isConverted ? convertedPseudoNode.result : pseudoNode);
|
|
9302
9707
|
}
|
|
9303
9708
|
},
|
|
9304
9709
|
});
|
|
9305
|
-
return selectorListClone;
|
|
9710
|
+
return createConversionResult(selectorListClone, true);
|
|
9306
9711
|
}
|
|
9307
9712
|
/**
|
|
9308
9713
|
* CSS selector converter
|
|
@@ -9314,32 +9719,51 @@ class CssSelectorConverter extends ConverterBase {
|
|
|
9314
9719
|
* Converts Extended CSS elements to AdGuard-compatible ones
|
|
9315
9720
|
*
|
|
9316
9721
|
* @param selectorList Selector list to convert
|
|
9317
|
-
* @returns
|
|
9722
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
9723
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
9724
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9318
9725
|
* @throws If the rule is invalid or incompatible
|
|
9319
9726
|
*/
|
|
9320
9727
|
static convertToAdg(selectorList) {
|
|
9321
9728
|
// First, convert
|
|
9322
9729
|
// - legacy Extended CSS selectors to the modern Extended CSS syntax and
|
|
9323
9730
|
// - some pseudo-classes to pseudo-elements
|
|
9324
|
-
const
|
|
9731
|
+
const legacyExtCssConverted = convertFromLegacyExtendedCss(selectorList);
|
|
9732
|
+
const pseudoElementsConverted = convertToPseudoElements(legacyExtCssConverted.result);
|
|
9733
|
+
const hasIndicator = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted || find(
|
|
9734
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9735
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9736
|
+
selectorList,
|
|
9737
|
+
// eslint-disable-next-line max-len
|
|
9738
|
+
(node) => node.type === CssTreeNodeType.PseudoClassSelector && CSS_CONVERSION_INDICATOR_PSEUDO_NAMES.has(node.name));
|
|
9739
|
+
if (!hasIndicator) {
|
|
9740
|
+
return createConversionResult(selectorList, false);
|
|
9741
|
+
}
|
|
9742
|
+
const selectorListClone = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted
|
|
9743
|
+
? pseudoElementsConverted.result
|
|
9744
|
+
: clone(selectorList);
|
|
9325
9745
|
// Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
|
|
9746
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9747
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9326
9748
|
walk(selectorListClone, {
|
|
9749
|
+
// TODO: Need to improve CSSTree types, until then we need to use any type here
|
|
9750
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9327
9751
|
leave: (node) => {
|
|
9328
9752
|
if (node.type === CssTreeNodeType.PseudoClassSelector) {
|
|
9329
9753
|
// :-abp-contains(...) → :contains(...)
|
|
9330
9754
|
// :has-text(...) → :contains(...)
|
|
9331
|
-
if (node.name ===
|
|
9332
|
-
CssTree.renamePseudoClass(node,
|
|
9755
|
+
if (node.name === PseudoClasses.AbpContains || node.name === PseudoClasses.HasText) {
|
|
9756
|
+
CssTree.renamePseudoClass(node, PseudoClasses.Contains);
|
|
9333
9757
|
}
|
|
9334
9758
|
// :-abp-has(...) → :has(...)
|
|
9335
|
-
if (node.name ===
|
|
9336
|
-
CssTree.renamePseudoClass(node,
|
|
9759
|
+
if (node.name === PseudoClasses.AbpHas) {
|
|
9760
|
+
CssTree.renamePseudoClass(node, PseudoClasses.Has);
|
|
9337
9761
|
}
|
|
9338
9762
|
// TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
|
|
9339
9763
|
}
|
|
9340
9764
|
},
|
|
9341
9765
|
});
|
|
9342
|
-
return selectorListClone;
|
|
9766
|
+
return createConversionResult(selectorListClone, true);
|
|
9343
9767
|
}
|
|
9344
9768
|
}
|
|
9345
9769
|
|
|
@@ -9356,27 +9780,39 @@ class CssInjectionRuleConverter extends RuleConverterBase {
|
|
|
9356
9780
|
* Converts a CSS injection rule to AdGuard format, if possible.
|
|
9357
9781
|
*
|
|
9358
9782
|
* @param rule Rule node to convert
|
|
9359
|
-
* @returns
|
|
9783
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9784
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9785
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9360
9786
|
* @throws If the rule is invalid or cannot be converted
|
|
9361
9787
|
*/
|
|
9362
9788
|
static convertToAdg(rule) {
|
|
9363
|
-
|
|
9364
|
-
|
|
9789
|
+
const separator = rule.separator.value;
|
|
9790
|
+
let convertedSeparator = separator;
|
|
9365
9791
|
// Change the separator if the rule contains ExtendedCSS selectors
|
|
9366
|
-
if (CssTree.hasAnySelectorExtendedCssNode(
|
|
9367
|
-
|
|
9792
|
+
if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList) || rule.body.remove) {
|
|
9793
|
+
convertedSeparator = rule.exception
|
|
9368
9794
|
? CosmeticRuleSeparator.AdgExtendedCssInjectionException
|
|
9369
9795
|
: CosmeticRuleSeparator.AdgExtendedCssInjection;
|
|
9370
9796
|
}
|
|
9371
9797
|
else {
|
|
9372
|
-
|
|
9798
|
+
convertedSeparator = rule.exception
|
|
9373
9799
|
? CosmeticRuleSeparator.AdgCssInjectionException
|
|
9374
9800
|
: CosmeticRuleSeparator.AdgCssInjection;
|
|
9375
9801
|
}
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9802
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
|
|
9803
|
+
// Check if the rule needs to be converted
|
|
9804
|
+
if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg)
|
|
9805
|
+
|| separator !== convertedSeparator
|
|
9806
|
+
|| convertedSelectorList.isConverted) {
|
|
9807
|
+
// TODO: Replace with custom clone method
|
|
9808
|
+
const ruleClone = clone(rule);
|
|
9809
|
+
ruleClone.syntax = AdblockSyntax.Adg;
|
|
9810
|
+
ruleClone.separator.value = convertedSeparator;
|
|
9811
|
+
ruleClone.body.selectorList = convertedSelectorList.result;
|
|
9812
|
+
return createNodeConversionResult([ruleClone], true);
|
|
9813
|
+
}
|
|
9814
|
+
// Otherwise, return the original rule
|
|
9815
|
+
return createNodeConversionResult([rule], false);
|
|
9380
9816
|
}
|
|
9381
9817
|
}
|
|
9382
9818
|
|
|
@@ -9393,27 +9829,39 @@ class ElementHidingRuleConverter extends RuleConverterBase {
|
|
|
9393
9829
|
* Converts an element hiding rule to AdGuard format, if possible.
|
|
9394
9830
|
*
|
|
9395
9831
|
* @param rule Rule node to convert
|
|
9396
|
-
* @returns
|
|
9832
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9833
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9834
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9397
9835
|
* @throws If the rule is invalid or cannot be converted
|
|
9398
9836
|
*/
|
|
9399
9837
|
static convertToAdg(rule) {
|
|
9400
|
-
|
|
9401
|
-
|
|
9838
|
+
const separator = rule.separator.value;
|
|
9839
|
+
let convertedSeparator = separator;
|
|
9402
9840
|
// Change the separator if the rule contains ExtendedCSS selectors
|
|
9403
|
-
if (CssTree.hasAnySelectorExtendedCssNode(
|
|
9404
|
-
|
|
9841
|
+
if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList)) {
|
|
9842
|
+
convertedSeparator = rule.exception
|
|
9405
9843
|
? CosmeticRuleSeparator.ExtendedElementHidingException
|
|
9406
9844
|
: CosmeticRuleSeparator.ExtendedElementHiding;
|
|
9407
9845
|
}
|
|
9408
9846
|
else {
|
|
9409
|
-
|
|
9847
|
+
convertedSeparator = rule.exception
|
|
9410
9848
|
? CosmeticRuleSeparator.ElementHidingException
|
|
9411
9849
|
: CosmeticRuleSeparator.ElementHiding;
|
|
9412
9850
|
}
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9851
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
|
|
9852
|
+
// Check if the rule needs to be converted
|
|
9853
|
+
if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg)
|
|
9854
|
+
|| separator !== convertedSeparator
|
|
9855
|
+
|| convertedSelectorList.isConverted) {
|
|
9856
|
+
// TODO: Replace with custom clone method
|
|
9857
|
+
const ruleClone = clone(rule);
|
|
9858
|
+
ruleClone.syntax = AdblockSyntax.Adg;
|
|
9859
|
+
ruleClone.separator.value = convertedSeparator;
|
|
9860
|
+
ruleClone.body.selectorList = convertedSelectorList.result;
|
|
9861
|
+
return createNodeConversionResult([ruleClone], true);
|
|
9862
|
+
}
|
|
9863
|
+
// Otherwise, return the original rule
|
|
9864
|
+
return createNodeConversionResult([rule], false);
|
|
9417
9865
|
}
|
|
9418
9866
|
}
|
|
9419
9867
|
|
|
@@ -9441,7 +9889,7 @@ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false
|
|
|
9441
9889
|
},
|
|
9442
9890
|
};
|
|
9443
9891
|
if (!isUndefined(modifiers)) {
|
|
9444
|
-
result.modifiers =
|
|
9892
|
+
result.modifiers = clone(modifiers);
|
|
9445
9893
|
}
|
|
9446
9894
|
return result;
|
|
9447
9895
|
}
|
|
@@ -9461,32 +9909,37 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
|
9461
9909
|
* Converts a header removal rule to AdGuard syntax, if possible.
|
|
9462
9910
|
*
|
|
9463
9911
|
* @param rule Rule node to convert
|
|
9464
|
-
* @returns
|
|
9912
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9913
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9914
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9465
9915
|
* @throws If the rule is invalid or cannot be converted
|
|
9916
|
+
* @example
|
|
9917
|
+
* If the input rule is:
|
|
9918
|
+
* ```adblock
|
|
9919
|
+
* example.com##^responseheader(header-name)
|
|
9920
|
+
* ```
|
|
9921
|
+
* The output will be:
|
|
9922
|
+
* ```adblock
|
|
9923
|
+
* ||example.com^$removeheader=header-name
|
|
9924
|
+
* ```
|
|
9466
9925
|
*/
|
|
9467
9926
|
static convertToAdg(rule) {
|
|
9468
|
-
// Clone the provided AST node to avoid side effects
|
|
9469
|
-
const ruleNode = cloneDeep(rule);
|
|
9470
9927
|
// TODO: Add support for ABP syntax once it starts supporting header removal rules
|
|
9471
|
-
//
|
|
9472
|
-
if (
|
|
9473
|
-
||
|
|
9474
|
-
||
|
|
9475
|
-
||
|
|
9476
|
-
|
|
9928
|
+
// Leave the rule as is if it's not a header removal rule
|
|
9929
|
+
if (rule.category !== RuleCategory.Cosmetic
|
|
9930
|
+
|| rule.type !== CosmeticRuleType.HtmlFilteringRule
|
|
9931
|
+
|| rule.body.body.type !== CssTreeNodeType.Function
|
|
9932
|
+
|| rule.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
|
|
9933
|
+
return createNodeConversionResult([rule], false);
|
|
9477
9934
|
}
|
|
9478
9935
|
// Prepare network rule pattern
|
|
9479
|
-
|
|
9480
|
-
if (
|
|
9936
|
+
const pattern = [];
|
|
9937
|
+
if (rule.domains.children.length === 1) {
|
|
9481
9938
|
// If the rule has only one domain, we can use a simple network rule pattern:
|
|
9482
9939
|
// ||single-domain-from-the-rule^
|
|
9483
|
-
pattern
|
|
9484
|
-
ADBLOCK_URL_START,
|
|
9485
|
-
ruleNode.domains.children[0].value,
|
|
9486
|
-
ADBLOCK_URL_SEPARATOR,
|
|
9487
|
-
].join(EMPTY);
|
|
9940
|
+
pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
|
|
9488
9941
|
}
|
|
9489
|
-
else if (
|
|
9942
|
+
else if (rule.domains.children.length > 1) {
|
|
9490
9943
|
// TODO: Add support for multiple domains, for example:
|
|
9491
9944
|
// example.com,example.org,example.net##^responseheader(header-name)
|
|
9492
9945
|
// We should consider allowing $domain with $removeheader modifier,
|
|
@@ -9496,13 +9949,13 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
|
9496
9949
|
}
|
|
9497
9950
|
// Prepare network rule modifiers
|
|
9498
9951
|
const modifiers = createModifierListNode();
|
|
9499
|
-
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.
|
|
9952
|
+
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionPlainValue(rule.body.body)));
|
|
9500
9953
|
// Construct the network rule
|
|
9501
|
-
return [
|
|
9502
|
-
createNetworkRuleNode(pattern, modifiers,
|
|
9954
|
+
return createNodeConversionResult([
|
|
9955
|
+
createNetworkRuleNode(pattern.join(EMPTY), modifiers,
|
|
9503
9956
|
// Copy the exception flag
|
|
9504
|
-
|
|
9505
|
-
];
|
|
9957
|
+
rule.exception, AdblockSyntax.Adg),
|
|
9958
|
+
], true);
|
|
9506
9959
|
}
|
|
9507
9960
|
}
|
|
9508
9961
|
|
|
@@ -9519,48 +9972,69 @@ class CosmeticRuleConverter extends RuleConverterBase {
|
|
|
9519
9972
|
* Converts a cosmetic rule to AdGuard syntax, if possible.
|
|
9520
9973
|
*
|
|
9521
9974
|
* @param rule Rule node to convert
|
|
9522
|
-
* @returns
|
|
9975
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
9976
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
9977
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9523
9978
|
* @throws If the rule is invalid or cannot be converted
|
|
9524
9979
|
*/
|
|
9525
9980
|
static convertToAdg(rule) {
|
|
9526
|
-
|
|
9527
|
-
const ruleNode = cloneDeep(rule);
|
|
9528
|
-
// Convert cosmetic rule modifiers
|
|
9529
|
-
if (ruleNode.modifiers) {
|
|
9530
|
-
if (ruleNode.syntax === AdblockSyntax.Ubo) {
|
|
9531
|
-
// uBO doesn't support this rule:
|
|
9532
|
-
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
9533
|
-
if (ruleNode.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
9534
|
-
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
9535
|
-
}
|
|
9536
|
-
ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
|
|
9537
|
-
}
|
|
9538
|
-
else if (ruleNode.syntax === AdblockSyntax.Abp) {
|
|
9539
|
-
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
9540
|
-
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
9541
|
-
}
|
|
9542
|
-
}
|
|
9981
|
+
let subconverterResult;
|
|
9543
9982
|
// Convert cosmetic rule based on its type
|
|
9544
|
-
switch (
|
|
9983
|
+
switch (rule.type) {
|
|
9545
9984
|
case CosmeticRuleType.ElementHidingRule:
|
|
9546
|
-
|
|
9985
|
+
subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
|
|
9986
|
+
break;
|
|
9547
9987
|
case CosmeticRuleType.ScriptletInjectionRule:
|
|
9548
|
-
|
|
9988
|
+
subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
|
|
9989
|
+
break;
|
|
9549
9990
|
case CosmeticRuleType.CssInjectionRule:
|
|
9550
|
-
|
|
9991
|
+
subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
|
|
9992
|
+
break;
|
|
9551
9993
|
case CosmeticRuleType.HtmlFilteringRule:
|
|
9552
9994
|
// Handle special case: uBO response header filtering rule
|
|
9553
|
-
if (
|
|
9554
|
-
&&
|
|
9555
|
-
|
|
9995
|
+
if (rule.body.body.type === CssTreeNodeType.Function
|
|
9996
|
+
&& rule.body.body.name === UBO_RESPONSEHEADER_MARKER) {
|
|
9997
|
+
subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
|
|
9556
9998
|
}
|
|
9557
|
-
|
|
9558
|
-
|
|
9999
|
+
else {
|
|
10000
|
+
subconverterResult = HtmlRuleConverter.convertToAdg(rule);
|
|
10001
|
+
}
|
|
10002
|
+
break;
|
|
10003
|
+
// Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
|
|
9559
10004
|
case CosmeticRuleType.JsInjectionRule:
|
|
9560
|
-
|
|
10005
|
+
subconverterResult = createNodeConversionResult([rule], false);
|
|
10006
|
+
break;
|
|
9561
10007
|
default:
|
|
9562
10008
|
throw new RuleConversionError('Unsupported cosmetic rule type');
|
|
9563
10009
|
}
|
|
10010
|
+
let convertedModifiers;
|
|
10011
|
+
// Convert cosmetic rule modifiers, if any
|
|
10012
|
+
if (rule.modifiers) {
|
|
10013
|
+
if (rule.syntax === AdblockSyntax.Ubo) {
|
|
10014
|
+
// uBO doesn't support this rule:
|
|
10015
|
+
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
10016
|
+
if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
10017
|
+
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
10018
|
+
}
|
|
10019
|
+
convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
|
|
10020
|
+
}
|
|
10021
|
+
else if (rule.syntax === AdblockSyntax.Abp) {
|
|
10022
|
+
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
10023
|
+
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
10024
|
+
}
|
|
10025
|
+
}
|
|
10026
|
+
if ((subconverterResult.result.length > 1 || subconverterResult.isConverted)
|
|
10027
|
+
|| (convertedModifiers && convertedModifiers.isConverted)) {
|
|
10028
|
+
// Add modifier list to the subconverter result rules
|
|
10029
|
+
subconverterResult.result.forEach((subconverterRule) => {
|
|
10030
|
+
if (convertedModifiers && subconverterRule.category === RuleCategory.Cosmetic) {
|
|
10031
|
+
// eslint-disable-next-line no-param-reassign
|
|
10032
|
+
subconverterRule.modifiers = convertedModifiers.result;
|
|
10033
|
+
}
|
|
10034
|
+
});
|
|
10035
|
+
return subconverterResult;
|
|
10036
|
+
}
|
|
10037
|
+
return createNodeConversionResult([rule], false);
|
|
9564
10038
|
}
|
|
9565
10039
|
}
|
|
9566
10040
|
|
|
@@ -9632,17 +10106,16 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9632
10106
|
* Converts a network rule modifier list to AdGuard format, if possible.
|
|
9633
10107
|
*
|
|
9634
10108
|
* @param modifierList Network rule modifier list node to convert
|
|
9635
|
-
* @returns
|
|
10109
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10110
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
10111
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
9636
10112
|
* @throws If the conversion is not possible
|
|
9637
10113
|
*/
|
|
9638
10114
|
static convertToAdg(modifierList) {
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
const cspValues = [];
|
|
9644
|
-
modifierListNode.children.forEach((modifierNode) => {
|
|
9645
|
-
// Handle regular modifiers conversion and $csp modifiers collection
|
|
10115
|
+
const conversionMap = new MultiValueMap();
|
|
10116
|
+
// Special case: $csp modifier
|
|
10117
|
+
let cspCount = 0;
|
|
10118
|
+
modifierList.children.forEach((modifierNode, index) => {
|
|
9646
10119
|
const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
|
|
9647
10120
|
if (modifierConversions) {
|
|
9648
10121
|
for (const modifierConversion of modifierConversions) {
|
|
@@ -9655,17 +10128,14 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9655
10128
|
const value = modifierConversion.value
|
|
9656
10129
|
? modifierConversion.value(modifierNode.value?.value)
|
|
9657
10130
|
: modifierNode.value?.value;
|
|
9658
|
-
if
|
|
9659
|
-
|
|
9660
|
-
|
|
10131
|
+
// Check if the name or the value is different from the original modifier
|
|
10132
|
+
// If so, add the converted modifier to the list
|
|
10133
|
+
if (name !== modifierNode.modifier.value || value !== modifierNode.value?.value) {
|
|
10134
|
+
conversionMap.add(index, createModifierNode(name, value, exception));
|
|
9661
10135
|
}
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
|
|
9666
|
-
if (!existingModifier) {
|
|
9667
|
-
convertedModifierList.children.push(createModifierNode(name, value, exception));
|
|
9668
|
-
}
|
|
10136
|
+
// Special case: $csp modifier
|
|
10137
|
+
if (name === CSP_MODIFIER) {
|
|
10138
|
+
cspCount += 1;
|
|
9669
10139
|
}
|
|
9670
10140
|
}
|
|
9671
10141
|
return;
|
|
@@ -9688,26 +10158,52 @@ class NetworkRuleModifierListConverter extends ConverterBase {
|
|
|
9688
10158
|
// Try to convert the redirect resource name to ADG format
|
|
9689
10159
|
// This function returns undefined if the resource name is unknown
|
|
9690
10160
|
const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
|
|
9691
|
-
|
|
9692
|
-
// If
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
&& m.exception === modifierNode.exception
|
|
9701
|
-
&& m.value?.value === modifierNode.value?.value);
|
|
9702
|
-
if (!existingModifier) {
|
|
9703
|
-
convertedModifierList.children.push(modifierNode);
|
|
10161
|
+
// Check if the modifier name or the redirect resource name is different from the original modifier
|
|
10162
|
+
// If so, add the converted modifier to the list
|
|
10163
|
+
if (modifierName !== modifierNode.modifier.value
|
|
10164
|
+
|| (convertedRedirectResource !== undefined && convertedRedirectResource !== redirectResource)) {
|
|
10165
|
+
conversionMap.add(index, createModifierNode(modifierName,
|
|
10166
|
+
// If the redirect resource name is unknown, fall back to the original one
|
|
10167
|
+
// Later, the validator will throw an error if the resource name is invalid
|
|
10168
|
+
convertedRedirectResource || redirectResource, modifierNode.exception));
|
|
10169
|
+
}
|
|
9704
10170
|
}
|
|
9705
10171
|
});
|
|
9706
|
-
//
|
|
9707
|
-
if (
|
|
9708
|
-
|
|
10172
|
+
// Prepare the result if there are any converted modifiers or $csp modifiers
|
|
10173
|
+
if (conversionMap.size || cspCount) {
|
|
10174
|
+
const modifierListClone = cloneModifierListNode(modifierList);
|
|
10175
|
+
// Replace the original modifiers with the converted ones
|
|
10176
|
+
// One modifier may be replaced with multiple modifiers, so we need to flatten the array
|
|
10177
|
+
modifierListClone.children = modifierListClone.children.map((modifierNode, index) => {
|
|
10178
|
+
const conversionRecord = conversionMap.get(index);
|
|
10179
|
+
if (conversionRecord) {
|
|
10180
|
+
return conversionRecord;
|
|
10181
|
+
}
|
|
10182
|
+
return modifierNode;
|
|
10183
|
+
}).flat();
|
|
10184
|
+
// Special case: $csp modifier: merge multiple $csp modifiers into one
|
|
10185
|
+
// and put it at the end of the modifier list
|
|
10186
|
+
if (cspCount) {
|
|
10187
|
+
const cspValues = [];
|
|
10188
|
+
modifierListClone.children = modifierListClone.children.filter((modifierNode) => {
|
|
10189
|
+
if (modifierNode.modifier.value === CSP_MODIFIER) {
|
|
10190
|
+
if (!modifierNode.value?.value) {
|
|
10191
|
+
throw new RuleConversionError('$csp modifier value is missing');
|
|
10192
|
+
}
|
|
10193
|
+
cspValues.push(modifierNode.value?.value);
|
|
10194
|
+
return false;
|
|
10195
|
+
}
|
|
10196
|
+
return true;
|
|
10197
|
+
});
|
|
10198
|
+
modifierListClone.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
|
|
10199
|
+
}
|
|
10200
|
+
// Before returning the result, remove duplicated modifiers
|
|
10201
|
+
modifierListClone.children = modifierListClone.children.filter((modifierNode, index, self) => self.findIndex((m) => m.modifier.value === modifierNode.modifier.value
|
|
10202
|
+
&& m.exception === modifierNode.exception
|
|
10203
|
+
&& m.value?.value === modifierNode.value?.value) === index);
|
|
10204
|
+
return createConversionResult(modifierListClone, true);
|
|
9709
10205
|
}
|
|
9710
|
-
return
|
|
10206
|
+
return createConversionResult(modifierList, false);
|
|
9711
10207
|
}
|
|
9712
10208
|
}
|
|
9713
10209
|
|
|
@@ -9724,17 +10220,35 @@ class NetworkRuleConverter extends RuleConverterBase {
|
|
|
9724
10220
|
* Converts a network rule to AdGuard format, if possible.
|
|
9725
10221
|
*
|
|
9726
10222
|
* @param rule Rule node to convert
|
|
9727
|
-
* @returns
|
|
10223
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
10224
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10225
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9728
10226
|
* @throws If the rule is invalid or cannot be converted
|
|
9729
10227
|
*/
|
|
9730
10228
|
static convertToAdg(rule) {
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
10229
|
+
if (rule.modifiers) {
|
|
10230
|
+
const modifiers = NetworkRuleModifierListConverter.convertToAdg(rule.modifiers);
|
|
10231
|
+
// If the object reference is different, it means that the modifiers were converted
|
|
10232
|
+
// In this case, we should clone the entire rule and replace the modifiers with the converted ones
|
|
10233
|
+
if (modifiers.isConverted) {
|
|
10234
|
+
return {
|
|
10235
|
+
result: [{
|
|
10236
|
+
category: RuleCategory.Network,
|
|
10237
|
+
type: 'NetworkRule',
|
|
10238
|
+
syntax: rule.syntax,
|
|
10239
|
+
exception: rule.exception,
|
|
10240
|
+
pattern: {
|
|
10241
|
+
type: 'Value',
|
|
10242
|
+
value: rule.pattern.value,
|
|
10243
|
+
},
|
|
10244
|
+
modifiers: modifiers.result,
|
|
10245
|
+
}],
|
|
10246
|
+
isConverted: true,
|
|
10247
|
+
};
|
|
10248
|
+
}
|
|
9736
10249
|
}
|
|
9737
|
-
return
|
|
10250
|
+
// If the modifiers were not converted, return the original rule
|
|
10251
|
+
return createNodeConversionResult([rule], false);
|
|
9738
10252
|
}
|
|
9739
10253
|
}
|
|
9740
10254
|
|
|
@@ -9755,48 +10269,27 @@ class RuleConverter extends RuleConverterBase {
|
|
|
9755
10269
|
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
9756
10270
|
*
|
|
9757
10271
|
* @param rule Rule node to convert
|
|
9758
|
-
* @returns
|
|
10272
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
10273
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10274
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
9759
10275
|
* @throws If the rule is invalid or cannot be converted
|
|
9760
10276
|
*/
|
|
9761
10277
|
static convertToAdg(rule) {
|
|
9762
|
-
// Clone the provided AST node to avoid side effects
|
|
9763
|
-
const ruleNode = cloneDeep(rule);
|
|
9764
10278
|
// Delegate conversion to the corresponding sub-converter
|
|
9765
10279
|
// based on the rule category
|
|
9766
|
-
switch (
|
|
10280
|
+
switch (rule.category) {
|
|
9767
10281
|
case RuleCategory.Comment:
|
|
9768
|
-
return CommentRuleConverter.convertToAdg(
|
|
10282
|
+
return CommentRuleConverter.convertToAdg(rule);
|
|
9769
10283
|
case RuleCategory.Cosmetic:
|
|
9770
|
-
return CosmeticRuleConverter.convertToAdg(
|
|
10284
|
+
return CosmeticRuleConverter.convertToAdg(rule);
|
|
9771
10285
|
case RuleCategory.Network:
|
|
9772
|
-
return NetworkRuleConverter.convertToAdg(
|
|
10286
|
+
return NetworkRuleConverter.convertToAdg(rule);
|
|
9773
10287
|
default:
|
|
9774
|
-
throw new RuleConversionError(`Unknown rule category: ${
|
|
10288
|
+
throw new RuleConversionError(`Unknown rule category: ${rule.category}`);
|
|
9775
10289
|
}
|
|
9776
10290
|
}
|
|
9777
10291
|
}
|
|
9778
10292
|
|
|
9779
|
-
/**
|
|
9780
|
-
* @file Utility functions for working with filter list nodes
|
|
9781
|
-
*/
|
|
9782
|
-
/**
|
|
9783
|
-
* Creates a filter list node
|
|
9784
|
-
*
|
|
9785
|
-
* @param rules Rules to put in the list (optional, defaults to an empty list)
|
|
9786
|
-
* @returns Filter list node
|
|
9787
|
-
*/
|
|
9788
|
-
function createFilterListNode(rules = []) {
|
|
9789
|
-
const result = {
|
|
9790
|
-
type: 'FilterList',
|
|
9791
|
-
children: [],
|
|
9792
|
-
};
|
|
9793
|
-
// We need to clone the rules to avoid side effects
|
|
9794
|
-
if (rules.length > 0) {
|
|
9795
|
-
result.children = cloneDeep(rules);
|
|
9796
|
-
}
|
|
9797
|
-
return result;
|
|
9798
|
-
}
|
|
9799
|
-
|
|
9800
10293
|
/**
|
|
9801
10294
|
* @file Adblock filter list converter
|
|
9802
10295
|
*/
|
|
@@ -9815,18 +10308,133 @@ class FilterListConverter extends ConverterBase {
|
|
|
9815
10308
|
* Converts an adblock filter list to AdGuard format, if possible.
|
|
9816
10309
|
*
|
|
9817
10310
|
* @param filterListNode Filter list node to convert
|
|
9818
|
-
* @
|
|
9819
|
-
*
|
|
10311
|
+
* @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
|
|
10312
|
+
* invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
|
|
10313
|
+
* Defaults to `true`.
|
|
10314
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10315
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
10316
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
10317
|
+
* @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
|
|
10318
|
+
*/
|
|
10319
|
+
static convertToAdg(filterListNode, tolerant = true) {
|
|
10320
|
+
// Prepare a map to store the converted rules by their index in the filter list
|
|
10321
|
+
const conversionMap = new MultiValueMap();
|
|
10322
|
+
// Iterate over the filtering rules and convert them one by one, then add them to the result (one conversion may
|
|
10323
|
+
// result in multiple rules)
|
|
10324
|
+
for (let i = 0; i < filterListNode.children.length; i += 1) {
|
|
10325
|
+
try {
|
|
10326
|
+
const convertedRules = RuleConverter.convertToAdg(filterListNode.children[i]);
|
|
10327
|
+
// Add the converted rules to the map if they were converted
|
|
10328
|
+
if (convertedRules.isConverted) {
|
|
10329
|
+
conversionMap.add(i, ...convertedRules.result);
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
catch (error) {
|
|
10333
|
+
// If the tolerant mode is disabled, we should throw an error, this will fail the whole filter list
|
|
10334
|
+
// conversion.
|
|
10335
|
+
// Otherwise, we just ignore the error and leave the rule as is
|
|
10336
|
+
if (!tolerant) {
|
|
10337
|
+
throw error;
|
|
10338
|
+
}
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
// If the conversion map is empty, it means that no rules were converted, so we can return the original filter
|
|
10342
|
+
// list
|
|
10343
|
+
if (conversionMap.size === 0) {
|
|
10344
|
+
return createConversionResult(filterListNode, false);
|
|
10345
|
+
}
|
|
10346
|
+
// Otherwise, create a new filter list node with the converted rules
|
|
10347
|
+
const convertedFilterList = {
|
|
10348
|
+
type: 'FilterList',
|
|
10349
|
+
children: [],
|
|
10350
|
+
};
|
|
10351
|
+
// Iterate over the original rules again and add them to the converted filter list, replacing the converted
|
|
10352
|
+
// rules with the new ones at the specified indexes
|
|
10353
|
+
for (let i = 0; i < filterListNode.children.length; i += 1) {
|
|
10354
|
+
const rules = conversionMap.get(i);
|
|
10355
|
+
if (rules) {
|
|
10356
|
+
convertedFilterList.children.push(...rules);
|
|
10357
|
+
}
|
|
10358
|
+
else {
|
|
10359
|
+
// We clone the unconverted rules to avoid mutating the original filter list if we return the converted
|
|
10360
|
+
// one
|
|
10361
|
+
convertedFilterList.children.push(clone(filterListNode.children[i]));
|
|
10362
|
+
}
|
|
10363
|
+
}
|
|
10364
|
+
return createConversionResult(convertedFilterList, true);
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
|
|
10368
|
+
/**
|
|
10369
|
+
* @file Filter list converter for raw filter lists
|
|
10370
|
+
*
|
|
10371
|
+
* Technically, this is a wrapper around `FilterListConverter` that works with nodes instead of strings.
|
|
10372
|
+
*/
|
|
10373
|
+
/**
|
|
10374
|
+
* Adblock filter list converter class.
|
|
10375
|
+
*
|
|
10376
|
+
* You can use this class to convert string-based filter lists, since most of the converters work with nodes.
|
|
10377
|
+
* This class just provides an extra layer on top of the {@link FilterListConverter} and calls the parser/serializer
|
|
10378
|
+
* before/after the conversion internally.
|
|
10379
|
+
*
|
|
10380
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
10381
|
+
*/
|
|
10382
|
+
class RawFilterListConverter extends ConverterBase {
|
|
10383
|
+
/**
|
|
10384
|
+
* Converts an adblock filter list text to AdGuard format, if possible.
|
|
10385
|
+
*
|
|
10386
|
+
* @param rawFilterList Raw filter list text to convert
|
|
10387
|
+
* @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
|
|
10388
|
+
* invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
|
|
10389
|
+
* Defaults to `true`.
|
|
10390
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10391
|
+
* the array of converted filter list text, and its `isConverted` flag indicates whether the original rule was
|
|
10392
|
+
* converted. If the rule was not converted, the original filter list text will be returned
|
|
10393
|
+
* @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
|
|
9820
10394
|
*/
|
|
9821
|
-
static convertToAdg(
|
|
9822
|
-
const
|
|
9823
|
-
//
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
const convertedRules = RuleConverter.convertToAdg(ruleNode);
|
|
9827
|
-
result.children.push(...convertedRules);
|
|
10395
|
+
static convertToAdg(rawFilterList, tolerant = true) {
|
|
10396
|
+
const conversionResult = FilterListConverter.convertToAdg(FilterListParser.parse(rawFilterList, tolerant), tolerant);
|
|
10397
|
+
// If the filter list was not converted, return the original text
|
|
10398
|
+
if (!conversionResult.isConverted) {
|
|
10399
|
+
return createConversionResult(rawFilterList, false);
|
|
9828
10400
|
}
|
|
9829
|
-
return result
|
|
10401
|
+
// Otherwise, serialize the filter list and return the result
|
|
10402
|
+
return createConversionResult(FilterListParser.generate(conversionResult.result), true);
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
|
|
10406
|
+
/**
|
|
10407
|
+
* @file Rule converter for raw rules
|
|
10408
|
+
*
|
|
10409
|
+
* Technically, this is a wrapper around `RuleConverter` that works with nodes instead of strings.
|
|
10410
|
+
*/
|
|
10411
|
+
/**
|
|
10412
|
+
* Adblock filtering rule converter class.
|
|
10413
|
+
*
|
|
10414
|
+
* You can use this class to convert string-based adblock rules, since most of the converters work with nodes.
|
|
10415
|
+
* This class just provides an extra layer on top of the {@link RuleConverter} and calls the parser/serializer
|
|
10416
|
+
* before/after the conversion internally.
|
|
10417
|
+
*
|
|
10418
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
10419
|
+
*/
|
|
10420
|
+
class RawRuleConverter extends ConverterBase {
|
|
10421
|
+
/**
|
|
10422
|
+
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
10423
|
+
*
|
|
10424
|
+
* @param rawRule Raw rule text to convert
|
|
10425
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
10426
|
+
* the array of converted rule texts, and its `isConverted` flag indicates whether the original rule was converted.
|
|
10427
|
+
* If the rule was not converted, the original rule text will be returned
|
|
10428
|
+
* @throws If the rule is invalid or cannot be converted
|
|
10429
|
+
*/
|
|
10430
|
+
static convertToAdg(rawRule) {
|
|
10431
|
+
const conversionResult = RuleConverter.convertToAdg(RuleParser.parse(rawRule));
|
|
10432
|
+
// If the rule was not converted, return the original rule text
|
|
10433
|
+
if (!conversionResult.isConverted) {
|
|
10434
|
+
return createConversionResult([rawRule], false);
|
|
10435
|
+
}
|
|
10436
|
+
// Otherwise, serialize the converted rule nodes
|
|
10437
|
+
return createConversionResult(conversionResult.result.map(RuleParser.generate), true);
|
|
9830
10438
|
}
|
|
9831
10439
|
}
|
|
9832
10440
|
|
|
@@ -9908,7 +10516,7 @@ class LogicalExpressionUtils {
|
|
|
9908
10516
|
}
|
|
9909
10517
|
}
|
|
9910
10518
|
|
|
9911
|
-
const version$1 = "1.1.
|
|
10519
|
+
const version$1 = "1.1.6";
|
|
9912
10520
|
|
|
9913
10521
|
/**
|
|
9914
10522
|
* @file AGTree version
|
|
@@ -9919,4 +10527,4 @@ const version$1 = "1.1.5";
|
|
|
9919
10527
|
// with wrong relative path to `package.json`. So we need this little "hack"
|
|
9920
10528
|
const version = version$1;
|
|
9921
10529
|
|
|
9922
|
-
export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, HINT_MARKER, HintCommentRuleParser, HintParser, IF, INCLUDE, LogicalExpressionParser, LogicalExpressionUtils, METADATA_HEADERS, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, StealthOptionListParser, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };
|
|
10530
|
+
export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, HINT_MARKER, HintCommentRuleParser, HintParser, IF, INCLUDE, LogicalExpressionParser, LogicalExpressionUtils, METADATA_HEADERS, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RawFilterListConverter, RawRuleConverter, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, StealthOptionListParser, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };
|