@adguard/agtree 1.1.2 → 1.1.4

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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v1.1.2 (build date: Mon, 14 Aug 2023 13:52:06 GMT)
2
+ * AGTree v1.1.4 (build date: Wed, 30 Aug 2023 10:02:46 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
@@ -11,8 +11,9 @@ import { walk, parse, toPlainObject, find, generate, fromPlainObject, List } fro
11
11
  import * as ecssTree from '@adguard/ecss-tree';
12
12
  export { ecssTree as ECSSTree };
13
13
  import cloneDeep from 'clone-deep';
14
- import { redirects } from '@adguard/scriptlets';
14
+ import XRegExp from 'xregexp';
15
15
  import { parse as parse$1 } from 'tldts';
16
+ import { redirects } from '@adguard/scriptlets';
16
17
 
17
18
  /**
18
19
  * @file Possible adblock syntaxes are listed here.
@@ -96,6 +97,7 @@ const CR = '\r';
96
97
  const FF = '\f';
97
98
  const LF = '\n';
98
99
  const CRLF = CR + LF;
100
+ const NEWLINE = LF;
99
101
  const DOUBLE_QUOTE = '"';
100
102
  const SINGLE_QUOTE = '\'';
101
103
  // Brackets
@@ -107,21 +109,157 @@ const OPEN_CURLY_BRACKET = '{';
107
109
  const CLOSE_CURLY_BRACKET = '}';
108
110
  // Letters
109
111
  const SMALL_LETTER_A = 'a';
112
+ const SMALL_LETTER_B = 'b';
113
+ const SMALL_LETTER_C = 'c';
114
+ const SMALL_LETTER_D = 'd';
115
+ const SMALL_LETTER_E = 'e';
116
+ const SMALL_LETTER_F = 'f';
117
+ const SMALL_LETTER_G = 'g';
118
+ const SMALL_LETTER_H = 'h';
119
+ const SMALL_LETTER_I = 'i';
120
+ const SMALL_LETTER_J = 'j';
121
+ const SMALL_LETTER_K = 'k';
122
+ const SMALL_LETTER_L = 'l';
123
+ const SMALL_LETTER_M = 'm';
124
+ const SMALL_LETTER_N = 'n';
125
+ const SMALL_LETTER_O = 'o';
126
+ const SMALL_LETTER_P = 'p';
127
+ const SMALL_LETTER_Q = 'q';
128
+ const SMALL_LETTER_R = 'r';
129
+ const SMALL_LETTER_S = 's';
130
+ const SMALL_LETTER_T = 't';
131
+ const SMALL_LETTER_U = 'u';
132
+ const SMALL_LETTER_V = 'v';
133
+ const SMALL_LETTER_W = 'w';
134
+ const SMALL_LETTER_X = 'x';
135
+ const SMALL_LETTER_Y = 'y';
110
136
  const SMALL_LETTER_Z = 'z';
137
+ /**
138
+ * Set of all small letters.
139
+ */
140
+ const SMALL_LETTERS = new Set([
141
+ SMALL_LETTER_A,
142
+ SMALL_LETTER_B,
143
+ SMALL_LETTER_C,
144
+ SMALL_LETTER_D,
145
+ SMALL_LETTER_E,
146
+ SMALL_LETTER_F,
147
+ SMALL_LETTER_G,
148
+ SMALL_LETTER_H,
149
+ SMALL_LETTER_I,
150
+ SMALL_LETTER_J,
151
+ SMALL_LETTER_K,
152
+ SMALL_LETTER_L,
153
+ SMALL_LETTER_M,
154
+ SMALL_LETTER_N,
155
+ SMALL_LETTER_O,
156
+ SMALL_LETTER_P,
157
+ SMALL_LETTER_Q,
158
+ SMALL_LETTER_R,
159
+ SMALL_LETTER_S,
160
+ SMALL_LETTER_T,
161
+ SMALL_LETTER_U,
162
+ SMALL_LETTER_V,
163
+ SMALL_LETTER_W,
164
+ SMALL_LETTER_X,
165
+ SMALL_LETTER_Y,
166
+ SMALL_LETTER_Z,
167
+ ]);
111
168
  // Capital letters
112
169
  const CAPITAL_LETTER_A = 'A';
170
+ const CAPITAL_LETTER_B = 'B';
171
+ const CAPITAL_LETTER_C = 'C';
172
+ const CAPITAL_LETTER_D = 'D';
173
+ const CAPITAL_LETTER_E = 'E';
174
+ const CAPITAL_LETTER_F = 'F';
175
+ const CAPITAL_LETTER_G = 'G';
176
+ const CAPITAL_LETTER_H = 'H';
177
+ const CAPITAL_LETTER_I = 'I';
178
+ const CAPITAL_LETTER_J = 'J';
179
+ const CAPITAL_LETTER_K = 'K';
180
+ const CAPITAL_LETTER_L = 'L';
181
+ const CAPITAL_LETTER_M = 'M';
182
+ const CAPITAL_LETTER_N = 'N';
183
+ const CAPITAL_LETTER_O = 'O';
184
+ const CAPITAL_LETTER_P = 'P';
185
+ const CAPITAL_LETTER_Q = 'Q';
186
+ const CAPITAL_LETTER_R = 'R';
187
+ const CAPITAL_LETTER_S = 'S';
188
+ const CAPITAL_LETTER_T = 'T';
189
+ const CAPITAL_LETTER_U = 'U';
190
+ const CAPITAL_LETTER_V = 'V';
191
+ const CAPITAL_LETTER_W = 'W';
192
+ const CAPITAL_LETTER_X = 'X';
193
+ const CAPITAL_LETTER_Y = 'Y';
113
194
  const CAPITAL_LETTER_Z = 'Z';
195
+ /**
196
+ * Set of all capital letters.
197
+ */
198
+ const CAPITAL_LETTERS = new Set([
199
+ CAPITAL_LETTER_A,
200
+ CAPITAL_LETTER_B,
201
+ CAPITAL_LETTER_C,
202
+ CAPITAL_LETTER_D,
203
+ CAPITAL_LETTER_E,
204
+ CAPITAL_LETTER_F,
205
+ CAPITAL_LETTER_G,
206
+ CAPITAL_LETTER_H,
207
+ CAPITAL_LETTER_I,
208
+ CAPITAL_LETTER_J,
209
+ CAPITAL_LETTER_K,
210
+ CAPITAL_LETTER_L,
211
+ CAPITAL_LETTER_M,
212
+ CAPITAL_LETTER_N,
213
+ CAPITAL_LETTER_O,
214
+ CAPITAL_LETTER_P,
215
+ CAPITAL_LETTER_Q,
216
+ CAPITAL_LETTER_R,
217
+ CAPITAL_LETTER_S,
218
+ CAPITAL_LETTER_T,
219
+ CAPITAL_LETTER_U,
220
+ CAPITAL_LETTER_V,
221
+ CAPITAL_LETTER_W,
222
+ CAPITAL_LETTER_X,
223
+ CAPITAL_LETTER_Y,
224
+ CAPITAL_LETTER_Z,
225
+ ]);
114
226
  // Numbers as strings
115
227
  const NUMBER_0 = '0';
228
+ const NUMBER_1 = '1';
229
+ const NUMBER_2 = '2';
230
+ const NUMBER_3 = '3';
231
+ const NUMBER_4 = '4';
232
+ const NUMBER_5 = '5';
233
+ const NUMBER_6 = '6';
234
+ const NUMBER_7 = '7';
235
+ const NUMBER_8 = '8';
116
236
  const NUMBER_9 = '9';
237
+ /**
238
+ * Set of all numbers as strings.
239
+ */
240
+ const NUMBERS = new Set([
241
+ NUMBER_0,
242
+ NUMBER_1,
243
+ NUMBER_2,
244
+ NUMBER_3,
245
+ NUMBER_4,
246
+ NUMBER_5,
247
+ NUMBER_6,
248
+ NUMBER_7,
249
+ NUMBER_8,
250
+ NUMBER_9,
251
+ ]);
117
252
  const REGEX_MARKER = '/';
118
253
  const ADG_SCRIPTLET_MASK = '//scriptlet';
119
254
  const UBO_SCRIPTLET_MASK = 'js';
120
255
  // Modifiers are separated by ",". For example: "script,domain=example.com"
121
256
  const MODIFIERS_SEPARATOR = ',';
122
- const MODIFIER_EXCEPTION_MARKER = '~';
123
257
  const MODIFIER_ASSIGN_OPERATOR = '=';
124
- const DOMAIN_EXCEPTION_MARKER = '~';
258
+ const NEGATION_MARKER = '~';
259
+ /**
260
+ * The wildcard symbol — `*`.
261
+ */
262
+ const WILDCARD$1 = ASTERISK;
125
263
  /**
126
264
  * Classic domain separator.
127
265
  *
@@ -131,9 +269,9 @@ const DOMAIN_EXCEPTION_MARKER = '~';
131
269
  * example.com,~example.org##.ads
132
270
  * ```
133
271
  */
134
- const CLASSIC_DOMAIN_SEPARATOR = ',';
272
+ const COMMA_DOMAIN_LIST_SEPARATOR = ',';
135
273
  /**
136
- * Modifier domain separator.
274
+ * Modifier separator for $app, $denyallow, $domain, $method.
137
275
  *
138
276
  * @example
139
277
  * ```adblock
@@ -141,8 +279,7 @@ const CLASSIC_DOMAIN_SEPARATOR = ',';
141
279
  * ads.js^$script,domains=example.com|~example.org
142
280
  * ```
143
281
  */
144
- const MODIFIER_DOMAIN_SEPARATOR = '|';
145
- const DOMAIN_LIST_TYPE = 'DomainList';
282
+ const PIPE_MODIFIER_SEPARATOR = '|';
146
283
  const CSS_IMPORTANT = '!important';
147
284
  const HINT_MARKER = '!+';
148
285
  const HINT_MARKER_LEN = HINT_MARKER.length;
@@ -775,6 +912,27 @@ var RuleCategory;
775
912
  */
776
913
  RuleCategory["Network"] = "Network";
777
914
  })(RuleCategory || (RuleCategory = {}));
915
+ /**
916
+ * Represents similar types of modifiers values
917
+ * which may be separated by a comma `,` (only for DomainList) or a pipe `|`.
918
+ */
919
+ var ListNodeType;
920
+ (function (ListNodeType) {
921
+ ListNodeType["AppList"] = "AppList";
922
+ ListNodeType["DomainList"] = "DomainList";
923
+ ListNodeType["MethodList"] = "MethodList";
924
+ ListNodeType["StealthOptionList"] = "StealthOptionList";
925
+ })(ListNodeType || (ListNodeType = {}));
926
+ /**
927
+ * Represents child items for {@link ListNodeType}.
928
+ */
929
+ var ListItemNodeType;
930
+ (function (ListItemNodeType) {
931
+ ListItemNodeType["App"] = "App";
932
+ ListItemNodeType["Domain"] = "Domain";
933
+ ListItemNodeType["Method"] = "Method";
934
+ ListItemNodeType["StealthOption"] = "StealthOption";
935
+ })(ListItemNodeType || (ListItemNodeType = {}));
778
936
  /**
779
937
  * Represents possible comment types.
780
938
  */
@@ -2526,6 +2684,87 @@ class CommentRuleParser {
2526
2684
  }
2527
2685
  }
2528
2686
 
2687
+ /**
2688
+ * Prefixes for error messages which are used for parsing of value lists.
2689
+ */
2690
+ const LIST_PARSE_ERROR_PREFIX = {
2691
+ EMPTY_ITEM: 'Empty value specified in the list',
2692
+ NO_MULTIPLE_NEGATION: 'Exception marker cannot be followed by another exception marker',
2693
+ NO_SEPARATOR_AFTER_NEGATION: 'Exception marker cannot be followed by a separator',
2694
+ NO_SEPARATOR_AT_THE_END: 'Value list cannot end with a separator',
2695
+ NO_WHITESPACE_AFTER_NEGATION: 'Exception marker cannot be followed by whitespace',
2696
+ };
2697
+ /**
2698
+ * Parses a `raw` modifier value which may be represented as a list of items separated by `separator`.
2699
+ * Needed for $app, $denyallow, $domain, $method.
2700
+ *
2701
+ * @param raw Raw modifier value.
2702
+ * @param separator Separator character.
2703
+ * @param loc Location of the modifier value.
2704
+ *
2705
+ * @returns List AST children — {@link App} | {@link Domain} | {@link Method} —
2706
+ * but with no `type` specified (see {@link ListItemNoType}).
2707
+ * @throws An {@link AdblockSyntaxError} if the list is syntactically invalid
2708
+ *
2709
+ * @example
2710
+ * - parses an app list — `com.example.app|Example.exe`
2711
+ * - parses a domain list — `example.com,example.org,~example.org` or `example.com|~example.org`
2712
+ * - parses a method list — `~post|~put`
2713
+ */
2714
+ const parseListItems = (raw, separator, loc = defaultLocation) => {
2715
+ const rawListItems = [];
2716
+ // If the last character is a separator, then the list item is invalid
2717
+ // and no need to continue parsing
2718
+ const realEndIndex = StringUtils.skipWSBack(raw);
2719
+ if (raw[realEndIndex] === separator) {
2720
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_SEPARATOR_AT_THE_END, locRange(loc, realEndIndex, realEndIndex + 1));
2721
+ }
2722
+ let offset = 0;
2723
+ // Skip whitespace before the list
2724
+ offset = StringUtils.skipWS(raw, offset);
2725
+ // Split list items by unescaped separators
2726
+ while (offset < raw.length) {
2727
+ // Skip whitespace before the list item
2728
+ offset = StringUtils.skipWS(raw, offset);
2729
+ let itemStart = offset;
2730
+ // Find the index of the first unescaped separator character
2731
+ const separatorStartIndex = StringUtils.findNextUnescapedCharacter(raw, separator, offset);
2732
+ const itemEnd = separatorStartIndex === -1
2733
+ ? StringUtils.skipWSBack(raw) + 1
2734
+ : StringUtils.skipWSBack(raw, separatorStartIndex - 1) + 1;
2735
+ const exception = raw[itemStart] === NEGATION_MARKER;
2736
+ // Skip the exception marker
2737
+ if (exception) {
2738
+ itemStart += 1;
2739
+ // Exception marker cannot be followed by another exception marker
2740
+ if (raw[itemStart] === NEGATION_MARKER) {
2741
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_MULTIPLE_NEGATION, locRange(loc, itemStart, itemStart + 1));
2742
+ }
2743
+ // Exception marker cannot be followed by a separator
2744
+ if (raw[itemStart] === separator) {
2745
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_SEPARATOR_AFTER_NEGATION, locRange(loc, itemStart, itemStart + 1));
2746
+ }
2747
+ // Exception marker cannot be followed by whitespace
2748
+ if (StringUtils.isWhitespace(raw[itemStart])) {
2749
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_WHITESPACE_AFTER_NEGATION, locRange(loc, itemStart, itemStart + 1));
2750
+ }
2751
+ }
2752
+ // List item can't be empty
2753
+ if (itemStart === itemEnd) {
2754
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.EMPTY_ITEM, locRange(loc, itemStart, raw.length));
2755
+ }
2756
+ // Collect list item
2757
+ rawListItems.push({
2758
+ loc: locRange(loc, itemStart, itemEnd),
2759
+ value: raw.substring(itemStart, itemEnd),
2760
+ exception,
2761
+ });
2762
+ // Increment the offset to the next list item (or the end of the string)
2763
+ offset = separatorStartIndex === -1 ? raw.length : separatorStartIndex + 1;
2764
+ }
2765
+ return rawListItems;
2766
+ };
2767
+
2529
2768
  /**
2530
2769
  * `DomainListParser` is responsible for parsing a domain list.
2531
2770
  *
@@ -2539,83 +2778,39 @@ class DomainListParser {
2539
2778
  /**
2540
2779
  * Parses a domain list, eg. `example.com,example.org,~example.org`
2541
2780
  *
2542
- * @param raw Raw domain list
2543
- * @param separator Separator character
2544
- * @param loc Location of the domain list
2545
- * @returns Domain list AST
2546
- * @throws If the domain list is syntactically invalid
2781
+ * @param raw Raw domain list.
2782
+ * @param separator Separator character.
2783
+ * @param loc Location of the domain list in the rule. If not set, the default location is used.
2784
+ *
2785
+ * @returns Domain list AST.
2786
+ * @throws An {@link AdblockSyntaxError} if the domain list is syntactically invalid.
2547
2787
  */
2548
- static parse(raw, separator = CLASSIC_DOMAIN_SEPARATOR, loc = defaultLocation) {
2549
- const result = {
2550
- type: DOMAIN_LIST_TYPE,
2788
+ static parse(raw, separator = COMMA_DOMAIN_LIST_SEPARATOR, loc = defaultLocation) {
2789
+ const rawItems = parseListItems(raw, separator, loc);
2790
+ const children = rawItems.map((rawListItem) => ({
2791
+ ...rawListItem,
2792
+ type: ListItemNodeType.Domain,
2793
+ }));
2794
+ return {
2795
+ type: ListNodeType.DomainList,
2551
2796
  loc: locRange(loc, 0, raw.length),
2552
2797
  separator,
2553
- children: [],
2798
+ children,
2554
2799
  };
2555
- // If the last character is a separator, then the domain list is invalid
2556
- // and no need to continue parsing
2557
- const realEndIndex = StringUtils.skipWSBack(raw);
2558
- if (raw[realEndIndex] === separator) {
2559
- throw new AdblockSyntaxError('Domain list cannot end with a separator', locRange(loc, realEndIndex, realEndIndex + 1));
2560
- }
2561
- let offset = 0;
2562
- // Skip whitespace before the domain list
2563
- offset = StringUtils.skipWS(raw, offset);
2564
- // Split domains by unescaped separators
2565
- while (offset < raw.length) {
2566
- // Skip whitespace before the domain
2567
- offset = StringUtils.skipWS(raw, offset);
2568
- let domainStart = offset;
2569
- // Find the index of the first unescaped separator character
2570
- const separatorStartIndex = StringUtils.findNextUnescapedCharacter(raw, separator, offset);
2571
- const domainEnd = separatorStartIndex === -1
2572
- ? StringUtils.skipWSBack(raw) + 1
2573
- : StringUtils.skipWSBack(raw, separatorStartIndex - 1) + 1;
2574
- const exception = raw[domainStart] === DOMAIN_EXCEPTION_MARKER;
2575
- // Skip the exception marker
2576
- if (exception) {
2577
- domainStart += 1;
2578
- // Exception marker cannot be followed by another exception marker
2579
- if (raw[domainStart] === DOMAIN_EXCEPTION_MARKER) {
2580
- throw new AdblockSyntaxError('Exception marker cannot be followed by another exception marker', locRange(loc, domainStart, domainStart + 1));
2581
- }
2582
- // Exception marker cannot be followed by a separator
2583
- if (raw[domainStart] === separator) {
2584
- throw new AdblockSyntaxError('Exception marker cannot be followed by a separator', locRange(loc, domainStart, domainStart + 1));
2585
- }
2586
- // Exception marker cannot be followed by whitespace
2587
- if (StringUtils.isWhitespace(raw[domainStart])) {
2588
- throw new AdblockSyntaxError('Exception marker cannot be followed by whitespace', locRange(loc, domainStart, domainStart + 1));
2589
- }
2590
- }
2591
- // Domain can't be empty
2592
- if (domainStart === domainEnd) {
2593
- throw new AdblockSyntaxError('Empty domain specified', locRange(loc, domainStart, raw.length));
2594
- }
2595
- // Add the domain to the result
2596
- result.children.push({
2597
- type: 'Domain',
2598
- loc: locRange(loc, domainStart, domainEnd),
2599
- value: raw.substring(domainStart, domainEnd),
2600
- exception,
2601
- });
2602
- // Increment the offset to the next domain (or the end of the string)
2603
- offset = separatorStartIndex === -1 ? raw.length : separatorStartIndex + 1;
2604
- }
2605
- return result;
2606
2800
  }
2607
2801
  /**
2608
2802
  * Converts a domain list AST to a string.
2609
2803
  *
2610
- * @param ast Domain list AST
2611
- * @returns Raw string
2804
+ * @param ast Domain list AST.
2805
+ *
2806
+ * @returns Raw string.
2612
2807
  */
2613
2808
  static generate(ast) {
2614
2809
  const result = ast.children
2615
2810
  .map(({ value, exception }) => {
2616
2811
  let subresult = EMPTY;
2617
2812
  if (exception) {
2618
- subresult += DOMAIN_EXCEPTION_MARKER;
2813
+ subresult += NEGATION_MARKER;
2619
2814
  }
2620
2815
  subresult += value.trim();
2621
2816
  return subresult;
@@ -2649,8 +2844,8 @@ class ModifierParser {
2649
2844
  const modifierStart = offset;
2650
2845
  // Check if the modifier is an exception
2651
2846
  let exception = false;
2652
- if (raw[offset] === MODIFIER_EXCEPTION_MARKER) {
2653
- offset += MODIFIER_EXCEPTION_MARKER.length;
2847
+ if (raw[offset] === NEGATION_MARKER) {
2848
+ offset += NEGATION_MARKER.length;
2654
2849
  exception = true;
2655
2850
  }
2656
2851
  // Skip whitespace after the exception marker (if any)
@@ -2714,7 +2909,7 @@ class ModifierParser {
2714
2909
  static generate(modifier) {
2715
2910
  let result = EMPTY;
2716
2911
  if (modifier.exception) {
2717
- result += MODIFIER_EXCEPTION_MARKER;
2912
+ result += NEGATION_MARKER;
2718
2913
  }
2719
2914
  result += modifier.modifier.value;
2720
2915
  if (modifier.value !== undefined) {
@@ -5562,6 +5757,102 @@ class RuleParser {
5562
5757
  }
5563
5758
  }
5564
5759
 
5760
+ /**
5761
+ * `AppListParser` is responsible for parsing an app list.
5762
+ *
5763
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier}
5764
+ */
5765
+ class AppListParser {
5766
+ /**
5767
+ * Parses an app list which items are separated by `|`,
5768
+ * e.g. `Example.exe|com.example.osx`.
5769
+ *
5770
+ * @param raw Raw app list
5771
+ * @param loc Location of the app list in the rule. If not set, the default location is used.
5772
+ *
5773
+ * @returns App list AST.
5774
+ * @throws An {@link AdblockSyntaxError} if the app list is syntactically invalid.
5775
+ */
5776
+ static parse(raw, loc = defaultLocation) {
5777
+ const separator = PIPE_MODIFIER_SEPARATOR;
5778
+ const rawItems = parseListItems(raw, separator, loc);
5779
+ const children = rawItems.map((rawListItem) => ({
5780
+ ...rawListItem,
5781
+ type: ListItemNodeType.App,
5782
+ }));
5783
+ return {
5784
+ type: ListNodeType.AppList,
5785
+ loc: locRange(loc, 0, raw.length),
5786
+ separator,
5787
+ children,
5788
+ };
5789
+ }
5790
+ }
5791
+
5792
+ /**
5793
+ * `MethodListParser` is responsible for parsing a method list.
5794
+ *
5795
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier}
5796
+ */
5797
+ class MethodListParser {
5798
+ /**
5799
+ * Parses a method list which items are separated by `|`,
5800
+ * e.g. `get|post|put`.
5801
+ *
5802
+ * @param raw Raw method list
5803
+ * @param loc Location of the method list in the rule. If not set, the default location is used.
5804
+ *
5805
+ * @returns Method list AST.
5806
+ * @throws An {@link AdblockSyntaxError} if the method list is syntactically invalid.
5807
+ */
5808
+ static parse(raw, loc = defaultLocation) {
5809
+ const separator = PIPE_MODIFIER_SEPARATOR;
5810
+ const rawItems = parseListItems(raw, separator, loc);
5811
+ const children = rawItems.map((rawListItem) => ({
5812
+ ...rawListItem,
5813
+ type: ListItemNodeType.Method,
5814
+ }));
5815
+ return {
5816
+ type: ListNodeType.MethodList,
5817
+ loc: locRange(loc, 0, raw.length),
5818
+ separator,
5819
+ children,
5820
+ };
5821
+ }
5822
+ }
5823
+
5824
+ /**
5825
+ * `StealthOptionListParser` is responsible for parsing a list of stealth options.
5826
+ *
5827
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier}
5828
+ */
5829
+ class StealthOptionListParser {
5830
+ /**
5831
+ * Parses a stealth option list which items are separated by `|`,
5832
+ * e.g. `dpi|ip`.
5833
+ *
5834
+ * @param raw Raw list of stealth options.
5835
+ * @param loc Location of the stealth option list in the rule. If not set, the default location is used.
5836
+ *
5837
+ * @returns Stealth option list AST.
5838
+ * @throws An {@link AdblockSyntaxError} if the stealth option list is syntactically invalid.
5839
+ */
5840
+ static parse(raw, loc = defaultLocation) {
5841
+ const separator = PIPE_MODIFIER_SEPARATOR;
5842
+ const rawItems = parseListItems(raw, separator, loc);
5843
+ const children = rawItems.map((rawListItem) => ({
5844
+ ...rawListItem,
5845
+ type: ListItemNodeType.StealthOption,
5846
+ }));
5847
+ return {
5848
+ type: ListNodeType.StealthOptionList,
5849
+ loc: locRange(loc, 0, raw.length),
5850
+ separator,
5851
+ children,
5852
+ };
5853
+ }
5854
+ }
5855
+
5565
5856
  /**
5566
5857
  * `FilterListParser` is responsible for parsing a whole adblock filter list (list of rules).
5567
5858
  * It is a wrapper around `RuleParser` which parses each line separately.
@@ -5772,7 +6063,8 @@ var data$S = { adg_os_any:{ name:"app",
5772
6063
  description:"The `$app` modifier lets you narrow the rule coverage down to a specific application or a list of applications.\nThe modifier's behavior and syntax perfectly match the corresponding basic rules `$app` modifier.",
5773
6064
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier",
5774
6065
  assignable:true,
5775
- negatable:false } };
6066
+ negatable:false,
6067
+ value_format:"pipe_separated_apps" } };
5776
6068
 
5777
6069
  var data$R = { adg_os_any:{ name:"badfilter",
5778
6070
  description:"The rules with the `$badfilter` modifier disable other basic rules to which they refer. It means that\nthe text of the disabled rule should match the text of the `$badfilter` rule (without the `$badfilter` modifier).",
@@ -5833,7 +6125,7 @@ var data$N = { adg_os_any:{ name:"csp",
5833
6125
  assignable:true,
5834
6126
  negatable:false,
5835
6127
  value_optional:true,
5836
- value_format:"/[^,$]+/" },
6128
+ value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
5837
6129
  adg_ext_any:{ name:"csp",
5838
6130
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
5839
6131
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
@@ -5845,7 +6137,7 @@ var data$N = { adg_os_any:{ name:"csp",
5845
6137
  assignable:true,
5846
6138
  negatable:false,
5847
6139
  value_optional:true,
5848
- value_format:"/[^,$]+/" },
6140
+ value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
5849
6141
  abp_ext_any:{ name:"csp",
5850
6142
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
5851
6143
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#content-security-policies",
@@ -5855,7 +6147,7 @@ var data$N = { adg_os_any:{ name:"csp",
5855
6147
  assignable:true,
5856
6148
  negatable:false,
5857
6149
  value_optional:true,
5858
- value_format:"/[^,$]+/" },
6150
+ value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
5859
6151
  ubo_ext_any:{ name:"csp",
5860
6152
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
5861
6153
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#csp",
@@ -5867,7 +6159,7 @@ var data$N = { adg_os_any:{ name:"csp",
5867
6159
  assignable:true,
5868
6160
  negatable:false,
5869
6161
  value_optional:true,
5870
- value_format:"/[^,$]+/" } };
6162
+ value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" } };
5871
6163
 
5872
6164
  var data$M = { adg_os_any:{ name:"denyallow",
5873
6165
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
@@ -5875,35 +6167,35 @@ var data$M = { adg_os_any:{ name:"denyallow",
5875
6167
  conflicts:[ "to" ],
5876
6168
  assignable:true,
5877
6169
  negatable:false,
5878
- value_format:"pipe_separated_domains" },
6170
+ value_format:"pipe_separated_denyallow_domains" },
5879
6171
  adg_ext_any:{ name:"denyallow",
5880
6172
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
5881
6173
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5882
6174
  conflicts:[ "to" ],
5883
6175
  assignable:true,
5884
6176
  negatable:false,
5885
- value_format:"pipe_separated_domains" },
6177
+ value_format:"pipe_separated_denyallow_domains" },
5886
6178
  adg_cb_ios:{ name:"denyallow",
5887
6179
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
5888
6180
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5889
6181
  conflicts:[ "to" ],
5890
6182
  assignable:true,
5891
6183
  negatable:false,
5892
- value_format:"pipe_separated_domains" },
6184
+ value_format:"pipe_separated_denyallow_domains" },
5893
6185
  adg_cb_safari:{ name:"denyallow",
5894
6186
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
5895
6187
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5896
6188
  conflicts:[ "to" ],
5897
6189
  assignable:true,
5898
6190
  negatable:false,
5899
- value_format:"pipe_separated_domains" },
6191
+ value_format:"pipe_separated_denyallow_domains" },
5900
6192
  ubo_ext_any:{ name:"denyallow",
5901
6193
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
5902
6194
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#denyallow",
5903
6195
  conflicts:[ "to" ],
5904
6196
  assignable:true,
5905
6197
  negatable:false,
5906
- value_format:"pipe_separated_domains" } };
6198
+ value_format:"pipe_separated_denyallow_domains" } };
5907
6199
 
5908
6200
  var data$L = { adg_os_any:{ name:"document",
5909
6201
  description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
@@ -6119,17 +6411,17 @@ var data$C = { adg_os_any:{ name:"header",
6119
6411
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6120
6412
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6121
6413
  assignable:true,
6122
- value_format:"/^[A-z0-9-]+(:.+|)$/" },
6414
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" },
6123
6415
  adg_ext_any:{ name:"header",
6124
6416
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6125
6417
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6126
6418
  assignable:true,
6127
- value_format:"/^[A-z0-9-]+(:.+|)$/" },
6419
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" },
6128
6420
  ubo_ext_any:{ name:"header",
6129
6421
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6130
6422
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#header",
6131
6423
  assignable:true,
6132
- value_format:"/^[A-z0-9-]+(:.+|)$/" } };
6424
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" } };
6133
6425
 
6134
6426
  var data$B = { adg_os_any:{ name:"hls",
6135
6427
  description:"The `$hls` rules modify the response of a matching request.\nThey are intended as a convenient way to remove segments from HLS playlists (RFC 8216).",
@@ -6144,7 +6436,8 @@ var data$B = { adg_os_any:{ name:"hls",
6144
6436
  inverse_conflicts:true,
6145
6437
  assignable:true,
6146
6438
  negatable:false,
6147
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6439
+ value_optional:true,
6440
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n # TODO: improve regexp pattern to invalidate unescaped `/`, `$`, and `,`\n \\/.+\\/\n # options\n ([ti]*)?\n )" } };
6148
6441
 
6149
6442
  var data$A = { adg_any:{ name:"image",
6150
6443
  description:"The rule corresponds to images requests.",
@@ -6249,7 +6542,8 @@ var data$v = { adg_os_any:{ name:"jsonprune",
6249
6542
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
6250
6543
  assignable:true,
6251
6544
  negatable:false,
6252
- value_format:"/^\\\\\\$\\.(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6545
+ value_optional:true,
6546
+ value_format:"(?xi)\n ^\n # the expression always starts with a dollar sign (for root)\n # which should be escaped\n \\\\\n \\$\n \\.?\n # TODO: improve the expression to invalidate unescaped `$` and `,`\n .+\n $" } };
6253
6547
 
6254
6548
  var data$u = { adg_any:{ name:"match-case",
6255
6549
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
@@ -6276,19 +6570,19 @@ var data$s = { adg_os_any:{ name:"method",
6276
6570
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6277
6571
  negatable:false,
6278
6572
  assignable:true,
6279
- value_format:"(?xi)\n ^(?!\\|)(?:\\b(\n get|\n head|\n post|\n put|\n delete|\n connect|\n options|\n trace|\n patch\n )\\b|(\\|?|~?))+(?<!\\|)$" },
6573
+ value_format:"pipe_separated_methods" },
6280
6574
  adg_ext_any:{ name:"method",
6281
6575
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6282
6576
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6283
6577
  negatable:false,
6284
6578
  assignable:true,
6285
- value_format:"(?xi)\n ^(?!\\|)(?:\\b(\n get|\n head|\n post|\n put|\n delete|\n connect|\n options|\n trace|\n patch\n )\\b|(\\|?|~?))+(?<!\\|)$" },
6579
+ value_format:"pipe_separated_methods" },
6286
6580
  ubo_ext_any:{ name:"method",
6287
6581
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6288
6582
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#method",
6289
6583
  negatable:false,
6290
6584
  assignable:true,
6291
- value_format:"(?xi)\n ^(?!\\|)(?:\\b(\n get|\n head|\n post|\n put|\n delete|\n connect|\n options|\n trace|\n patch\n )\\b|(\\|?|~?))+(?<!\\|)$" } };
6585
+ value_format:"pipe_separated_methods" } };
6292
6586
 
6293
6587
  var data$r = { adg_os_any:{ name:"mp4",
6294
6588
  description:"As a response to blocked request AdGuard returns a short video placeholder.\nRules with `$mp4` are still supported and being converted into `$redirect=noopmp4-1s` now\nbut the support shall be removed in the future.",
@@ -6372,7 +6666,7 @@ var data$l = { adg_os_any:{ name:"permissions",
6372
6666
  inverse_conflicts:true,
6373
6667
  assignable:true,
6374
6668
  negatable:false,
6375
- value_format:"(?x)\n^(?:(\n accelerometer|\n ambient-light-sensor|\n autoplay|\n battery|\n camera|\n display-capture|\n document-domain|\n encrypted-media|\n execution-while-not-rendered|\n execution-while-out-of-viewport|\n fullscreen|\n gamepad|\n geolocation|\n gyroscope|\n hid|\n identity-credentials-get|\n idle-detection|\n local-fonts|\n magnetometer|\n microphone|\n midi|\n payment|\n picture-in-picture|\n publickey-credentials-create|\n publickey-credentials-get|\n screen-wake-lock|\n serial|\n speaker-selection|\n storage-access|\n usb|\n web-share|\n xr-spatial-tracking\n )=\\(\\)(\\\\, )?)+(?<!,)$" } };
6669
+ value_format:"(?x)\n ^\n (\n ?:(\n accelerometer|\n ambient-light-sensor|\n autoplay|\n battery|\n camera|\n display-capture|\n document-domain|\n encrypted-media|\n execution-while-not-rendered|\n execution-while-out-of-viewport|\n fullscreen|\n gamepad|\n geolocation|\n gyroscope|\n hid|\n identity-credentials-get|\n idle-detection|\n local-fonts|\n magnetometer|\n microphone|\n midi|\n payment|\n picture-in-picture|\n publickey-credentials-create|\n publickey-credentials-get|\n screen-wake-lock|\n serial|\n speaker-selection|\n storage-access|\n usb|\n web-share|\n xr-spatial-tracking\n )\n =\\(\\)\n # optional escaped comma for multiple permissions\n (\\\\,(\\s+)?)?\n )+\n $" } };
6376
6670
 
6377
6671
  var data$k = { adg_any:{ name:"ping",
6378
6672
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
@@ -6500,14 +6794,14 @@ var data$g = { adg_os_any:{ name:"redirect",
6500
6794
  assignable:true,
6501
6795
  negatable:false,
6502
6796
  value_optional:true,
6503
- value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6797
+ value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n googletagmanager-gtm|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6504
6798
  adg_ext_any:{ name:"redirect",
6505
6799
  description:"Used to redirect web requests to a local \"resource\".",
6506
6800
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6507
6801
  assignable:true,
6508
6802
  negatable:false,
6509
6803
  value_optional:true,
6510
- value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6804
+ value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n googletagmanager-gtm|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6511
6805
  ubo_ext_any:{ name:"redirect",
6512
6806
  description:"Used to redirect web requests to a local \"resource\".",
6513
6807
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect",
@@ -6583,20 +6877,20 @@ var data$e = { adg_os_any:{ name:"removeparam",
6583
6877
  assignable:true,
6584
6878
  negatable:false,
6585
6879
  value_optional:true,
6586
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6880
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" },
6587
6881
  adg_ext_any:{ name:"removeparam",
6588
6882
  description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6589
6883
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
6590
6884
  assignable:true,
6591
6885
  negatable:false,
6592
6886
  value_optional:true,
6593
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6887
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" },
6594
6888
  ubo_ext_any:{ name:"removeparam",
6595
6889
  description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6596
6890
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#removeparam",
6597
6891
  assignable:true,
6598
6892
  negatable:false,
6599
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6893
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" } };
6600
6894
 
6601
6895
  var data$d = { adg_os_any:{ name:"replace",
6602
6896
  description:"This modifier completely changes the rule behavior.\nIf it is applied, the rule will not block the request. The response is going to be modified instead.",
@@ -6616,7 +6910,7 @@ var data$d = { adg_os_any:{ name:"replace",
6616
6910
  inverse_conflicts:true,
6617
6911
  assignable:true,
6618
6912
  negatable:false,
6619
- value_format:"/^\\/.+\\/.*\\/$/" },
6913
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" },
6620
6914
  adg_ext_firefox:{ name:"replace",
6621
6915
  description:"This modifier completely changes the rule behavior.\nIf it is applied, the rule will not block the request. The response is going to be modified instead.",
6622
6916
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
@@ -6634,7 +6928,7 @@ var data$d = { adg_os_any:{ name:"replace",
6634
6928
  inverse_conflicts:true,
6635
6929
  assignable:true,
6636
6930
  negatable:false,
6637
- value_format:"/^\\/.+\\/.*\\/$/" } };
6931
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" } };
6638
6932
 
6639
6933
  var data$c = { adg_any:{ name:"script",
6640
6934
  description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
@@ -6678,7 +6972,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6678
6972
  negatable:false,
6679
6973
  exception_only:true,
6680
6974
  value_optional:true,
6681
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n 3p-cache|\n 3p-auth|\n webrtc|\n push|\n location|\n flash|\n java|\n referrer|\n useragent|\n ip|\n xclientdata|\n dpi|\n \\|?\n )\\b)+(?<!\\|)$" },
6975
+ value_format:"pipe_separated_stealth_options" },
6682
6976
  adg_ext_chrome:{ name:"stealth",
6683
6977
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6684
6978
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6686,7 +6980,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6686
6980
  negatable:false,
6687
6981
  exception_only:true,
6688
6982
  value_optional:true,
6689
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n xclientdata\n |\\|?\n )\\b)+(?<!\\|)$" },
6983
+ value_format:"pipe_separated_stealth_options" },
6690
6984
  adg_ext_firefox:{ name:"stealth",
6691
6985
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6692
6986
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6694,7 +6988,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6694
6988
  negatable:false,
6695
6989
  exception_only:true,
6696
6990
  value_optional:true,
6697
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
6991
+ value_format:"pipe_separated_stealth_options" },
6698
6992
  adg_ext_opera:{ name:"stealth",
6699
6993
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6700
6994
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6702,7 +6996,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6702
6996
  negatable:false,
6703
6997
  exception_only:true,
6704
6998
  value_optional:true,
6705
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
6999
+ value_format:"pipe_separated_stealth_options" },
6706
7000
  adg_ext_edge:{ name:"stealth",
6707
7001
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6708
7002
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6710,7 +7004,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6710
7004
  negatable:false,
6711
7005
  exception_only:true,
6712
7006
  value_optional:true,
6713
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" } };
7007
+ value_format:"pipe_separated_stealth_options" } };
6714
7008
 
6715
7009
  var data$9 = { ubo_any:{ name:"strict1p",
6716
7010
  description:"This new `strict1p` option can check for strict partyness.\nFor example, a network request qualifies as 1st-party if both the context and the request share the same hostname.",
@@ -6913,8 +7207,6 @@ var SpecificKey;
6913
7207
  SpecificKey["Negatable"] = "negatable";
6914
7208
  SpecificKey["BlockOnly"] = "block_only";
6915
7209
  SpecificKey["ExceptionOnly"] = "exception_only";
6916
- // TODO: consider removing this field
6917
- // and handle whether the value is optional by `value_format`. AG-24028
6918
7210
  SpecificKey["ValueOptional"] = "value_optional";
6919
7211
  SpecificKey["ValueFormat"] = "value_format";
6920
7212
  // TODO: following fields should be handled later
@@ -6978,25 +7270,123 @@ const getModifiersData = () => {
6978
7270
  return dataMap;
6979
7271
  };
6980
7272
 
6981
- const INVALID_ERROR_PREFIX = {
6982
- NOT_EXISTENT: 'Not existent modifier',
6983
- NOT_SUPPORTED: 'The adblocker does not support the modifier',
6984
- REMOVED: 'Removed and no longer supported modifier',
7273
+ /**
7274
+ * Prefixes for different adblockers to describe the platform-specific modifiers data
7275
+ * stored in the yaml files.
7276
+ */
7277
+ const BLOCKER_PREFIX = {
7278
+ [AdblockSyntax.Adg]: 'adg_',
7279
+ [AdblockSyntax.Ubo]: 'ubo_',
7280
+ [AdblockSyntax.Abp]: 'abp_',
7281
+ };
7282
+ /**
7283
+ * Set of all allowed characters for app name except the dot `.`.
7284
+ */
7285
+ const APP_NAME_ALLOWED_CHARS = new Set([
7286
+ ...CAPITAL_LETTERS,
7287
+ ...SMALL_LETTERS,
7288
+ ...NUMBERS,
7289
+ UNDERSCORE,
7290
+ ]);
7291
+ /**
7292
+ * Allowed methods for $method modifier.
7293
+ *
7294
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier}
7295
+ */
7296
+ const ALLOWED_METHODS = new Set([
7297
+ 'connect',
7298
+ 'delete',
7299
+ 'get',
7300
+ 'head',
7301
+ 'options',
7302
+ 'patch',
7303
+ 'post',
7304
+ 'put',
7305
+ 'trace',
7306
+ ]);
7307
+ /**
7308
+ * Allowed stealth options for $stealth modifier.
7309
+ *
7310
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier}
7311
+ */
7312
+ const ALLOWED_STEALTH_OPTIONS = new Set([
7313
+ 'searchqueries',
7314
+ 'donottrack',
7315
+ '3p-cookie',
7316
+ '1p-cookie',
7317
+ '3p-cache',
7318
+ '3p-auth',
7319
+ 'webrtc',
7320
+ 'push',
7321
+ 'location',
7322
+ 'flash',
7323
+ 'java',
7324
+ 'referrer',
7325
+ 'useragent',
7326
+ 'ip',
7327
+ 'xclientdata',
7328
+ 'dpi',
7329
+ ]);
7330
+ /**
7331
+ * Prefixes for error messages used in modifier validation.
7332
+ */
7333
+ const VALIDATION_ERROR_PREFIX = {
6985
7334
  BLOCK_ONLY: 'Only blocking rules may contain the modifier',
6986
7335
  EXCEPTION_ONLY: 'Only exception rules may contain the modifier',
6987
- NOT_NEGATABLE: 'Not negatable modifier',
6988
- VALUE_REQUIRED: 'Value is required for the modifier',
6989
- VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
7336
+ INVALID_LIST_VALUES: 'Invalid values for the modifier',
6990
7337
  INVALID_NOOP: 'Invalid noop modifier',
7338
+ MIXED_NEGATIONS: 'Simultaneous usage of negated and not negated values is forbidden for the modifier',
7339
+ NOT_EXISTENT: 'Non-existent modifier',
7340
+ NOT_NEGATABLE_MODIFIER: 'Non-negatable modifier',
7341
+ NOT_NEGATABLE_VALUE: 'Values cannot be negated for the modifier',
7342
+ NOT_SUPPORTED: 'The adblocker does not support the modifier',
7343
+ REMOVED: 'Removed and no longer supported modifier',
7344
+ VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
7345
+ VALUE_INVALID: 'Value is invalid for the modifier',
7346
+ VALUE_REQUIRED: 'Value is required for the modifier',
7347
+ };
7348
+ /**
7349
+ * Prefixes for error messages related to issues in the source YAML files' data.
7350
+ */
7351
+ const SOURCE_DATA_ERROR_PREFIX = {
7352
+ INVALID_VALUE_FORMAT_REGEXP: "Invalid regular expression in 'value_format' for the modifier",
7353
+ NO_DEPRECATION_MESSAGE: "Property 'deprecation_message' is required for the 'deprecated' modifier",
7354
+ NO_VALUE_FORMAT_FOR_ASSIGNABLE: "Property 'value_format' should be specified for the assignable modifier",
6991
7355
  };
6992
7356
 
6993
7357
  /**
6994
- * @file Validator for modifiers.
7358
+ * Validates the noop modifier (i.e. only underscores).
7359
+ *
7360
+ * @param value Value of the modifier.
7361
+ *
7362
+ * @returns True if the modifier is valid, false otherwise.
6995
7363
  */
6996
- const BLOCKER_PREFIX = {
6997
- [AdblockSyntax.Adg]: 'adg_',
6998
- [AdblockSyntax.Ubo]: 'ubo_',
6999
- [AdblockSyntax.Abp]: 'abp_',
7364
+ const isValidNoopModifier = (value) => {
7365
+ return value.split('').every((char) => char === UNDERSCORE);
7366
+ };
7367
+ /**
7368
+ * Returns invalid validation result with given error message.
7369
+ *
7370
+ * @param error Error message.
7371
+ *
7372
+ * @returns Validation result `{ valid: false, error }`.
7373
+ */
7374
+ const getInvalidValidationResult = (error) => {
7375
+ return {
7376
+ valid: false,
7377
+ error,
7378
+ };
7379
+ };
7380
+ /**
7381
+ * Returns invalid validation result which uses {@link VALIDATION_ERROR_PREFIX.VALUE_REQUIRED} as prefix
7382
+ * and specifies the given `modifierName` in the error message.
7383
+ *
7384
+ * @param modifierName Modifier name.
7385
+ *
7386
+ * @returns Validation result `{ valid: false, error }`.
7387
+ */
7388
+ const getValueRequiredValidationResult = (modifierName) => {
7389
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
7000
7390
  };
7001
7391
  /**
7002
7392
  * Collects names and aliases for all supported modifiers.
@@ -7048,116 +7438,620 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
7048
7438
  });
7049
7439
  return specificBlockerData;
7050
7440
  };
7441
+
7051
7442
  /**
7052
- * Returns invalid validation result with given error message.
7053
- *
7054
- * @param error Error message.
7055
- *
7056
- * @returns Validation result `{ ok: false, error }`.
7443
+ * @file Utility functions for domain and hostname validation.
7057
7444
  */
7058
- const getInvalidValidationResult = (error) => {
7059
- return {
7060
- ok: false,
7061
- error,
7062
- };
7063
- };
7064
7445
  /**
7065
- * Fully checks whether the given `modifier` valid for given blocker `syntax`:
7066
- * is it supported by the blocker, deprecated, assignable, negatable, etc.
7446
+ * Marker for a wildcard top-level domain `.*`.
7067
7447
  *
7068
- * @param modifiersData Parsed all modifiers data map.
7069
- * @param syntax Adblock syntax to check the modifier for.
7070
- * 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
7071
- * @param modifier Parsed modifier AST node.
7072
- * @param isException Whether the modifier is used in exception rule.
7073
- * Needed to check whether the modifier is allowed only in blocking or exception rules.
7448
+ * @example
7449
+ * `example.*` matches with any TLD, e.g. `example.org`, `example.com`, etc.
7450
+ */
7451
+ const WILDCARD_TLD = DOT + WILDCARD$1;
7452
+ /**
7453
+ * Marker for a wildcard subdomain `*.`.
7074
7454
  *
7075
- * @returns Result of modifier validation.
7455
+ * @example
7456
+ * `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
7076
7457
  */
7077
- const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
7078
- if (syntax === AdblockSyntax.Common) {
7079
- throw new Error(`Syntax should be specific, '${AdblockSyntax.Common}' is not supported`);
7080
- }
7081
- const modifierName = modifier.modifier.value;
7082
- const blockerPrefix = BLOCKER_PREFIX[syntax];
7083
- if (!blockerPrefix) {
7084
- throw new Error(`Unknown syntax: ${syntax}`);
7085
- }
7086
- // needed for validation of negation, assignment, etc.
7087
- const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
7088
- // if no specific blocker data is found
7089
- if (!specificBlockerData) {
7090
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
7091
- }
7092
- // e.g. 'object-subrequest'
7093
- if (specificBlockerData.removed) {
7094
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
7095
- }
7096
- if (specificBlockerData.deprecated) {
7097
- if (!specificBlockerData.deprecation_message) {
7098
- throw new Error('Deprecation notice is required for deprecated modifier');
7458
+ const WILDCARD_SUBDOMAIN = WILDCARD$1 + DOT;
7459
+ class DomainUtils {
7460
+ /**
7461
+ * Check if the input is a valid domain or hostname.
7462
+ *
7463
+ * @param domain Domain to check
7464
+ * @returns `true` if the domain is valid, `false` otherwise
7465
+ */
7466
+ static isValidDomainOrHostname(domain) {
7467
+ let domainToCheck = domain;
7468
+ // Wildcard-only domain, typically a generic rule
7469
+ if (domainToCheck === WILDCARD$1) {
7470
+ return true;
7099
7471
  }
7100
- return {
7101
- ok: true,
7102
- warn: specificBlockerData.deprecation_message,
7103
- };
7104
- }
7105
- if (specificBlockerData.block_only && isException) {
7106
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
7107
- }
7108
- if (specificBlockerData.exception_only && !isException) {
7109
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
7110
- }
7111
- // e.g. '~domain=example.com'
7112
- if (!specificBlockerData.negatable && modifier.exception) {
7113
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_NEGATABLE}: '${modifierName}'`);
7114
- }
7115
- // e.g. 'domain'
7116
- if (specificBlockerData.assignable) {
7117
- /**
7118
- * Some assignable modifiers can be used without a value,
7119
- * e.g. '@@||example.com^$cookie'.
7120
- */
7121
- if (!modifier.value
7122
- // value should be specified if it is not optional
7123
- && !specificBlockerData.value_optional) {
7124
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
7472
+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
7473
+ if (domainToCheck.endsWith(WILDCARD_TLD)) {
7474
+ // Remove the wildcard TLD
7475
+ domainToCheck = domainToCheck.substring(0, domainToCheck.length - WILDCARD_TLD.length);
7125
7476
  }
7126
- /**
7127
- * TODO: consider to return `{ ok: true, warn: 'Modifier value may be specified' }` (???)
7128
- * for $stealth modifier without a value
7129
- * but only after the extension will support value for $stealth:
7130
- * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
7131
- */
7132
- }
7133
- else if (modifier?.value) {
7134
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
7477
+ if (domainToCheck.startsWith(WILDCARD_SUBDOMAIN)) {
7478
+ // Remove the wildcard subdomain
7479
+ domainToCheck = domainToCheck.substring(WILDCARD_SUBDOMAIN.length);
7480
+ }
7481
+ // Parse the domain with tldts
7482
+ const tldtsResult = parse$1(domainToCheck);
7483
+ // Check if the domain is valid
7484
+ return domainToCheck === tldtsResult.domain || domainToCheck === tldtsResult.hostname;
7135
7485
  }
7136
- return { ok: true };
7137
- };
7486
+ }
7487
+
7138
7488
  /**
7139
- * Returns documentation URL for given modifier and adblocker.
7140
- *
7141
- * @param modifiersData Parsed all modifiers data map.
7142
- * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
7143
- * @param modifier Parsed modifier AST node.
7144
- *
7145
- * @returns Documentation URL or `null` if not found.
7489
+ * @file Utility functions for working with quotes
7146
7490
  */
7147
- const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
7148
- const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
7149
- return specificBlockerData?.docs || null;
7491
+ /**
7492
+ * Possible quote types for scriptlet parameters
7493
+ */
7494
+ var QuoteType;
7495
+ (function (QuoteType) {
7496
+ /**
7497
+ * No quotes at all
7498
+ */
7499
+ QuoteType["None"] = "none";
7500
+ /**
7501
+ * Single quotes (`'`)
7502
+ */
7503
+ QuoteType["Single"] = "single";
7504
+ /**
7505
+ * Double quotes (`"`)
7506
+ */
7507
+ QuoteType["Double"] = "double";
7508
+ })(QuoteType || (QuoteType = {}));
7509
+ /**
7510
+ * Utility functions for working with quotes
7511
+ */
7512
+ class QuoteUtils {
7513
+ /**
7514
+ * Escape all unescaped occurrences of the character
7515
+ *
7516
+ * @param string String to escape
7517
+ * @param char Character to escape
7518
+ * @returns Escaped string
7519
+ */
7520
+ static escapeUnescapedOccurrences(string, char) {
7521
+ let result = EMPTY;
7522
+ for (let i = 0; i < string.length; i += 1) {
7523
+ if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
7524
+ result += ESCAPE_CHARACTER;
7525
+ }
7526
+ result += string[i];
7527
+ }
7528
+ return result;
7529
+ }
7530
+ /**
7531
+ * Unescape all single escaped occurrences of the character
7532
+ *
7533
+ * @param string String to unescape
7534
+ * @param char Character to unescape
7535
+ * @returns Unescaped string
7536
+ */
7537
+ static unescapeSingleEscapedOccurrences(string, char) {
7538
+ let result = EMPTY;
7539
+ for (let i = 0; i < string.length; i += 1) {
7540
+ if (string[i] === char
7541
+ && string[i - 1] === ESCAPE_CHARACTER
7542
+ && (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
7543
+ result = result.slice(0, -1);
7544
+ }
7545
+ result += string[i];
7546
+ }
7547
+ return result;
7548
+ }
7549
+ /**
7550
+ * Get quote type of the string
7551
+ *
7552
+ * @param string String to check
7553
+ * @returns Quote type of the string
7554
+ */
7555
+ static getStringQuoteType(string) {
7556
+ // Don't check 1-character strings to avoid false positives
7557
+ if (string.length > 1) {
7558
+ if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
7559
+ return QuoteType.Single;
7560
+ }
7561
+ if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
7562
+ return QuoteType.Double;
7563
+ }
7564
+ }
7565
+ return QuoteType.None;
7566
+ }
7567
+ /**
7568
+ * Set quote type of the string
7569
+ *
7570
+ * @param string String to set quote type of
7571
+ * @param quoteType Quote type to set
7572
+ * @returns String with the specified quote type
7573
+ */
7574
+ static setStringQuoteType(string, quoteType) {
7575
+ const actualQuoteType = QuoteUtils.getStringQuoteType(string);
7576
+ switch (quoteType) {
7577
+ case QuoteType.None:
7578
+ if (actualQuoteType === QuoteType.Single) {
7579
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
7580
+ }
7581
+ if (actualQuoteType === QuoteType.Double) {
7582
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
7583
+ }
7584
+ return string;
7585
+ case QuoteType.Single:
7586
+ if (actualQuoteType === QuoteType.None) {
7587
+ return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
7588
+ }
7589
+ if (actualQuoteType === QuoteType.Double) {
7590
+ return SINGLE_QUOTE
7591
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
7592
+ }
7593
+ return string;
7594
+ case QuoteType.Double:
7595
+ if (actualQuoteType === QuoteType.None) {
7596
+ return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
7597
+ }
7598
+ if (actualQuoteType !== QuoteType.Double) {
7599
+ // eslint-disable-next-line max-len
7600
+ return DOUBLE_QUOTE
7601
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
7602
+ }
7603
+ return string;
7604
+ default:
7605
+ return string;
7606
+ }
7607
+ }
7608
+ /**
7609
+ * Removes bounding quotes from a string, if any
7610
+ *
7611
+ * @param string Input string
7612
+ * @returns String without quotes
7613
+ */
7614
+ static removeQuotes(string) {
7615
+ if (
7616
+ // We should check for string length to avoid false positives
7617
+ string.length > 1
7618
+ && (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
7619
+ && string[0] === string[string.length - 1]) {
7620
+ return string.slice(1, -1);
7621
+ }
7622
+ return string;
7623
+ }
7624
+ /**
7625
+ * Wraps given `strings` with `quote` (defaults to single quote `'`)
7626
+ * and joins them with `separator` (defaults to comma+space `, `).
7627
+ *
7628
+ * @param strings Strings to quote and join.
7629
+ * @param quoteType Quote to use.
7630
+ * @param separator Separator to use.
7631
+ *
7632
+ * @returns String with joined items.
7633
+ *
7634
+ * @example
7635
+ * ['abc', 'def']: strings[] -> "'abc', 'def'": string
7636
+ */
7637
+ static quoteAndJoinStrings(strings, quoteType = QuoteType.Single, separator = `${COMMA}${SPACE}`) {
7638
+ return strings
7639
+ .map((s) => QuoteUtils.setStringQuoteType(s, quoteType))
7640
+ .join(separator);
7641
+ }
7642
+ }
7643
+
7644
+ /**
7645
+ * Pre-defined available validators for modifiers with custom `value_format`.
7646
+ */
7647
+ var CustomValueFormatValidatorName;
7648
+ (function (CustomValueFormatValidatorName) {
7649
+ CustomValueFormatValidatorName["App"] = "pipe_separated_apps";
7650
+ // there are some differences between $domain and $denyallow
7651
+ CustomValueFormatValidatorName["DenyAllow"] = "pipe_separated_denyallow_domains";
7652
+ CustomValueFormatValidatorName["Domain"] = "pipe_separated_domains";
7653
+ CustomValueFormatValidatorName["Method"] = "pipe_separated_methods";
7654
+ CustomValueFormatValidatorName["StealthOption"] = "pipe_separated_stealth_options";
7655
+ })(CustomValueFormatValidatorName || (CustomValueFormatValidatorName = {}));
7656
+ /**
7657
+ * Checks whether the `chunk` of app name (which if splitted by dot `.`) is valid.
7658
+ * Only letters, numbers, and underscore `_` are allowed.
7659
+ *
7660
+ * @param chunk Chunk of app name to check.
7661
+ *
7662
+ * @returns True if the `chunk` is valid part of app name, false otherwise.
7663
+ */
7664
+ const isValidAppNameChunk = (chunk) => {
7665
+ // e.g. 'Example..exe'
7666
+ if (chunk.length === 0) {
7667
+ return false;
7668
+ }
7669
+ for (let i = 0; i < chunk.length; i += 1) {
7670
+ const char = chunk[i];
7671
+ if (!APP_NAME_ALLOWED_CHARS.has(char)) {
7672
+ return false;
7673
+ }
7674
+ }
7675
+ return true;
7150
7676
  };
7151
7677
  /**
7152
- * Validates the noop modifier (i.e. only underscores).
7678
+ * Checks whether the given `value` is valid app name as $app modifier value.
7153
7679
  *
7154
- * @param value Value of the modifier.
7680
+ * @param value App name to check.
7155
7681
  *
7156
- * @returns True if the modifier is valid, false otherwise.
7682
+ * @returns True if the `value` is valid app name, false otherwise.
7157
7683
  */
7158
- const isValidNoopModifier = (value) => {
7159
- return value.split('').every((char) => char === UNDERSCORE);
7684
+ const isValidAppModifierValue = (value) => {
7685
+ // $app modifier does not support wildcard tld
7686
+ // https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
7687
+ if (value.includes(WILDCARD$1)) {
7688
+ return false;
7689
+ }
7690
+ return value
7691
+ .split(DOT)
7692
+ .every((chunk) => isValidAppNameChunk(chunk));
7693
+ };
7694
+ /**
7695
+ * Checks whether the given `value` is valid HTTP method as $method modifier value.
7696
+ *
7697
+ * @param value Method to check.
7698
+ *
7699
+ * @returns True if the `value` is valid HTTP method, false otherwise.
7700
+ */
7701
+ const isValidMethodModifierValue = (value) => {
7702
+ return ALLOWED_METHODS.has(value);
7703
+ };
7704
+ /**
7705
+ * Checks whether the given `value` is valid option as $stealth modifier value.
7706
+ *
7707
+ * @param value Stealth option to check.
7708
+ *
7709
+ * @returns True if the `value` is valid stealth option, false otherwise.
7710
+ */
7711
+ const isValidStealthModifierValue = (value) => {
7712
+ return ALLOWED_STEALTH_OPTIONS.has(value);
7713
+ };
7714
+ /**
7715
+ * Checks whether the given `value` is valid domain as $denyallow modifier value.
7716
+ * Important: wildcard tld are not supported, compared to $domain.
7717
+ *
7718
+ * @param value Value to check.
7719
+ *
7720
+ * @returns True if the `value` is valid domain and does not contain wildcard `*`, false otherwise.
7721
+ */
7722
+ const isValidDenyAllowModifierValue = (value) => {
7723
+ // $denyallow modifier does not support wildcard tld
7724
+ // https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
7725
+ // but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
7726
+ if (value.includes(WILDCARD$1)) {
7727
+ return false;
7728
+ }
7729
+ // TODO: add cache for domains validation
7730
+ return DomainUtils.isValidDomainOrHostname(value);
7731
+ };
7732
+ /**
7733
+ * Checks whether the given `value` is valid domain as $domain modifier value.
7734
+ *
7735
+ * @param value Value to check.
7736
+ *
7737
+ * @returns True if the `value` is valid domain, false otherwise.
7738
+ */
7739
+ const isValidDomainModifierValue = (value) => {
7740
+ // TODO: add cache for domains validation
7741
+ return DomainUtils.isValidDomainOrHostname(value);
7742
+ };
7743
+ /**
7744
+ * Checks whether the all list items' exceptions are `false`.
7745
+ * Those items which `exception` is `true` is to be specified in the validation result error message.
7746
+ *
7747
+ * @param modifierName Modifier name.
7748
+ * @param listItems List items to check.
7749
+ *
7750
+ * @returns Validation result.
7751
+ */
7752
+ const customNoNegatedListItemsValidator = (modifierName, listItems) => {
7753
+ const negatedValues = [];
7754
+ listItems.forEach((listItem) => {
7755
+ if (listItem.exception) {
7756
+ negatedValues.push(listItem.value);
7757
+ }
7758
+ });
7759
+ if (negatedValues.length > 0) {
7760
+ const valuesToStr = QuoteUtils.quoteAndJoinStrings(negatedValues);
7761
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_NEGATABLE_VALUE}: '${modifierName}': ${valuesToStr}`);
7762
+ }
7763
+ return { valid: true };
7764
+ };
7765
+ /**
7766
+ * Checks whether the all list items' exceptions are consistent,
7767
+ * i.e. all items are either negated or not negated.
7768
+ *
7769
+ * The `exception` value of the first item is used as a reference, and all other items are checked against it.
7770
+ * Those items which `exception` is not consistent with the first item
7771
+ * is to be specified in the validation result error message.
7772
+ *
7773
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#method-modifier}
7774
+ *
7775
+ * @param modifierName Modifier name.
7776
+ * @param listItems List items to check.
7777
+ *
7778
+ * @returns Validation result.
7779
+ */
7780
+ const customConsistentExceptionsValidator = (modifierName, listItems) => {
7781
+ const firstException = listItems[0].exception;
7782
+ const nonConsistentItemValues = [];
7783
+ listItems.forEach((listItem) => {
7784
+ if (listItem.exception !== firstException) {
7785
+ nonConsistentItemValues.push(listItem.value);
7786
+ }
7787
+ });
7788
+ if (nonConsistentItemValues.length > 0) {
7789
+ const valuesToStr = QuoteUtils.quoteAndJoinStrings(nonConsistentItemValues);
7790
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.MIXED_NEGATIONS}: '${modifierName}': ${valuesToStr}`);
7791
+ }
7792
+ return { valid: true };
7793
+ };
7794
+ /**
7795
+ * Checks whether the given `modifier` value is valid.
7796
+ * Supposed to validate the value of modifiers which values are lists separated by pipe `|` —
7797
+ * $app, $domain, $denyallow, $method.
7798
+ *
7799
+ * @param modifier Modifier AST node.
7800
+ * @param listParser Parser function for parsing modifier value
7801
+ * which is supposed to be a list separated by pipe `|`.
7802
+ * @param isValidListItem Predicate function for checking of modifier's list item validity,
7803
+ * e.g. $denyallow modifier does not support wildcard tld, but $domain does.
7804
+ * @param customListValidator Optional; custom validator for specific modifier,
7805
+ * e.g. $denyallow modifier does not support negated domains.
7806
+ *
7807
+ * @returns Result of modifier domains validation.
7808
+ */
7809
+ const validateListItemsModifier = (modifier, listParser, isValidListItem, customListValidator) => {
7810
+ const modifierName = modifier.modifier.value;
7811
+ const defaultInvalidValueResult = getValueRequiredValidationResult(modifierName);
7812
+ if (!modifier.value?.value) {
7813
+ return defaultInvalidValueResult;
7814
+ }
7815
+ let theList;
7816
+ try {
7817
+ theList = listParser(modifier.value.value, PIPE_MODIFIER_SEPARATOR);
7818
+ }
7819
+ catch (e) {
7820
+ if (e instanceof AdblockSyntaxError) {
7821
+ return {
7822
+ valid: false,
7823
+ error: e.message,
7824
+ };
7825
+ }
7826
+ return defaultInvalidValueResult;
7827
+ }
7828
+ const invalidListItems = [];
7829
+ theList.children.forEach((item) => {
7830
+ // different validators are used for $denyallow and $domain modifiers
7831
+ // because of different requirements and restrictions
7832
+ if (!isValidListItem(item.value)) {
7833
+ invalidListItems.push(item.value);
7834
+ }
7835
+ });
7836
+ if (invalidListItems.length > 0) {
7837
+ const itemsToStr = QuoteUtils.quoteAndJoinStrings(invalidListItems);
7838
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_LIST_VALUES}: '${modifierName}': ${itemsToStr}`);
7839
+ }
7840
+ // IMPORTANT: run custom validator after all other checks
7841
+ // Some lists should be fully checked, not just the list items:
7842
+ // e.g. Safari does not support allowed and disallowed domains for $domain in the same list
7843
+ // or domains cannot be negated for $denyallow modifier
7844
+ if (customListValidator) {
7845
+ return customListValidator(modifierName, theList.children);
7846
+ }
7847
+ return { valid: true };
7848
+ };
7849
+ /**
7850
+ * Validates 'pipe_separated_apps' custom value format.
7851
+ * Used for $app modifier.
7852
+ *
7853
+ * @param modifier Modifier AST node.
7854
+ *
7855
+ * @returns Validation result.
7856
+ */
7857
+ const validatePipeSeparatedApps = (modifier) => {
7858
+ return validateListItemsModifier(modifier, (raw) => AppListParser.parse(raw), isValidAppModifierValue);
7859
+ };
7860
+ /**
7861
+ * Validates 'pipe_separated_denyallow_domains' custom value format.
7862
+ * Used for $denyallow modifier.
7863
+ *
7864
+ * @param modifier Modifier AST node.
7865
+ *
7866
+ * @returns Validation result.
7867
+ */
7868
+ const validatePipeSeparatedDenyAllowDomains = (modifier) => {
7869
+ return validateListItemsModifier(modifier, DomainListParser.parse, isValidDenyAllowModifierValue, customNoNegatedListItemsValidator);
7870
+ };
7871
+ /**
7872
+ * Validates 'pipe_separated_domains' custom value format.
7873
+ * Used for $domains modifier.
7874
+ *
7875
+ * @param modifier Modifier AST node.
7876
+ *
7877
+ * @returns Validation result.
7878
+ */
7879
+ const validatePipeSeparatedDomains = (modifier) => {
7880
+ return validateListItemsModifier(modifier, DomainListParser.parse, isValidDomainModifierValue);
7881
+ };
7882
+ /**
7883
+ * Validates 'pipe_separated_methods' custom value format.
7884
+ * Used for $method modifier.
7885
+ *
7886
+ * @param modifier Modifier AST node.
7887
+ *
7888
+ * @returns Validation result.
7889
+ */
7890
+ const validatePipeSeparatedMethods = (modifier) => {
7891
+ return validateListItemsModifier(modifier, (raw) => MethodListParser.parse(raw), isValidMethodModifierValue, customConsistentExceptionsValidator);
7892
+ };
7893
+ /**
7894
+ * Validates 'pipe_separated_stealth_options' custom value format.
7895
+ * Used for $stealth modifier.
7896
+ *
7897
+ * @param modifier Modifier AST node.
7898
+ *
7899
+ * @returns Validation result.
7900
+ */
7901
+ const validatePipeSeparatedStealthOptions = (modifier) => {
7902
+ return validateListItemsModifier(modifier, (raw) => StealthOptionListParser.parse(raw), isValidStealthModifierValue, customNoNegatedListItemsValidator);
7903
+ };
7904
+ /**
7905
+ * Map of all available pre-defined validators for modifiers with custom `value_format`.
7906
+ */
7907
+ const CUSTOM_VALUE_FORMAT_MAP = {
7908
+ [CustomValueFormatValidatorName.App]: validatePipeSeparatedApps,
7909
+ [CustomValueFormatValidatorName.DenyAllow]: validatePipeSeparatedDenyAllowDomains,
7910
+ [CustomValueFormatValidatorName.Domain]: validatePipeSeparatedDomains,
7911
+ [CustomValueFormatValidatorName.Method]: validatePipeSeparatedMethods,
7912
+ [CustomValueFormatValidatorName.StealthOption]: validatePipeSeparatedStealthOptions,
7913
+ };
7914
+ /**
7915
+ * Returns whether the given `valueFormat` is a valid custom value format validator name.
7916
+ *
7917
+ * @param valueFormat Value format for the modifier.
7918
+ *
7919
+ * @returns True if `valueFormat` is a supported pre-defined value format validator name, false otherwise.
7920
+ */
7921
+ const isCustomValueFormatValidator = (valueFormat) => {
7922
+ return Object.keys(CUSTOM_VALUE_FORMAT_MAP).includes(valueFormat);
7923
+ };
7924
+ /**
7925
+ * Checks whether the value for given `modifier` is valid.
7926
+ *
7927
+ * @param modifier Modifier AST node.
7928
+ * @param valueFormat Value format for the modifier.
7929
+ *
7930
+ * @returns Validation result.
7931
+ */
7932
+ const validateValue = (modifier, valueFormat) => {
7933
+ if (isCustomValueFormatValidator(valueFormat)) {
7934
+ const validator = CUSTOM_VALUE_FORMAT_MAP[valueFormat];
7935
+ return validator(modifier);
7936
+ }
7937
+ const modifierName = modifier.modifier.value;
7938
+ if (!modifier.value?.value) {
7939
+ return getValueRequiredValidationResult(modifierName);
7940
+ }
7941
+ let xRegExp;
7942
+ try {
7943
+ xRegExp = XRegExp(valueFormat);
7944
+ }
7945
+ catch (e) {
7946
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.INVALID_VALUE_FORMAT_REGEXP}: '${modifierName}'`);
7947
+ }
7948
+ const isValid = xRegExp.test(modifier.value?.value);
7949
+ if (!isValid) {
7950
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}'`);
7951
+ }
7952
+ return { valid: true };
7160
7953
  };
7954
+
7955
+ /**
7956
+ * @file Validator for modifiers.
7957
+ */
7958
+ /**
7959
+ * Fully checks whether the given `modifier` valid for given blocker `syntax`:
7960
+ * is it supported by the blocker, deprecated, assignable, negatable, etc.
7961
+ *
7962
+ * @param modifiersData Parsed all modifiers data map.
7963
+ * @param syntax Adblock syntax to check the modifier for.
7964
+ * 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
7965
+ * @param modifier Parsed modifier AST node.
7966
+ * @param isException Whether the modifier is used in exception rule.
7967
+ * Needed to check whether the modifier is allowed only in blocking or exception rules.
7968
+ *
7969
+ * @returns Result of modifier validation.
7970
+ */
7971
+ const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
7972
+ if (syntax === AdblockSyntax.Common) {
7973
+ throw new Error(`Syntax should be specific, '${AdblockSyntax.Common}' is not supported`);
7974
+ }
7975
+ const modifierName = modifier.modifier.value;
7976
+ const blockerPrefix = BLOCKER_PREFIX[syntax];
7977
+ if (!blockerPrefix) {
7978
+ throw new Error(`Unknown syntax: ${syntax}`);
7979
+ }
7980
+ // needed for validation of negation, assignment, etc.
7981
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
7982
+ // if no specific blocker data is found
7983
+ if (!specificBlockerData) {
7984
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
7985
+ }
7986
+ // e.g. 'object-subrequest'
7987
+ if (specificBlockerData[SpecificKey.Removed]) {
7988
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
7989
+ }
7990
+ if (specificBlockerData[SpecificKey.Deprecated]) {
7991
+ if (!specificBlockerData[SpecificKey.DeprecationMessage]) {
7992
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.NO_DEPRECATION_MESSAGE}: '${modifierName}'`);
7993
+ }
7994
+ // prepare the message which is multiline in the yaml file
7995
+ const warn = specificBlockerData[SpecificKey.DeprecationMessage].replace(NEWLINE, SPACE);
7996
+ return {
7997
+ valid: true,
7998
+ warn,
7999
+ };
8000
+ }
8001
+ if (specificBlockerData[SpecificKey.BlockOnly] && isException) {
8002
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
8003
+ }
8004
+ if (specificBlockerData[SpecificKey.ExceptionOnly] && !isException) {
8005
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
8006
+ }
8007
+ // e.g. '~domain=example.com'
8008
+ if (!specificBlockerData[SpecificKey.Negatable] && modifier.exception) {
8009
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_NEGATABLE_MODIFIER}: '${modifierName}'`);
8010
+ }
8011
+ // e.g. 'domain'
8012
+ if (specificBlockerData[SpecificKey.Assignable]) {
8013
+ if (!modifier.value) {
8014
+ /**
8015
+ * Some assignable modifiers can be used without a value,
8016
+ * e.g. '@@||example.com^$cookie'.
8017
+ */
8018
+ if (specificBlockerData[SpecificKey.ValueOptional]) {
8019
+ return { valid: true };
8020
+ }
8021
+ // for other assignable modifiers the value is required
8022
+ return getValueRequiredValidationResult(modifierName);
8023
+ }
8024
+ /**
8025
+ * TODO: consider to return `{ valid: true, warn: 'Modifier value may be specified' }` (???)
8026
+ * for $stealth modifier without a value
8027
+ * but only after the extension will support value for $stealth:
8028
+ * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
8029
+ */
8030
+ if (!specificBlockerData[SpecificKey.ValueFormat]) {
8031
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.NO_VALUE_FORMAT_FOR_ASSIGNABLE}: '${modifierName}'`);
8032
+ }
8033
+ return validateValue(modifier, specificBlockerData[SpecificKey.ValueFormat]);
8034
+ }
8035
+ if (modifier?.value) {
8036
+ // e.g. 'third-party=true'
8037
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
8038
+ }
8039
+ return { valid: true };
8040
+ };
8041
+ /**
8042
+ * Returns documentation URL for given modifier and adblocker.
8043
+ *
8044
+ * @param modifiersData Parsed all modifiers data map.
8045
+ * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
8046
+ * @param modifier Parsed modifier AST node.
8047
+ *
8048
+ * @returns Documentation URL or `null` if not found.
8049
+ */
8050
+ const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
8051
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
8052
+ return specificBlockerData?.docs || null;
8053
+ };
8054
+ // TODO: move to modifier.ts and use index.ts only for exporting
7161
8055
  /**
7162
8056
  * Modifier validator class.
7163
8057
  */
@@ -7210,18 +8104,18 @@ class ModifierValidator {
7210
8104
  if (modifier.modifier.value.startsWith(UNDERSCORE)) {
7211
8105
  // check whether the modifier value contains something else besides underscores
7212
8106
  if (!isValidNoopModifier(modifier.modifier.value)) {
7213
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
8107
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
7214
8108
  }
7215
8109
  // otherwise, replace the modifier value with single underscore.
7216
8110
  // it is needed to check whether the modifier is supported by specific adblocker due to the syntax
7217
8111
  modifier.modifier.value = UNDERSCORE;
7218
8112
  }
7219
8113
  if (!this.exists(modifier)) {
7220
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
8114
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
7221
8115
  }
7222
8116
  // for 'Common' syntax we cannot check something more
7223
8117
  if (syntax === AdblockSyntax.Common) {
7224
- return { ok: true };
8118
+ return { valid: true };
7225
8119
  }
7226
8120
  return validateForSpecificSyntax(this.modifiersData, syntax, modifier, isException);
7227
8121
  };
@@ -7579,7 +8473,7 @@ const MAX_LENGTH = 'max-length';
7579
8473
  const MIN_LENGTH = 'min-length';
7580
8474
  const MIN_TEXT_LENGTH = 'min-text-length';
7581
8475
  const TAG_CONTENT = 'tag-content';
7582
- const WILDCARD$1 = 'wildcard';
8476
+ const WILDCARD = 'wildcard';
7583
8477
  /**
7584
8478
  * HTML filtering rule converter class
7585
8479
  *
@@ -7650,7 +8544,7 @@ class HtmlRuleConverter extends RuleConverterBase {
7650
8544
  maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
7651
8545
  break;
7652
8546
  case TAG_CONTENT:
7653
- case WILDCARD$1:
8547
+ case WILDCARD:
7654
8548
  CssTree.assertAttributeSelectorHasStringValue(node);
7655
8549
  convertedSelector.children.push(cloneDeep(node));
7656
8550
  break;
@@ -7728,144 +8622,6 @@ class HtmlRuleConverter extends RuleConverterBase {
7728
8622
  }
7729
8623
  }
7730
8624
 
7731
- /**
7732
- * @file Utility functions for working with quotes
7733
- */
7734
- /**
7735
- * Possible quote types for scriptlet parameters
7736
- */
7737
- var QuoteType;
7738
- (function (QuoteType) {
7739
- /**
7740
- * No quotes at all
7741
- */
7742
- QuoteType["None"] = "none";
7743
- /**
7744
- * Single quotes (`'`)
7745
- */
7746
- QuoteType["Single"] = "single";
7747
- /**
7748
- * Double quotes (`"`)
7749
- */
7750
- QuoteType["Double"] = "double";
7751
- })(QuoteType || (QuoteType = {}));
7752
- /**
7753
- * Utility functions for working with quotes
7754
- */
7755
- class QuoteUtils {
7756
- /**
7757
- * Escape all unescaped occurrences of the character
7758
- *
7759
- * @param string String to escape
7760
- * @param char Character to escape
7761
- * @returns Escaped string
7762
- */
7763
- static escapeUnescapedOccurrences(string, char) {
7764
- let result = EMPTY;
7765
- for (let i = 0; i < string.length; i += 1) {
7766
- if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
7767
- result += ESCAPE_CHARACTER;
7768
- }
7769
- result += string[i];
7770
- }
7771
- return result;
7772
- }
7773
- /**
7774
- * Unescape all single escaped occurrences of the character
7775
- *
7776
- * @param string String to unescape
7777
- * @param char Character to unescape
7778
- * @returns Unescaped string
7779
- */
7780
- static unescapeSingleEscapedOccurrences(string, char) {
7781
- let result = EMPTY;
7782
- for (let i = 0; i < string.length; i += 1) {
7783
- if (string[i] === char
7784
- && string[i - 1] === ESCAPE_CHARACTER
7785
- && (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
7786
- result = result.slice(0, -1);
7787
- }
7788
- result += string[i];
7789
- }
7790
- return result;
7791
- }
7792
- /**
7793
- * Get quote type of the string
7794
- *
7795
- * @param string String to check
7796
- * @returns Quote type of the string
7797
- */
7798
- static getStringQuoteType(string) {
7799
- // Don't check 1-character strings to avoid false positives
7800
- if (string.length > 1) {
7801
- if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
7802
- return QuoteType.Single;
7803
- }
7804
- if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
7805
- return QuoteType.Double;
7806
- }
7807
- }
7808
- return QuoteType.None;
7809
- }
7810
- /**
7811
- * Set quote type of the string
7812
- *
7813
- * @param string String to set quote type of
7814
- * @param quoteType Quote type to set
7815
- * @returns String with the specified quote type
7816
- */
7817
- static setStringQuoteType(string, quoteType) {
7818
- const actualQuoteType = QuoteUtils.getStringQuoteType(string);
7819
- switch (quoteType) {
7820
- case QuoteType.None:
7821
- if (actualQuoteType === QuoteType.Single) {
7822
- return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
7823
- }
7824
- if (actualQuoteType === QuoteType.Double) {
7825
- return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
7826
- }
7827
- return string;
7828
- case QuoteType.Single:
7829
- if (actualQuoteType === QuoteType.None) {
7830
- return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
7831
- }
7832
- if (actualQuoteType === QuoteType.Double) {
7833
- return SINGLE_QUOTE
7834
- + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
7835
- }
7836
- return string;
7837
- case QuoteType.Double:
7838
- if (actualQuoteType === QuoteType.None) {
7839
- return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
7840
- }
7841
- if (actualQuoteType !== QuoteType.Double) {
7842
- // eslint-disable-next-line max-len
7843
- return DOUBLE_QUOTE
7844
- + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
7845
- }
7846
- return string;
7847
- default:
7848
- return string;
7849
- }
7850
- }
7851
- /**
7852
- * Removes bounding quotes from a string, if any
7853
- *
7854
- * @param string Input string
7855
- * @returns String without quotes
7856
- */
7857
- static removeQuotes(string) {
7858
- if (
7859
- // We should check for string length to avoid false positives
7860
- string.length > 1
7861
- && (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
7862
- && string[0] === string[string.length - 1]) {
7863
- return string.slice(1, -1);
7864
- }
7865
- return string;
7866
- }
7867
- }
7868
-
7869
8625
  /**
7870
8626
  * @file Utility functions for working with scriptlet nodes
7871
8627
  */
@@ -8762,41 +9518,6 @@ class FilterListConverter extends ConverterBase {
8762
9518
  }
8763
9519
  }
8764
9520
 
8765
- /**
8766
- * @file Utility functions for domain and hostname validation.
8767
- */
8768
- const WILDCARD = ASTERISK; // *
8769
- const WILDCARD_TLD = DOT + WILDCARD; // .*
8770
- const WILDCARD_SUBDOMAIN = WILDCARD + DOT; // *.
8771
- class DomainUtils {
8772
- /**
8773
- * Check if the input is a valid domain or hostname.
8774
- *
8775
- * @param domain Domain to check
8776
- * @returns `true` if the domain is valid, `false` otherwise
8777
- */
8778
- static isValidDomainOrHostname(domain) {
8779
- let domainToCheck = domain;
8780
- // Wildcard-only domain, typically a generic rule
8781
- if (domainToCheck === WILDCARD) {
8782
- return true;
8783
- }
8784
- // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
8785
- if (domainToCheck.endsWith(WILDCARD_TLD)) {
8786
- // Remove the wildcard TLD
8787
- domainToCheck = domainToCheck.substring(0, domainToCheck.length - WILDCARD_TLD.length);
8788
- }
8789
- if (domainToCheck.startsWith(WILDCARD_SUBDOMAIN)) {
8790
- // Remove the wildcard subdomain
8791
- domainToCheck = domainToCheck.substring(WILDCARD_SUBDOMAIN.length);
8792
- }
8793
- // Parse the domain with tldts
8794
- const tldtsResult = parse$1(domainToCheck);
8795
- // Check if the domain is valid
8796
- return domainToCheck === tldtsResult.domain || domainToCheck === tldtsResult.hostname;
8797
- }
8798
- }
8799
-
8800
9521
  /**
8801
9522
  * @file Utility functions for logical expression AST.
8802
9523
  */
@@ -8875,7 +9596,7 @@ class LogicalExpressionUtils {
8875
9596
  }
8876
9597
  }
8877
9598
 
8878
- const version$1 = "1.1.2";
9599
+ const version$1 = "1.1.4";
8879
9600
 
8880
9601
  /**
8881
9602
  * @file AGTree version
@@ -8886,4 +9607,4 @@ const version$1 = "1.1.2";
8886
9607
  // with wrong relative path to `package.json`. So we need this little "hack"
8887
9608
  const version = version$1;
8888
9609
 
8889
- 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, CLASSIC_DOMAIN_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DOMAIN_EXCEPTION_MARKER, 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, MODIFIER_DOMAIN_SEPARATOR, MODIFIER_EXCEPTION_MARKER, MetadataCommentRuleParser, ModifierListParser, ModifierParser, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };
9610
+ 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 };