@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.
package/dist/agtree.cjs CHANGED
@@ -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 @@ var coerce = require('semver/functions/coerce.js');
11
11
  var JSON5 = require('json5');
12
12
  var ecssTree = require('@adguard/ecss-tree');
13
13
  var cloneDeep = require('clone-deep');
14
- var scriptlets = require('@adguard/scriptlets');
14
+ var XRegExp = require('xregexp');
15
15
  var tldts = require('tldts');
16
+ var scriptlets = require('@adguard/scriptlets');
16
17
 
17
18
  function _interopNamespaceDefault(e) {
18
19
  var n = Object.create(null);
@@ -115,6 +116,7 @@ const CR = '\r';
115
116
  const FF = '\f';
116
117
  const LF = '\n';
117
118
  const CRLF = CR + LF;
119
+ const NEWLINE = LF;
118
120
  const DOUBLE_QUOTE = '"';
119
121
  const SINGLE_QUOTE = '\'';
120
122
  // Brackets
@@ -126,21 +128,157 @@ const OPEN_CURLY_BRACKET = '{';
126
128
  const CLOSE_CURLY_BRACKET = '}';
127
129
  // Letters
128
130
  const SMALL_LETTER_A = 'a';
131
+ const SMALL_LETTER_B = 'b';
132
+ const SMALL_LETTER_C = 'c';
133
+ const SMALL_LETTER_D = 'd';
134
+ const SMALL_LETTER_E = 'e';
135
+ const SMALL_LETTER_F = 'f';
136
+ const SMALL_LETTER_G = 'g';
137
+ const SMALL_LETTER_H = 'h';
138
+ const SMALL_LETTER_I = 'i';
139
+ const SMALL_LETTER_J = 'j';
140
+ const SMALL_LETTER_K = 'k';
141
+ const SMALL_LETTER_L = 'l';
142
+ const SMALL_LETTER_M = 'm';
143
+ const SMALL_LETTER_N = 'n';
144
+ const SMALL_LETTER_O = 'o';
145
+ const SMALL_LETTER_P = 'p';
146
+ const SMALL_LETTER_Q = 'q';
147
+ const SMALL_LETTER_R = 'r';
148
+ const SMALL_LETTER_S = 's';
149
+ const SMALL_LETTER_T = 't';
150
+ const SMALL_LETTER_U = 'u';
151
+ const SMALL_LETTER_V = 'v';
152
+ const SMALL_LETTER_W = 'w';
153
+ const SMALL_LETTER_X = 'x';
154
+ const SMALL_LETTER_Y = 'y';
129
155
  const SMALL_LETTER_Z = 'z';
156
+ /**
157
+ * Set of all small letters.
158
+ */
159
+ const SMALL_LETTERS = new Set([
160
+ SMALL_LETTER_A,
161
+ SMALL_LETTER_B,
162
+ SMALL_LETTER_C,
163
+ SMALL_LETTER_D,
164
+ SMALL_LETTER_E,
165
+ SMALL_LETTER_F,
166
+ SMALL_LETTER_G,
167
+ SMALL_LETTER_H,
168
+ SMALL_LETTER_I,
169
+ SMALL_LETTER_J,
170
+ SMALL_LETTER_K,
171
+ SMALL_LETTER_L,
172
+ SMALL_LETTER_M,
173
+ SMALL_LETTER_N,
174
+ SMALL_LETTER_O,
175
+ SMALL_LETTER_P,
176
+ SMALL_LETTER_Q,
177
+ SMALL_LETTER_R,
178
+ SMALL_LETTER_S,
179
+ SMALL_LETTER_T,
180
+ SMALL_LETTER_U,
181
+ SMALL_LETTER_V,
182
+ SMALL_LETTER_W,
183
+ SMALL_LETTER_X,
184
+ SMALL_LETTER_Y,
185
+ SMALL_LETTER_Z,
186
+ ]);
130
187
  // Capital letters
131
188
  const CAPITAL_LETTER_A = 'A';
189
+ const CAPITAL_LETTER_B = 'B';
190
+ const CAPITAL_LETTER_C = 'C';
191
+ const CAPITAL_LETTER_D = 'D';
192
+ const CAPITAL_LETTER_E = 'E';
193
+ const CAPITAL_LETTER_F = 'F';
194
+ const CAPITAL_LETTER_G = 'G';
195
+ const CAPITAL_LETTER_H = 'H';
196
+ const CAPITAL_LETTER_I = 'I';
197
+ const CAPITAL_LETTER_J = 'J';
198
+ const CAPITAL_LETTER_K = 'K';
199
+ const CAPITAL_LETTER_L = 'L';
200
+ const CAPITAL_LETTER_M = 'M';
201
+ const CAPITAL_LETTER_N = 'N';
202
+ const CAPITAL_LETTER_O = 'O';
203
+ const CAPITAL_LETTER_P = 'P';
204
+ const CAPITAL_LETTER_Q = 'Q';
205
+ const CAPITAL_LETTER_R = 'R';
206
+ const CAPITAL_LETTER_S = 'S';
207
+ const CAPITAL_LETTER_T = 'T';
208
+ const CAPITAL_LETTER_U = 'U';
209
+ const CAPITAL_LETTER_V = 'V';
210
+ const CAPITAL_LETTER_W = 'W';
211
+ const CAPITAL_LETTER_X = 'X';
212
+ const CAPITAL_LETTER_Y = 'Y';
132
213
  const CAPITAL_LETTER_Z = 'Z';
214
+ /**
215
+ * Set of all capital letters.
216
+ */
217
+ const CAPITAL_LETTERS = new Set([
218
+ CAPITAL_LETTER_A,
219
+ CAPITAL_LETTER_B,
220
+ CAPITAL_LETTER_C,
221
+ CAPITAL_LETTER_D,
222
+ CAPITAL_LETTER_E,
223
+ CAPITAL_LETTER_F,
224
+ CAPITAL_LETTER_G,
225
+ CAPITAL_LETTER_H,
226
+ CAPITAL_LETTER_I,
227
+ CAPITAL_LETTER_J,
228
+ CAPITAL_LETTER_K,
229
+ CAPITAL_LETTER_L,
230
+ CAPITAL_LETTER_M,
231
+ CAPITAL_LETTER_N,
232
+ CAPITAL_LETTER_O,
233
+ CAPITAL_LETTER_P,
234
+ CAPITAL_LETTER_Q,
235
+ CAPITAL_LETTER_R,
236
+ CAPITAL_LETTER_S,
237
+ CAPITAL_LETTER_T,
238
+ CAPITAL_LETTER_U,
239
+ CAPITAL_LETTER_V,
240
+ CAPITAL_LETTER_W,
241
+ CAPITAL_LETTER_X,
242
+ CAPITAL_LETTER_Y,
243
+ CAPITAL_LETTER_Z,
244
+ ]);
133
245
  // Numbers as strings
134
246
  const NUMBER_0 = '0';
247
+ const NUMBER_1 = '1';
248
+ const NUMBER_2 = '2';
249
+ const NUMBER_3 = '3';
250
+ const NUMBER_4 = '4';
251
+ const NUMBER_5 = '5';
252
+ const NUMBER_6 = '6';
253
+ const NUMBER_7 = '7';
254
+ const NUMBER_8 = '8';
135
255
  const NUMBER_9 = '9';
256
+ /**
257
+ * Set of all numbers as strings.
258
+ */
259
+ const NUMBERS = new Set([
260
+ NUMBER_0,
261
+ NUMBER_1,
262
+ NUMBER_2,
263
+ NUMBER_3,
264
+ NUMBER_4,
265
+ NUMBER_5,
266
+ NUMBER_6,
267
+ NUMBER_7,
268
+ NUMBER_8,
269
+ NUMBER_9,
270
+ ]);
136
271
  const REGEX_MARKER = '/';
137
272
  const ADG_SCRIPTLET_MASK = '//scriptlet';
138
273
  const UBO_SCRIPTLET_MASK = 'js';
139
274
  // Modifiers are separated by ",". For example: "script,domain=example.com"
140
275
  const MODIFIERS_SEPARATOR = ',';
141
- const MODIFIER_EXCEPTION_MARKER = '~';
142
276
  const MODIFIER_ASSIGN_OPERATOR = '=';
143
- const DOMAIN_EXCEPTION_MARKER = '~';
277
+ const NEGATION_MARKER = '~';
278
+ /**
279
+ * The wildcard symbol — `*`.
280
+ */
281
+ const WILDCARD$1 = ASTERISK;
144
282
  /**
145
283
  * Classic domain separator.
146
284
  *
@@ -150,9 +288,9 @@ const DOMAIN_EXCEPTION_MARKER = '~';
150
288
  * example.com,~example.org##.ads
151
289
  * ```
152
290
  */
153
- const CLASSIC_DOMAIN_SEPARATOR = ',';
291
+ const COMMA_DOMAIN_LIST_SEPARATOR = ',';
154
292
  /**
155
- * Modifier domain separator.
293
+ * Modifier separator for $app, $denyallow, $domain, $method.
156
294
  *
157
295
  * @example
158
296
  * ```adblock
@@ -160,8 +298,7 @@ const CLASSIC_DOMAIN_SEPARATOR = ',';
160
298
  * ads.js^$script,domains=example.com|~example.org
161
299
  * ```
162
300
  */
163
- const MODIFIER_DOMAIN_SEPARATOR = '|';
164
- const DOMAIN_LIST_TYPE = 'DomainList';
301
+ const PIPE_MODIFIER_SEPARATOR = '|';
165
302
  const CSS_IMPORTANT = '!important';
166
303
  const HINT_MARKER = '!+';
167
304
  const HINT_MARKER_LEN = HINT_MARKER.length;
@@ -794,6 +931,27 @@ exports.RuleCategory = void 0;
794
931
  */
795
932
  RuleCategory["Network"] = "Network";
796
933
  })(exports.RuleCategory || (exports.RuleCategory = {}));
934
+ /**
935
+ * Represents similar types of modifiers values
936
+ * which may be separated by a comma `,` (only for DomainList) or a pipe `|`.
937
+ */
938
+ var ListNodeType;
939
+ (function (ListNodeType) {
940
+ ListNodeType["AppList"] = "AppList";
941
+ ListNodeType["DomainList"] = "DomainList";
942
+ ListNodeType["MethodList"] = "MethodList";
943
+ ListNodeType["StealthOptionList"] = "StealthOptionList";
944
+ })(ListNodeType || (ListNodeType = {}));
945
+ /**
946
+ * Represents child items for {@link ListNodeType}.
947
+ */
948
+ var ListItemNodeType;
949
+ (function (ListItemNodeType) {
950
+ ListItemNodeType["App"] = "App";
951
+ ListItemNodeType["Domain"] = "Domain";
952
+ ListItemNodeType["Method"] = "Method";
953
+ ListItemNodeType["StealthOption"] = "StealthOption";
954
+ })(ListItemNodeType || (ListItemNodeType = {}));
797
955
  /**
798
956
  * Represents possible comment types.
799
957
  */
@@ -2545,6 +2703,87 @@ class CommentRuleParser {
2545
2703
  }
2546
2704
  }
2547
2705
 
2706
+ /**
2707
+ * Prefixes for error messages which are used for parsing of value lists.
2708
+ */
2709
+ const LIST_PARSE_ERROR_PREFIX = {
2710
+ EMPTY_ITEM: 'Empty value specified in the list',
2711
+ NO_MULTIPLE_NEGATION: 'Exception marker cannot be followed by another exception marker',
2712
+ NO_SEPARATOR_AFTER_NEGATION: 'Exception marker cannot be followed by a separator',
2713
+ NO_SEPARATOR_AT_THE_END: 'Value list cannot end with a separator',
2714
+ NO_WHITESPACE_AFTER_NEGATION: 'Exception marker cannot be followed by whitespace',
2715
+ };
2716
+ /**
2717
+ * Parses a `raw` modifier value which may be represented as a list of items separated by `separator`.
2718
+ * Needed for $app, $denyallow, $domain, $method.
2719
+ *
2720
+ * @param raw Raw modifier value.
2721
+ * @param separator Separator character.
2722
+ * @param loc Location of the modifier value.
2723
+ *
2724
+ * @returns List AST children — {@link App} | {@link Domain} | {@link Method} —
2725
+ * but with no `type` specified (see {@link ListItemNoType}).
2726
+ * @throws An {@link AdblockSyntaxError} if the list is syntactically invalid
2727
+ *
2728
+ * @example
2729
+ * - parses an app list — `com.example.app|Example.exe`
2730
+ * - parses a domain list — `example.com,example.org,~example.org` or `example.com|~example.org`
2731
+ * - parses a method list — `~post|~put`
2732
+ */
2733
+ const parseListItems = (raw, separator, loc = defaultLocation) => {
2734
+ const rawListItems = [];
2735
+ // If the last character is a separator, then the list item is invalid
2736
+ // and no need to continue parsing
2737
+ const realEndIndex = StringUtils.skipWSBack(raw);
2738
+ if (raw[realEndIndex] === separator) {
2739
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_SEPARATOR_AT_THE_END, locRange(loc, realEndIndex, realEndIndex + 1));
2740
+ }
2741
+ let offset = 0;
2742
+ // Skip whitespace before the list
2743
+ offset = StringUtils.skipWS(raw, offset);
2744
+ // Split list items by unescaped separators
2745
+ while (offset < raw.length) {
2746
+ // Skip whitespace before the list item
2747
+ offset = StringUtils.skipWS(raw, offset);
2748
+ let itemStart = offset;
2749
+ // Find the index of the first unescaped separator character
2750
+ const separatorStartIndex = StringUtils.findNextUnescapedCharacter(raw, separator, offset);
2751
+ const itemEnd = separatorStartIndex === -1
2752
+ ? StringUtils.skipWSBack(raw) + 1
2753
+ : StringUtils.skipWSBack(raw, separatorStartIndex - 1) + 1;
2754
+ const exception = raw[itemStart] === NEGATION_MARKER;
2755
+ // Skip the exception marker
2756
+ if (exception) {
2757
+ itemStart += 1;
2758
+ // Exception marker cannot be followed by another exception marker
2759
+ if (raw[itemStart] === NEGATION_MARKER) {
2760
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_MULTIPLE_NEGATION, locRange(loc, itemStart, itemStart + 1));
2761
+ }
2762
+ // Exception marker cannot be followed by a separator
2763
+ if (raw[itemStart] === separator) {
2764
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_SEPARATOR_AFTER_NEGATION, locRange(loc, itemStart, itemStart + 1));
2765
+ }
2766
+ // Exception marker cannot be followed by whitespace
2767
+ if (StringUtils.isWhitespace(raw[itemStart])) {
2768
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.NO_WHITESPACE_AFTER_NEGATION, locRange(loc, itemStart, itemStart + 1));
2769
+ }
2770
+ }
2771
+ // List item can't be empty
2772
+ if (itemStart === itemEnd) {
2773
+ throw new AdblockSyntaxError(LIST_PARSE_ERROR_PREFIX.EMPTY_ITEM, locRange(loc, itemStart, raw.length));
2774
+ }
2775
+ // Collect list item
2776
+ rawListItems.push({
2777
+ loc: locRange(loc, itemStart, itemEnd),
2778
+ value: raw.substring(itemStart, itemEnd),
2779
+ exception,
2780
+ });
2781
+ // Increment the offset to the next list item (or the end of the string)
2782
+ offset = separatorStartIndex === -1 ? raw.length : separatorStartIndex + 1;
2783
+ }
2784
+ return rawListItems;
2785
+ };
2786
+
2548
2787
  /**
2549
2788
  * `DomainListParser` is responsible for parsing a domain list.
2550
2789
  *
@@ -2558,83 +2797,39 @@ class DomainListParser {
2558
2797
  /**
2559
2798
  * Parses a domain list, eg. `example.com,example.org,~example.org`
2560
2799
  *
2561
- * @param raw Raw domain list
2562
- * @param separator Separator character
2563
- * @param loc Location of the domain list
2564
- * @returns Domain list AST
2565
- * @throws If the domain list is syntactically invalid
2800
+ * @param raw Raw domain list.
2801
+ * @param separator Separator character.
2802
+ * @param loc Location of the domain list in the rule. If not set, the default location is used.
2803
+ *
2804
+ * @returns Domain list AST.
2805
+ * @throws An {@link AdblockSyntaxError} if the domain list is syntactically invalid.
2566
2806
  */
2567
- static parse(raw, separator = CLASSIC_DOMAIN_SEPARATOR, loc = defaultLocation) {
2568
- const result = {
2569
- type: DOMAIN_LIST_TYPE,
2807
+ static parse(raw, separator = COMMA_DOMAIN_LIST_SEPARATOR, loc = defaultLocation) {
2808
+ const rawItems = parseListItems(raw, separator, loc);
2809
+ const children = rawItems.map((rawListItem) => ({
2810
+ ...rawListItem,
2811
+ type: ListItemNodeType.Domain,
2812
+ }));
2813
+ return {
2814
+ type: ListNodeType.DomainList,
2570
2815
  loc: locRange(loc, 0, raw.length),
2571
2816
  separator,
2572
- children: [],
2817
+ children,
2573
2818
  };
2574
- // If the last character is a separator, then the domain list is invalid
2575
- // and no need to continue parsing
2576
- const realEndIndex = StringUtils.skipWSBack(raw);
2577
- if (raw[realEndIndex] === separator) {
2578
- throw new AdblockSyntaxError('Domain list cannot end with a separator', locRange(loc, realEndIndex, realEndIndex + 1));
2579
- }
2580
- let offset = 0;
2581
- // Skip whitespace before the domain list
2582
- offset = StringUtils.skipWS(raw, offset);
2583
- // Split domains by unescaped separators
2584
- while (offset < raw.length) {
2585
- // Skip whitespace before the domain
2586
- offset = StringUtils.skipWS(raw, offset);
2587
- let domainStart = offset;
2588
- // Find the index of the first unescaped separator character
2589
- const separatorStartIndex = StringUtils.findNextUnescapedCharacter(raw, separator, offset);
2590
- const domainEnd = separatorStartIndex === -1
2591
- ? StringUtils.skipWSBack(raw) + 1
2592
- : StringUtils.skipWSBack(raw, separatorStartIndex - 1) + 1;
2593
- const exception = raw[domainStart] === DOMAIN_EXCEPTION_MARKER;
2594
- // Skip the exception marker
2595
- if (exception) {
2596
- domainStart += 1;
2597
- // Exception marker cannot be followed by another exception marker
2598
- if (raw[domainStart] === DOMAIN_EXCEPTION_MARKER) {
2599
- throw new AdblockSyntaxError('Exception marker cannot be followed by another exception marker', locRange(loc, domainStart, domainStart + 1));
2600
- }
2601
- // Exception marker cannot be followed by a separator
2602
- if (raw[domainStart] === separator) {
2603
- throw new AdblockSyntaxError('Exception marker cannot be followed by a separator', locRange(loc, domainStart, domainStart + 1));
2604
- }
2605
- // Exception marker cannot be followed by whitespace
2606
- if (StringUtils.isWhitespace(raw[domainStart])) {
2607
- throw new AdblockSyntaxError('Exception marker cannot be followed by whitespace', locRange(loc, domainStart, domainStart + 1));
2608
- }
2609
- }
2610
- // Domain can't be empty
2611
- if (domainStart === domainEnd) {
2612
- throw new AdblockSyntaxError('Empty domain specified', locRange(loc, domainStart, raw.length));
2613
- }
2614
- // Add the domain to the result
2615
- result.children.push({
2616
- type: 'Domain',
2617
- loc: locRange(loc, domainStart, domainEnd),
2618
- value: raw.substring(domainStart, domainEnd),
2619
- exception,
2620
- });
2621
- // Increment the offset to the next domain (or the end of the string)
2622
- offset = separatorStartIndex === -1 ? raw.length : separatorStartIndex + 1;
2623
- }
2624
- return result;
2625
2819
  }
2626
2820
  /**
2627
2821
  * Converts a domain list AST to a string.
2628
2822
  *
2629
- * @param ast Domain list AST
2630
- * @returns Raw string
2823
+ * @param ast Domain list AST.
2824
+ *
2825
+ * @returns Raw string.
2631
2826
  */
2632
2827
  static generate(ast) {
2633
2828
  const result = ast.children
2634
2829
  .map(({ value, exception }) => {
2635
2830
  let subresult = EMPTY;
2636
2831
  if (exception) {
2637
- subresult += DOMAIN_EXCEPTION_MARKER;
2832
+ subresult += NEGATION_MARKER;
2638
2833
  }
2639
2834
  subresult += value.trim();
2640
2835
  return subresult;
@@ -2668,8 +2863,8 @@ class ModifierParser {
2668
2863
  const modifierStart = offset;
2669
2864
  // Check if the modifier is an exception
2670
2865
  let exception = false;
2671
- if (raw[offset] === MODIFIER_EXCEPTION_MARKER) {
2672
- offset += MODIFIER_EXCEPTION_MARKER.length;
2866
+ if (raw[offset] === NEGATION_MARKER) {
2867
+ offset += NEGATION_MARKER.length;
2673
2868
  exception = true;
2674
2869
  }
2675
2870
  // Skip whitespace after the exception marker (if any)
@@ -2733,7 +2928,7 @@ class ModifierParser {
2733
2928
  static generate(modifier) {
2734
2929
  let result = EMPTY;
2735
2930
  if (modifier.exception) {
2736
- result += MODIFIER_EXCEPTION_MARKER;
2931
+ result += NEGATION_MARKER;
2737
2932
  }
2738
2933
  result += modifier.modifier.value;
2739
2934
  if (modifier.value !== undefined) {
@@ -5581,6 +5776,102 @@ class RuleParser {
5581
5776
  }
5582
5777
  }
5583
5778
 
5779
+ /**
5780
+ * `AppListParser` is responsible for parsing an app list.
5781
+ *
5782
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier}
5783
+ */
5784
+ class AppListParser {
5785
+ /**
5786
+ * Parses an app list which items are separated by `|`,
5787
+ * e.g. `Example.exe|com.example.osx`.
5788
+ *
5789
+ * @param raw Raw app list
5790
+ * @param loc Location of the app list in the rule. If not set, the default location is used.
5791
+ *
5792
+ * @returns App list AST.
5793
+ * @throws An {@link AdblockSyntaxError} if the app list is syntactically invalid.
5794
+ */
5795
+ static parse(raw, loc = defaultLocation) {
5796
+ const separator = PIPE_MODIFIER_SEPARATOR;
5797
+ const rawItems = parseListItems(raw, separator, loc);
5798
+ const children = rawItems.map((rawListItem) => ({
5799
+ ...rawListItem,
5800
+ type: ListItemNodeType.App,
5801
+ }));
5802
+ return {
5803
+ type: ListNodeType.AppList,
5804
+ loc: locRange(loc, 0, raw.length),
5805
+ separator,
5806
+ children,
5807
+ };
5808
+ }
5809
+ }
5810
+
5811
+ /**
5812
+ * `MethodListParser` is responsible for parsing a method list.
5813
+ *
5814
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier}
5815
+ */
5816
+ class MethodListParser {
5817
+ /**
5818
+ * Parses a method list which items are separated by `|`,
5819
+ * e.g. `get|post|put`.
5820
+ *
5821
+ * @param raw Raw method list
5822
+ * @param loc Location of the method list in the rule. If not set, the default location is used.
5823
+ *
5824
+ * @returns Method list AST.
5825
+ * @throws An {@link AdblockSyntaxError} if the method list is syntactically invalid.
5826
+ */
5827
+ static parse(raw, loc = defaultLocation) {
5828
+ const separator = PIPE_MODIFIER_SEPARATOR;
5829
+ const rawItems = parseListItems(raw, separator, loc);
5830
+ const children = rawItems.map((rawListItem) => ({
5831
+ ...rawListItem,
5832
+ type: ListItemNodeType.Method,
5833
+ }));
5834
+ return {
5835
+ type: ListNodeType.MethodList,
5836
+ loc: locRange(loc, 0, raw.length),
5837
+ separator,
5838
+ children,
5839
+ };
5840
+ }
5841
+ }
5842
+
5843
+ /**
5844
+ * `StealthOptionListParser` is responsible for parsing a list of stealth options.
5845
+ *
5846
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier}
5847
+ */
5848
+ class StealthOptionListParser {
5849
+ /**
5850
+ * Parses a stealth option list which items are separated by `|`,
5851
+ * e.g. `dpi|ip`.
5852
+ *
5853
+ * @param raw Raw list of stealth options.
5854
+ * @param loc Location of the stealth option list in the rule. If not set, the default location is used.
5855
+ *
5856
+ * @returns Stealth option list AST.
5857
+ * @throws An {@link AdblockSyntaxError} if the stealth option list is syntactically invalid.
5858
+ */
5859
+ static parse(raw, loc = defaultLocation) {
5860
+ const separator = PIPE_MODIFIER_SEPARATOR;
5861
+ const rawItems = parseListItems(raw, separator, loc);
5862
+ const children = rawItems.map((rawListItem) => ({
5863
+ ...rawListItem,
5864
+ type: ListItemNodeType.StealthOption,
5865
+ }));
5866
+ return {
5867
+ type: ListNodeType.StealthOptionList,
5868
+ loc: locRange(loc, 0, raw.length),
5869
+ separator,
5870
+ children,
5871
+ };
5872
+ }
5873
+ }
5874
+
5584
5875
  /**
5585
5876
  * `FilterListParser` is responsible for parsing a whole adblock filter list (list of rules).
5586
5877
  * It is a wrapper around `RuleParser` which parses each line separately.
@@ -5791,7 +6082,8 @@ var data$S = { adg_os_any:{ name:"app",
5791
6082
  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.",
5792
6083
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier",
5793
6084
  assignable:true,
5794
- negatable:false } };
6085
+ negatable:false,
6086
+ value_format:"pipe_separated_apps" } };
5795
6087
 
5796
6088
  var data$R = { adg_os_any:{ name:"badfilter",
5797
6089
  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).",
@@ -5852,7 +6144,7 @@ var data$N = { adg_os_any:{ name:"csp",
5852
6144
  assignable:true,
5853
6145
  negatable:false,
5854
6146
  value_optional:true,
5855
- value_format:"/[^,$]+/" },
6147
+ 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,}" },
5856
6148
  adg_ext_any:{ name:"csp",
5857
6149
  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.",
5858
6150
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
@@ -5864,7 +6156,7 @@ var data$N = { adg_os_any:{ name:"csp",
5864
6156
  assignable:true,
5865
6157
  negatable:false,
5866
6158
  value_optional:true,
5867
- value_format:"/[^,$]+/" },
6159
+ 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,}" },
5868
6160
  abp_ext_any:{ name:"csp",
5869
6161
  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.",
5870
6162
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#content-security-policies",
@@ -5874,7 +6166,7 @@ var data$N = { adg_os_any:{ name:"csp",
5874
6166
  assignable:true,
5875
6167
  negatable:false,
5876
6168
  value_optional:true,
5877
- value_format:"/[^,$]+/" },
6169
+ 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,}" },
5878
6170
  ubo_ext_any:{ name:"csp",
5879
6171
  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.",
5880
6172
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#csp",
@@ -5886,7 +6178,7 @@ var data$N = { adg_os_any:{ name:"csp",
5886
6178
  assignable:true,
5887
6179
  negatable:false,
5888
6180
  value_optional:true,
5889
- value_format:"/[^,$]+/" } };
6181
+ 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,}" } };
5890
6182
 
5891
6183
  var data$M = { adg_os_any:{ name:"denyallow",
5892
6184
  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.",
@@ -5894,35 +6186,35 @@ var data$M = { adg_os_any:{ name:"denyallow",
5894
6186
  conflicts:[ "to" ],
5895
6187
  assignable:true,
5896
6188
  negatable:false,
5897
- value_format:"pipe_separated_domains" },
6189
+ value_format:"pipe_separated_denyallow_domains" },
5898
6190
  adg_ext_any:{ name:"denyallow",
5899
6191
  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.",
5900
6192
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5901
6193
  conflicts:[ "to" ],
5902
6194
  assignable:true,
5903
6195
  negatable:false,
5904
- value_format:"pipe_separated_domains" },
6196
+ value_format:"pipe_separated_denyallow_domains" },
5905
6197
  adg_cb_ios:{ name:"denyallow",
5906
6198
  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.",
5907
6199
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5908
6200
  conflicts:[ "to" ],
5909
6201
  assignable:true,
5910
6202
  negatable:false,
5911
- value_format:"pipe_separated_domains" },
6203
+ value_format:"pipe_separated_denyallow_domains" },
5912
6204
  adg_cb_safari:{ name:"denyallow",
5913
6205
  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.",
5914
6206
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5915
6207
  conflicts:[ "to" ],
5916
6208
  assignable:true,
5917
6209
  negatable:false,
5918
- value_format:"pipe_separated_domains" },
6210
+ value_format:"pipe_separated_denyallow_domains" },
5919
6211
  ubo_ext_any:{ name:"denyallow",
5920
6212
  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.",
5921
6213
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#denyallow",
5922
6214
  conflicts:[ "to" ],
5923
6215
  assignable:true,
5924
6216
  negatable:false,
5925
- value_format:"pipe_separated_domains" } };
6217
+ value_format:"pipe_separated_denyallow_domains" } };
5926
6218
 
5927
6219
  var data$L = { adg_os_any:{ name:"document",
5928
6220
  description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
@@ -6138,17 +6430,17 @@ var data$C = { adg_os_any:{ name:"header",
6138
6430
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6139
6431
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6140
6432
  assignable:true,
6141
- value_format:"/^[A-z0-9-]+(:.+|)$/" },
6433
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" },
6142
6434
  adg_ext_any:{ name:"header",
6143
6435
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6144
6436
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6145
6437
  assignable:true,
6146
- value_format:"/^[A-z0-9-]+(:.+|)$/" },
6438
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" },
6147
6439
  ubo_ext_any:{ name:"header",
6148
6440
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6149
6441
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#header",
6150
6442
  assignable:true,
6151
- value_format:"/^[A-z0-9-]+(:.+|)$/" } };
6443
+ value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" } };
6152
6444
 
6153
6445
  var data$B = { adg_os_any:{ name:"hls",
6154
6446
  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).",
@@ -6163,7 +6455,8 @@ var data$B = { adg_os_any:{ name:"hls",
6163
6455
  inverse_conflicts:true,
6164
6456
  assignable:true,
6165
6457
  negatable:false,
6166
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6458
+ value_optional:true,
6459
+ 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 )" } };
6167
6460
 
6168
6461
  var data$A = { adg_any:{ name:"image",
6169
6462
  description:"The rule corresponds to images requests.",
@@ -6268,7 +6561,8 @@ var data$v = { adg_os_any:{ name:"jsonprune",
6268
6561
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
6269
6562
  assignable:true,
6270
6563
  negatable:false,
6271
- value_format:"/^\\\\\\$\\.(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6564
+ value_optional:true,
6565
+ 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 $" } };
6272
6566
 
6273
6567
  var data$u = { adg_any:{ name:"match-case",
6274
6568
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
@@ -6295,19 +6589,19 @@ var data$s = { adg_os_any:{ name:"method",
6295
6589
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6296
6590
  negatable:false,
6297
6591
  assignable:true,
6298
- 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|(\\|?|~?))+(?<!\\|)$" },
6592
+ value_format:"pipe_separated_methods" },
6299
6593
  adg_ext_any:{ name:"method",
6300
6594
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6301
6595
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6302
6596
  negatable:false,
6303
6597
  assignable:true,
6304
- 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|(\\|?|~?))+(?<!\\|)$" },
6598
+ value_format:"pipe_separated_methods" },
6305
6599
  ubo_ext_any:{ name:"method",
6306
6600
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6307
6601
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#method",
6308
6602
  negatable:false,
6309
6603
  assignable:true,
6310
- 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|(\\|?|~?))+(?<!\\|)$" } };
6604
+ value_format:"pipe_separated_methods" } };
6311
6605
 
6312
6606
  var data$r = { adg_os_any:{ name:"mp4",
6313
6607
  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.",
@@ -6391,7 +6685,7 @@ var data$l = { adg_os_any:{ name:"permissions",
6391
6685
  inverse_conflicts:true,
6392
6686
  assignable:true,
6393
6687
  negatable:false,
6394
- 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 )=\\(\\)(\\\\, )?)+(?<!,)$" } };
6688
+ 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 $" } };
6395
6689
 
6396
6690
  var data$k = { adg_any:{ name:"ping",
6397
6691
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
@@ -6519,14 +6813,14 @@ var data$g = { adg_os_any:{ name:"redirect",
6519
6813
  assignable:true,
6520
6814
  negatable:false,
6521
6815
  value_optional:true,
6522
- 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 )?$" },
6816
+ 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 )?$" },
6523
6817
  adg_ext_any:{ name:"redirect",
6524
6818
  description:"Used to redirect web requests to a local \"resource\".",
6525
6819
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6526
6820
  assignable:true,
6527
6821
  negatable:false,
6528
6822
  value_optional:true,
6529
- 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 )?$" },
6823
+ 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 )?$" },
6530
6824
  ubo_ext_any:{ name:"redirect",
6531
6825
  description:"Used to redirect web requests to a local \"resource\".",
6532
6826
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect",
@@ -6602,20 +6896,20 @@ var data$e = { adg_os_any:{ name:"removeparam",
6602
6896
  assignable:true,
6603
6897
  negatable:false,
6604
6898
  value_optional:true,
6605
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6899
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" },
6606
6900
  adg_ext_any:{ name:"removeparam",
6607
6901
  description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6608
6902
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
6609
6903
  assignable:true,
6610
6904
  negatable:false,
6611
6905
  value_optional:true,
6612
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6906
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" },
6613
6907
  ubo_ext_any:{ name:"removeparam",
6614
6908
  description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6615
6909
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#removeparam",
6616
6910
  assignable:true,
6617
6911
  negatable:false,
6618
- value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6912
+ value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n \\/.+\\/\n # flags\n ([gimuy]+)?\n )" } };
6619
6913
 
6620
6914
  var data$d = { adg_os_any:{ 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.",
@@ -6635,7 +6929,7 @@ var data$d = { adg_os_any:{ name:"replace",
6635
6929
  inverse_conflicts:true,
6636
6930
  assignable:true,
6637
6931
  negatable:false,
6638
- value_format:"/^\\/.+\\/.*\\/$/" },
6932
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" },
6639
6933
  adg_ext_firefox:{ name:"replace",
6640
6934
  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.",
6641
6935
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
@@ -6653,7 +6947,7 @@ var data$d = { adg_os_any:{ name:"replace",
6653
6947
  inverse_conflicts:true,
6654
6948
  assignable:true,
6655
6949
  negatable:false,
6656
- value_format:"/^\\/.+\\/.*\\/$/" } };
6950
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" } };
6657
6951
 
6658
6952
  var data$c = { adg_any:{ name:"script",
6659
6953
  description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
@@ -6697,7 +6991,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6697
6991
  negatable:false,
6698
6992
  exception_only:true,
6699
6993
  value_optional:true,
6700
- 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)+(?<!\\|)$" },
6994
+ value_format:"pipe_separated_stealth_options" },
6701
6995
  adg_ext_chrome:{ name:"stealth",
6702
6996
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6703
6997
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6705,7 +6999,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6705
6999
  negatable:false,
6706
7000
  exception_only:true,
6707
7001
  value_optional:true,
6708
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n xclientdata\n |\\|?\n )\\b)+(?<!\\|)$" },
7002
+ value_format:"pipe_separated_stealth_options" },
6709
7003
  adg_ext_firefox:{ name:"stealth",
6710
7004
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6711
7005
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6713,7 +7007,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6713
7007
  negatable:false,
6714
7008
  exception_only:true,
6715
7009
  value_optional:true,
6716
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
7010
+ value_format:"pipe_separated_stealth_options" },
6717
7011
  adg_ext_opera:{ name:"stealth",
6718
7012
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6719
7013
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6721,7 +7015,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6721
7015
  negatable:false,
6722
7016
  exception_only:true,
6723
7017
  value_optional:true,
6724
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
7018
+ value_format:"pipe_separated_stealth_options" },
6725
7019
  adg_ext_edge:{ name:"stealth",
6726
7020
  description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6727
7021
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
@@ -6729,7 +7023,7 @@ var data$a = { adg_os_any:{ name:"stealth",
6729
7023
  negatable:false,
6730
7024
  exception_only:true,
6731
7025
  value_optional:true,
6732
- value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" } };
7026
+ value_format:"pipe_separated_stealth_options" } };
6733
7027
 
6734
7028
  var data$9 = { ubo_any:{ name:"strict1p",
6735
7029
  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.",
@@ -6932,8 +7226,6 @@ var SpecificKey;
6932
7226
  SpecificKey["Negatable"] = "negatable";
6933
7227
  SpecificKey["BlockOnly"] = "block_only";
6934
7228
  SpecificKey["ExceptionOnly"] = "exception_only";
6935
- // TODO: consider removing this field
6936
- // and handle whether the value is optional by `value_format`. AG-24028
6937
7229
  SpecificKey["ValueOptional"] = "value_optional";
6938
7230
  SpecificKey["ValueFormat"] = "value_format";
6939
7231
  // TODO: following fields should be handled later
@@ -6997,25 +7289,123 @@ const getModifiersData = () => {
6997
7289
  return dataMap;
6998
7290
  };
6999
7291
 
7000
- const INVALID_ERROR_PREFIX = {
7001
- NOT_EXISTENT: 'Not existent modifier',
7002
- NOT_SUPPORTED: 'The adblocker does not support the modifier',
7003
- REMOVED: 'Removed and no longer supported modifier',
7292
+ /**
7293
+ * Prefixes for different adblockers to describe the platform-specific modifiers data
7294
+ * stored in the yaml files.
7295
+ */
7296
+ const BLOCKER_PREFIX = {
7297
+ [exports.AdblockSyntax.Adg]: 'adg_',
7298
+ [exports.AdblockSyntax.Ubo]: 'ubo_',
7299
+ [exports.AdblockSyntax.Abp]: 'abp_',
7300
+ };
7301
+ /**
7302
+ * Set of all allowed characters for app name except the dot `.`.
7303
+ */
7304
+ const APP_NAME_ALLOWED_CHARS = new Set([
7305
+ ...CAPITAL_LETTERS,
7306
+ ...SMALL_LETTERS,
7307
+ ...NUMBERS,
7308
+ UNDERSCORE,
7309
+ ]);
7310
+ /**
7311
+ * Allowed methods for $method modifier.
7312
+ *
7313
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier}
7314
+ */
7315
+ const ALLOWED_METHODS = new Set([
7316
+ 'connect',
7317
+ 'delete',
7318
+ 'get',
7319
+ 'head',
7320
+ 'options',
7321
+ 'patch',
7322
+ 'post',
7323
+ 'put',
7324
+ 'trace',
7325
+ ]);
7326
+ /**
7327
+ * Allowed stealth options for $stealth modifier.
7328
+ *
7329
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier}
7330
+ */
7331
+ const ALLOWED_STEALTH_OPTIONS = new Set([
7332
+ 'searchqueries',
7333
+ 'donottrack',
7334
+ '3p-cookie',
7335
+ '1p-cookie',
7336
+ '3p-cache',
7337
+ '3p-auth',
7338
+ 'webrtc',
7339
+ 'push',
7340
+ 'location',
7341
+ 'flash',
7342
+ 'java',
7343
+ 'referrer',
7344
+ 'useragent',
7345
+ 'ip',
7346
+ 'xclientdata',
7347
+ 'dpi',
7348
+ ]);
7349
+ /**
7350
+ * Prefixes for error messages used in modifier validation.
7351
+ */
7352
+ const VALIDATION_ERROR_PREFIX = {
7004
7353
  BLOCK_ONLY: 'Only blocking rules may contain the modifier',
7005
7354
  EXCEPTION_ONLY: 'Only exception rules may contain the modifier',
7006
- NOT_NEGATABLE: 'Not negatable modifier',
7007
- VALUE_REQUIRED: 'Value is required for the modifier',
7008
- VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
7355
+ INVALID_LIST_VALUES: 'Invalid values for the modifier',
7009
7356
  INVALID_NOOP: 'Invalid noop modifier',
7357
+ MIXED_NEGATIONS: 'Simultaneous usage of negated and not negated values is forbidden for the modifier',
7358
+ NOT_EXISTENT: 'Non-existent modifier',
7359
+ NOT_NEGATABLE_MODIFIER: 'Non-negatable modifier',
7360
+ NOT_NEGATABLE_VALUE: 'Values cannot be negated for the modifier',
7361
+ NOT_SUPPORTED: 'The adblocker does not support the modifier',
7362
+ REMOVED: 'Removed and no longer supported modifier',
7363
+ VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
7364
+ VALUE_INVALID: 'Value is invalid for the modifier',
7365
+ VALUE_REQUIRED: 'Value is required for the modifier',
7366
+ };
7367
+ /**
7368
+ * Prefixes for error messages related to issues in the source YAML files' data.
7369
+ */
7370
+ const SOURCE_DATA_ERROR_PREFIX = {
7371
+ INVALID_VALUE_FORMAT_REGEXP: "Invalid regular expression in 'value_format' for the modifier",
7372
+ NO_DEPRECATION_MESSAGE: "Property 'deprecation_message' is required for the 'deprecated' modifier",
7373
+ NO_VALUE_FORMAT_FOR_ASSIGNABLE: "Property 'value_format' should be specified for the assignable modifier",
7010
7374
  };
7011
7375
 
7012
7376
  /**
7013
- * @file Validator for modifiers.
7377
+ * Validates the noop modifier (i.e. only underscores).
7378
+ *
7379
+ * @param value Value of the modifier.
7380
+ *
7381
+ * @returns True if the modifier is valid, false otherwise.
7014
7382
  */
7015
- const BLOCKER_PREFIX = {
7016
- [exports.AdblockSyntax.Adg]: 'adg_',
7017
- [exports.AdblockSyntax.Ubo]: 'ubo_',
7018
- [exports.AdblockSyntax.Abp]: 'abp_',
7383
+ const isValidNoopModifier = (value) => {
7384
+ return value.split('').every((char) => char === UNDERSCORE);
7385
+ };
7386
+ /**
7387
+ * Returns invalid validation result with given error message.
7388
+ *
7389
+ * @param error Error message.
7390
+ *
7391
+ * @returns Validation result `{ valid: false, error }`.
7392
+ */
7393
+ const getInvalidValidationResult = (error) => {
7394
+ return {
7395
+ valid: false,
7396
+ error,
7397
+ };
7398
+ };
7399
+ /**
7400
+ * Returns invalid validation result which uses {@link VALIDATION_ERROR_PREFIX.VALUE_REQUIRED} as prefix
7401
+ * and specifies the given `modifierName` in the error message.
7402
+ *
7403
+ * @param modifierName Modifier name.
7404
+ *
7405
+ * @returns Validation result `{ valid: false, error }`.
7406
+ */
7407
+ const getValueRequiredValidationResult = (modifierName) => {
7408
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
7019
7409
  };
7020
7410
  /**
7021
7411
  * Collects names and aliases for all supported modifiers.
@@ -7067,116 +7457,620 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
7067
7457
  });
7068
7458
  return specificBlockerData;
7069
7459
  };
7460
+
7070
7461
  /**
7071
- * Returns invalid validation result with given error message.
7072
- *
7073
- * @param error Error message.
7074
- *
7075
- * @returns Validation result `{ ok: false, error }`.
7462
+ * @file Utility functions for domain and hostname validation.
7076
7463
  */
7077
- const getInvalidValidationResult = (error) => {
7078
- return {
7079
- ok: false,
7080
- error,
7081
- };
7082
- };
7083
7464
  /**
7084
- * Fully checks whether the given `modifier` valid for given blocker `syntax`:
7085
- * is it supported by the blocker, deprecated, assignable, negatable, etc.
7465
+ * Marker for a wildcard top-level domain `.*`.
7086
7466
  *
7087
- * @param modifiersData Parsed all modifiers data map.
7088
- * @param syntax Adblock syntax to check the modifier for.
7089
- * 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
7090
- * @param modifier Parsed modifier AST node.
7091
- * @param isException Whether the modifier is used in exception rule.
7092
- * Needed to check whether the modifier is allowed only in blocking or exception rules.
7467
+ * @example
7468
+ * `example.*` matches with any TLD, e.g. `example.org`, `example.com`, etc.
7469
+ */
7470
+ const WILDCARD_TLD = DOT + WILDCARD$1;
7471
+ /**
7472
+ * Marker for a wildcard subdomain `*.`.
7093
7473
  *
7094
- * @returns Result of modifier validation.
7474
+ * @example
7475
+ * `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
7095
7476
  */
7096
- const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
7097
- if (syntax === exports.AdblockSyntax.Common) {
7098
- throw new Error(`Syntax should be specific, '${exports.AdblockSyntax.Common}' is not supported`);
7099
- }
7100
- const modifierName = modifier.modifier.value;
7101
- const blockerPrefix = BLOCKER_PREFIX[syntax];
7102
- if (!blockerPrefix) {
7103
- throw new Error(`Unknown syntax: ${syntax}`);
7104
- }
7105
- // needed for validation of negation, assignment, etc.
7106
- const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
7107
- // if no specific blocker data is found
7108
- if (!specificBlockerData) {
7109
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
7110
- }
7111
- // e.g. 'object-subrequest'
7112
- if (specificBlockerData.removed) {
7113
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
7114
- }
7115
- if (specificBlockerData.deprecated) {
7116
- if (!specificBlockerData.deprecation_message) {
7117
- throw new Error('Deprecation notice is required for deprecated modifier');
7477
+ const WILDCARD_SUBDOMAIN = WILDCARD$1 + DOT;
7478
+ class DomainUtils {
7479
+ /**
7480
+ * Check if the input is a valid domain or hostname.
7481
+ *
7482
+ * @param domain Domain to check
7483
+ * @returns `true` if the domain is valid, `false` otherwise
7484
+ */
7485
+ static isValidDomainOrHostname(domain) {
7486
+ let domainToCheck = domain;
7487
+ // Wildcard-only domain, typically a generic rule
7488
+ if (domainToCheck === WILDCARD$1) {
7489
+ return true;
7118
7490
  }
7119
- return {
7120
- ok: true,
7121
- warn: specificBlockerData.deprecation_message,
7122
- };
7123
- }
7124
- if (specificBlockerData.block_only && isException) {
7125
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
7126
- }
7127
- if (specificBlockerData.exception_only && !isException) {
7128
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
7129
- }
7130
- // e.g. '~domain=example.com'
7131
- if (!specificBlockerData.negatable && modifier.exception) {
7132
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_NEGATABLE}: '${modifierName}'`);
7133
- }
7134
- // e.g. 'domain'
7135
- if (specificBlockerData.assignable) {
7136
- /**
7137
- * Some assignable modifiers can be used without a value,
7138
- * e.g. '@@||example.com^$cookie'.
7139
- */
7140
- if (!modifier.value
7141
- // value should be specified if it is not optional
7142
- && !specificBlockerData.value_optional) {
7143
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
7491
+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
7492
+ if (domainToCheck.endsWith(WILDCARD_TLD)) {
7493
+ // Remove the wildcard TLD
7494
+ domainToCheck = domainToCheck.substring(0, domainToCheck.length - WILDCARD_TLD.length);
7144
7495
  }
7145
- /**
7146
- * TODO: consider to return `{ ok: true, warn: 'Modifier value may be specified' }` (???)
7147
- * for $stealth modifier without a value
7148
- * but only after the extension will support value for $stealth:
7149
- * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
7150
- */
7151
- }
7152
- else if (modifier?.value) {
7153
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
7496
+ if (domainToCheck.startsWith(WILDCARD_SUBDOMAIN)) {
7497
+ // Remove the wildcard subdomain
7498
+ domainToCheck = domainToCheck.substring(WILDCARD_SUBDOMAIN.length);
7499
+ }
7500
+ // Parse the domain with tldts
7501
+ const tldtsResult = tldts.parse(domainToCheck);
7502
+ // Check if the domain is valid
7503
+ return domainToCheck === tldtsResult.domain || domainToCheck === tldtsResult.hostname;
7154
7504
  }
7155
- return { ok: true };
7156
- };
7505
+ }
7506
+
7157
7507
  /**
7158
- * Returns documentation URL for given modifier and adblocker.
7159
- *
7160
- * @param modifiersData Parsed all modifiers data map.
7161
- * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
7162
- * @param modifier Parsed modifier AST node.
7163
- *
7164
- * @returns Documentation URL or `null` if not found.
7508
+ * @file Utility functions for working with quotes
7165
7509
  */
7166
- const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
7167
- const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
7168
- return specificBlockerData?.docs || null;
7510
+ /**
7511
+ * Possible quote types for scriptlet parameters
7512
+ */
7513
+ exports.QuoteType = void 0;
7514
+ (function (QuoteType) {
7515
+ /**
7516
+ * No quotes at all
7517
+ */
7518
+ QuoteType["None"] = "none";
7519
+ /**
7520
+ * Single quotes (`'`)
7521
+ */
7522
+ QuoteType["Single"] = "single";
7523
+ /**
7524
+ * Double quotes (`"`)
7525
+ */
7526
+ QuoteType["Double"] = "double";
7527
+ })(exports.QuoteType || (exports.QuoteType = {}));
7528
+ /**
7529
+ * Utility functions for working with quotes
7530
+ */
7531
+ class QuoteUtils {
7532
+ /**
7533
+ * Escape all unescaped occurrences of the character
7534
+ *
7535
+ * @param string String to escape
7536
+ * @param char Character to escape
7537
+ * @returns Escaped string
7538
+ */
7539
+ static escapeUnescapedOccurrences(string, char) {
7540
+ let result = EMPTY;
7541
+ for (let i = 0; i < string.length; i += 1) {
7542
+ if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
7543
+ result += ESCAPE_CHARACTER;
7544
+ }
7545
+ result += string[i];
7546
+ }
7547
+ return result;
7548
+ }
7549
+ /**
7550
+ * Unescape all single escaped occurrences of the character
7551
+ *
7552
+ * @param string String to unescape
7553
+ * @param char Character to unescape
7554
+ * @returns Unescaped string
7555
+ */
7556
+ static unescapeSingleEscapedOccurrences(string, char) {
7557
+ let result = EMPTY;
7558
+ for (let i = 0; i < string.length; i += 1) {
7559
+ if (string[i] === char
7560
+ && string[i - 1] === ESCAPE_CHARACTER
7561
+ && (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
7562
+ result = result.slice(0, -1);
7563
+ }
7564
+ result += string[i];
7565
+ }
7566
+ return result;
7567
+ }
7568
+ /**
7569
+ * Get quote type of the string
7570
+ *
7571
+ * @param string String to check
7572
+ * @returns Quote type of the string
7573
+ */
7574
+ static getStringQuoteType(string) {
7575
+ // Don't check 1-character strings to avoid false positives
7576
+ if (string.length > 1) {
7577
+ if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
7578
+ return exports.QuoteType.Single;
7579
+ }
7580
+ if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
7581
+ return exports.QuoteType.Double;
7582
+ }
7583
+ }
7584
+ return exports.QuoteType.None;
7585
+ }
7586
+ /**
7587
+ * Set quote type of the string
7588
+ *
7589
+ * @param string String to set quote type of
7590
+ * @param quoteType Quote type to set
7591
+ * @returns String with the specified quote type
7592
+ */
7593
+ static setStringQuoteType(string, quoteType) {
7594
+ const actualQuoteType = QuoteUtils.getStringQuoteType(string);
7595
+ switch (quoteType) {
7596
+ case exports.QuoteType.None:
7597
+ if (actualQuoteType === exports.QuoteType.Single) {
7598
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
7599
+ }
7600
+ if (actualQuoteType === exports.QuoteType.Double) {
7601
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
7602
+ }
7603
+ return string;
7604
+ case exports.QuoteType.Single:
7605
+ if (actualQuoteType === exports.QuoteType.None) {
7606
+ return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
7607
+ }
7608
+ if (actualQuoteType === exports.QuoteType.Double) {
7609
+ return SINGLE_QUOTE
7610
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
7611
+ }
7612
+ return string;
7613
+ case exports.QuoteType.Double:
7614
+ if (actualQuoteType === exports.QuoteType.None) {
7615
+ return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
7616
+ }
7617
+ if (actualQuoteType !== exports.QuoteType.Double) {
7618
+ // eslint-disable-next-line max-len
7619
+ return DOUBLE_QUOTE
7620
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
7621
+ }
7622
+ return string;
7623
+ default:
7624
+ return string;
7625
+ }
7626
+ }
7627
+ /**
7628
+ * Removes bounding quotes from a string, if any
7629
+ *
7630
+ * @param string Input string
7631
+ * @returns String without quotes
7632
+ */
7633
+ static removeQuotes(string) {
7634
+ if (
7635
+ // We should check for string length to avoid false positives
7636
+ string.length > 1
7637
+ && (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
7638
+ && string[0] === string[string.length - 1]) {
7639
+ return string.slice(1, -1);
7640
+ }
7641
+ return string;
7642
+ }
7643
+ /**
7644
+ * Wraps given `strings` with `quote` (defaults to single quote `'`)
7645
+ * and joins them with `separator` (defaults to comma+space `, `).
7646
+ *
7647
+ * @param strings Strings to quote and join.
7648
+ * @param quoteType Quote to use.
7649
+ * @param separator Separator to use.
7650
+ *
7651
+ * @returns String with joined items.
7652
+ *
7653
+ * @example
7654
+ * ['abc', 'def']: strings[] -> "'abc', 'def'": string
7655
+ */
7656
+ static quoteAndJoinStrings(strings, quoteType = exports.QuoteType.Single, separator = `${COMMA}${SPACE}`) {
7657
+ return strings
7658
+ .map((s) => QuoteUtils.setStringQuoteType(s, quoteType))
7659
+ .join(separator);
7660
+ }
7661
+ }
7662
+
7663
+ /**
7664
+ * Pre-defined available validators for modifiers with custom `value_format`.
7665
+ */
7666
+ var CustomValueFormatValidatorName;
7667
+ (function (CustomValueFormatValidatorName) {
7668
+ CustomValueFormatValidatorName["App"] = "pipe_separated_apps";
7669
+ // there are some differences between $domain and $denyallow
7670
+ CustomValueFormatValidatorName["DenyAllow"] = "pipe_separated_denyallow_domains";
7671
+ CustomValueFormatValidatorName["Domain"] = "pipe_separated_domains";
7672
+ CustomValueFormatValidatorName["Method"] = "pipe_separated_methods";
7673
+ CustomValueFormatValidatorName["StealthOption"] = "pipe_separated_stealth_options";
7674
+ })(CustomValueFormatValidatorName || (CustomValueFormatValidatorName = {}));
7675
+ /**
7676
+ * Checks whether the `chunk` of app name (which if splitted by dot `.`) is valid.
7677
+ * Only letters, numbers, and underscore `_` are allowed.
7678
+ *
7679
+ * @param chunk Chunk of app name to check.
7680
+ *
7681
+ * @returns True if the `chunk` is valid part of app name, false otherwise.
7682
+ */
7683
+ const isValidAppNameChunk = (chunk) => {
7684
+ // e.g. 'Example..exe'
7685
+ if (chunk.length === 0) {
7686
+ return false;
7687
+ }
7688
+ for (let i = 0; i < chunk.length; i += 1) {
7689
+ const char = chunk[i];
7690
+ if (!APP_NAME_ALLOWED_CHARS.has(char)) {
7691
+ return false;
7692
+ }
7693
+ }
7694
+ return true;
7169
7695
  };
7170
7696
  /**
7171
- * Validates the noop modifier (i.e. only underscores).
7697
+ * Checks whether the given `value` is valid app name as $app modifier value.
7172
7698
  *
7173
- * @param value Value of the modifier.
7699
+ * @param value App name to check.
7174
7700
  *
7175
- * @returns True if the modifier is valid, false otherwise.
7701
+ * @returns True if the `value` is valid app name, false otherwise.
7176
7702
  */
7177
- const isValidNoopModifier = (value) => {
7178
- return value.split('').every((char) => char === UNDERSCORE);
7703
+ const isValidAppModifierValue = (value) => {
7704
+ // $app modifier does not support wildcard tld
7705
+ // https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
7706
+ if (value.includes(WILDCARD$1)) {
7707
+ return false;
7708
+ }
7709
+ return value
7710
+ .split(DOT)
7711
+ .every((chunk) => isValidAppNameChunk(chunk));
7712
+ };
7713
+ /**
7714
+ * Checks whether the given `value` is valid HTTP method as $method modifier value.
7715
+ *
7716
+ * @param value Method to check.
7717
+ *
7718
+ * @returns True if the `value` is valid HTTP method, false otherwise.
7719
+ */
7720
+ const isValidMethodModifierValue = (value) => {
7721
+ return ALLOWED_METHODS.has(value);
7722
+ };
7723
+ /**
7724
+ * Checks whether the given `value` is valid option as $stealth modifier value.
7725
+ *
7726
+ * @param value Stealth option to check.
7727
+ *
7728
+ * @returns True if the `value` is valid stealth option, false otherwise.
7729
+ */
7730
+ const isValidStealthModifierValue = (value) => {
7731
+ return ALLOWED_STEALTH_OPTIONS.has(value);
7732
+ };
7733
+ /**
7734
+ * Checks whether the given `value` is valid domain as $denyallow modifier value.
7735
+ * Important: wildcard tld are not supported, compared to $domain.
7736
+ *
7737
+ * @param value Value to check.
7738
+ *
7739
+ * @returns True if the `value` is valid domain and does not contain wildcard `*`, false otherwise.
7740
+ */
7741
+ const isValidDenyAllowModifierValue = (value) => {
7742
+ // $denyallow modifier does not support wildcard tld
7743
+ // https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
7744
+ // but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
7745
+ if (value.includes(WILDCARD$1)) {
7746
+ return false;
7747
+ }
7748
+ // TODO: add cache for domains validation
7749
+ return DomainUtils.isValidDomainOrHostname(value);
7750
+ };
7751
+ /**
7752
+ * Checks whether the given `value` is valid domain as $domain modifier value.
7753
+ *
7754
+ * @param value Value to check.
7755
+ *
7756
+ * @returns True if the `value` is valid domain, false otherwise.
7757
+ */
7758
+ const isValidDomainModifierValue = (value) => {
7759
+ // TODO: add cache for domains validation
7760
+ return DomainUtils.isValidDomainOrHostname(value);
7761
+ };
7762
+ /**
7763
+ * Checks whether the all list items' exceptions are `false`.
7764
+ * Those items which `exception` is `true` is to be specified in the validation result error message.
7765
+ *
7766
+ * @param modifierName Modifier name.
7767
+ * @param listItems List items to check.
7768
+ *
7769
+ * @returns Validation result.
7770
+ */
7771
+ const customNoNegatedListItemsValidator = (modifierName, listItems) => {
7772
+ const negatedValues = [];
7773
+ listItems.forEach((listItem) => {
7774
+ if (listItem.exception) {
7775
+ negatedValues.push(listItem.value);
7776
+ }
7777
+ });
7778
+ if (negatedValues.length > 0) {
7779
+ const valuesToStr = QuoteUtils.quoteAndJoinStrings(negatedValues);
7780
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_NEGATABLE_VALUE}: '${modifierName}': ${valuesToStr}`);
7781
+ }
7782
+ return { valid: true };
7783
+ };
7784
+ /**
7785
+ * Checks whether the all list items' exceptions are consistent,
7786
+ * i.e. all items are either negated or not negated.
7787
+ *
7788
+ * The `exception` value of the first item is used as a reference, and all other items are checked against it.
7789
+ * Those items which `exception` is not consistent with the first item
7790
+ * is to be specified in the validation result error message.
7791
+ *
7792
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#method-modifier}
7793
+ *
7794
+ * @param modifierName Modifier name.
7795
+ * @param listItems List items to check.
7796
+ *
7797
+ * @returns Validation result.
7798
+ */
7799
+ const customConsistentExceptionsValidator = (modifierName, listItems) => {
7800
+ const firstException = listItems[0].exception;
7801
+ const nonConsistentItemValues = [];
7802
+ listItems.forEach((listItem) => {
7803
+ if (listItem.exception !== firstException) {
7804
+ nonConsistentItemValues.push(listItem.value);
7805
+ }
7806
+ });
7807
+ if (nonConsistentItemValues.length > 0) {
7808
+ const valuesToStr = QuoteUtils.quoteAndJoinStrings(nonConsistentItemValues);
7809
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.MIXED_NEGATIONS}: '${modifierName}': ${valuesToStr}`);
7810
+ }
7811
+ return { valid: true };
7812
+ };
7813
+ /**
7814
+ * Checks whether the given `modifier` value is valid.
7815
+ * Supposed to validate the value of modifiers which values are lists separated by pipe `|` —
7816
+ * $app, $domain, $denyallow, $method.
7817
+ *
7818
+ * @param modifier Modifier AST node.
7819
+ * @param listParser Parser function for parsing modifier value
7820
+ * which is supposed to be a list separated by pipe `|`.
7821
+ * @param isValidListItem Predicate function for checking of modifier's list item validity,
7822
+ * e.g. $denyallow modifier does not support wildcard tld, but $domain does.
7823
+ * @param customListValidator Optional; custom validator for specific modifier,
7824
+ * e.g. $denyallow modifier does not support negated domains.
7825
+ *
7826
+ * @returns Result of modifier domains validation.
7827
+ */
7828
+ const validateListItemsModifier = (modifier, listParser, isValidListItem, customListValidator) => {
7829
+ const modifierName = modifier.modifier.value;
7830
+ const defaultInvalidValueResult = getValueRequiredValidationResult(modifierName);
7831
+ if (!modifier.value?.value) {
7832
+ return defaultInvalidValueResult;
7833
+ }
7834
+ let theList;
7835
+ try {
7836
+ theList = listParser(modifier.value.value, PIPE_MODIFIER_SEPARATOR);
7837
+ }
7838
+ catch (e) {
7839
+ if (e instanceof AdblockSyntaxError) {
7840
+ return {
7841
+ valid: false,
7842
+ error: e.message,
7843
+ };
7844
+ }
7845
+ return defaultInvalidValueResult;
7846
+ }
7847
+ const invalidListItems = [];
7848
+ theList.children.forEach((item) => {
7849
+ // different validators are used for $denyallow and $domain modifiers
7850
+ // because of different requirements and restrictions
7851
+ if (!isValidListItem(item.value)) {
7852
+ invalidListItems.push(item.value);
7853
+ }
7854
+ });
7855
+ if (invalidListItems.length > 0) {
7856
+ const itemsToStr = QuoteUtils.quoteAndJoinStrings(invalidListItems);
7857
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_LIST_VALUES}: '${modifierName}': ${itemsToStr}`);
7858
+ }
7859
+ // IMPORTANT: run custom validator after all other checks
7860
+ // Some lists should be fully checked, not just the list items:
7861
+ // e.g. Safari does not support allowed and disallowed domains for $domain in the same list
7862
+ // or domains cannot be negated for $denyallow modifier
7863
+ if (customListValidator) {
7864
+ return customListValidator(modifierName, theList.children);
7865
+ }
7866
+ return { valid: true };
7867
+ };
7868
+ /**
7869
+ * Validates 'pipe_separated_apps' custom value format.
7870
+ * Used for $app modifier.
7871
+ *
7872
+ * @param modifier Modifier AST node.
7873
+ *
7874
+ * @returns Validation result.
7875
+ */
7876
+ const validatePipeSeparatedApps = (modifier) => {
7877
+ return validateListItemsModifier(modifier, (raw) => AppListParser.parse(raw), isValidAppModifierValue);
7878
+ };
7879
+ /**
7880
+ * Validates 'pipe_separated_denyallow_domains' custom value format.
7881
+ * Used for $denyallow modifier.
7882
+ *
7883
+ * @param modifier Modifier AST node.
7884
+ *
7885
+ * @returns Validation result.
7886
+ */
7887
+ const validatePipeSeparatedDenyAllowDomains = (modifier) => {
7888
+ return validateListItemsModifier(modifier, DomainListParser.parse, isValidDenyAllowModifierValue, customNoNegatedListItemsValidator);
7889
+ };
7890
+ /**
7891
+ * Validates 'pipe_separated_domains' custom value format.
7892
+ * Used for $domains modifier.
7893
+ *
7894
+ * @param modifier Modifier AST node.
7895
+ *
7896
+ * @returns Validation result.
7897
+ */
7898
+ const validatePipeSeparatedDomains = (modifier) => {
7899
+ return validateListItemsModifier(modifier, DomainListParser.parse, isValidDomainModifierValue);
7900
+ };
7901
+ /**
7902
+ * Validates 'pipe_separated_methods' custom value format.
7903
+ * Used for $method modifier.
7904
+ *
7905
+ * @param modifier Modifier AST node.
7906
+ *
7907
+ * @returns Validation result.
7908
+ */
7909
+ const validatePipeSeparatedMethods = (modifier) => {
7910
+ return validateListItemsModifier(modifier, (raw) => MethodListParser.parse(raw), isValidMethodModifierValue, customConsistentExceptionsValidator);
7911
+ };
7912
+ /**
7913
+ * Validates 'pipe_separated_stealth_options' custom value format.
7914
+ * Used for $stealth modifier.
7915
+ *
7916
+ * @param modifier Modifier AST node.
7917
+ *
7918
+ * @returns Validation result.
7919
+ */
7920
+ const validatePipeSeparatedStealthOptions = (modifier) => {
7921
+ return validateListItemsModifier(modifier, (raw) => StealthOptionListParser.parse(raw), isValidStealthModifierValue, customNoNegatedListItemsValidator);
7922
+ };
7923
+ /**
7924
+ * Map of all available pre-defined validators for modifiers with custom `value_format`.
7925
+ */
7926
+ const CUSTOM_VALUE_FORMAT_MAP = {
7927
+ [CustomValueFormatValidatorName.App]: validatePipeSeparatedApps,
7928
+ [CustomValueFormatValidatorName.DenyAllow]: validatePipeSeparatedDenyAllowDomains,
7929
+ [CustomValueFormatValidatorName.Domain]: validatePipeSeparatedDomains,
7930
+ [CustomValueFormatValidatorName.Method]: validatePipeSeparatedMethods,
7931
+ [CustomValueFormatValidatorName.StealthOption]: validatePipeSeparatedStealthOptions,
7932
+ };
7933
+ /**
7934
+ * Returns whether the given `valueFormat` is a valid custom value format validator name.
7935
+ *
7936
+ * @param valueFormat Value format for the modifier.
7937
+ *
7938
+ * @returns True if `valueFormat` is a supported pre-defined value format validator name, false otherwise.
7939
+ */
7940
+ const isCustomValueFormatValidator = (valueFormat) => {
7941
+ return Object.keys(CUSTOM_VALUE_FORMAT_MAP).includes(valueFormat);
7942
+ };
7943
+ /**
7944
+ * Checks whether the value for given `modifier` is valid.
7945
+ *
7946
+ * @param modifier Modifier AST node.
7947
+ * @param valueFormat Value format for the modifier.
7948
+ *
7949
+ * @returns Validation result.
7950
+ */
7951
+ const validateValue = (modifier, valueFormat) => {
7952
+ if (isCustomValueFormatValidator(valueFormat)) {
7953
+ const validator = CUSTOM_VALUE_FORMAT_MAP[valueFormat];
7954
+ return validator(modifier);
7955
+ }
7956
+ const modifierName = modifier.modifier.value;
7957
+ if (!modifier.value?.value) {
7958
+ return getValueRequiredValidationResult(modifierName);
7959
+ }
7960
+ let xRegExp;
7961
+ try {
7962
+ xRegExp = XRegExp(valueFormat);
7963
+ }
7964
+ catch (e) {
7965
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.INVALID_VALUE_FORMAT_REGEXP}: '${modifierName}'`);
7966
+ }
7967
+ const isValid = xRegExp.test(modifier.value?.value);
7968
+ if (!isValid) {
7969
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}'`);
7970
+ }
7971
+ return { valid: true };
7179
7972
  };
7973
+
7974
+ /**
7975
+ * @file Validator for modifiers.
7976
+ */
7977
+ /**
7978
+ * Fully checks whether the given `modifier` valid for given blocker `syntax`:
7979
+ * is it supported by the blocker, deprecated, assignable, negatable, etc.
7980
+ *
7981
+ * @param modifiersData Parsed all modifiers data map.
7982
+ * @param syntax Adblock syntax to check the modifier for.
7983
+ * 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
7984
+ * @param modifier Parsed modifier AST node.
7985
+ * @param isException Whether the modifier is used in exception rule.
7986
+ * Needed to check whether the modifier is allowed only in blocking or exception rules.
7987
+ *
7988
+ * @returns Result of modifier validation.
7989
+ */
7990
+ const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
7991
+ if (syntax === exports.AdblockSyntax.Common) {
7992
+ throw new Error(`Syntax should be specific, '${exports.AdblockSyntax.Common}' is not supported`);
7993
+ }
7994
+ const modifierName = modifier.modifier.value;
7995
+ const blockerPrefix = BLOCKER_PREFIX[syntax];
7996
+ if (!blockerPrefix) {
7997
+ throw new Error(`Unknown syntax: ${syntax}`);
7998
+ }
7999
+ // needed for validation of negation, assignment, etc.
8000
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
8001
+ // if no specific blocker data is found
8002
+ if (!specificBlockerData) {
8003
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
8004
+ }
8005
+ // e.g. 'object-subrequest'
8006
+ if (specificBlockerData[SpecificKey.Removed]) {
8007
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
8008
+ }
8009
+ if (specificBlockerData[SpecificKey.Deprecated]) {
8010
+ if (!specificBlockerData[SpecificKey.DeprecationMessage]) {
8011
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.NO_DEPRECATION_MESSAGE}: '${modifierName}'`);
8012
+ }
8013
+ // prepare the message which is multiline in the yaml file
8014
+ const warn = specificBlockerData[SpecificKey.DeprecationMessage].replace(NEWLINE, SPACE);
8015
+ return {
8016
+ valid: true,
8017
+ warn,
8018
+ };
8019
+ }
8020
+ if (specificBlockerData[SpecificKey.BlockOnly] && isException) {
8021
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
8022
+ }
8023
+ if (specificBlockerData[SpecificKey.ExceptionOnly] && !isException) {
8024
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
8025
+ }
8026
+ // e.g. '~domain=example.com'
8027
+ if (!specificBlockerData[SpecificKey.Negatable] && modifier.exception) {
8028
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_NEGATABLE_MODIFIER}: '${modifierName}'`);
8029
+ }
8030
+ // e.g. 'domain'
8031
+ if (specificBlockerData[SpecificKey.Assignable]) {
8032
+ if (!modifier.value) {
8033
+ /**
8034
+ * Some assignable modifiers can be used without a value,
8035
+ * e.g. '@@||example.com^$cookie'.
8036
+ */
8037
+ if (specificBlockerData[SpecificKey.ValueOptional]) {
8038
+ return { valid: true };
8039
+ }
8040
+ // for other assignable modifiers the value is required
8041
+ return getValueRequiredValidationResult(modifierName);
8042
+ }
8043
+ /**
8044
+ * TODO: consider to return `{ valid: true, warn: 'Modifier value may be specified' }` (???)
8045
+ * for $stealth modifier without a value
8046
+ * but only after the extension will support value for $stealth:
8047
+ * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
8048
+ */
8049
+ if (!specificBlockerData[SpecificKey.ValueFormat]) {
8050
+ throw new Error(`${SOURCE_DATA_ERROR_PREFIX.NO_VALUE_FORMAT_FOR_ASSIGNABLE}: '${modifierName}'`);
8051
+ }
8052
+ return validateValue(modifier, specificBlockerData[SpecificKey.ValueFormat]);
8053
+ }
8054
+ if (modifier?.value) {
8055
+ // e.g. 'third-party=true'
8056
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
8057
+ }
8058
+ return { valid: true };
8059
+ };
8060
+ /**
8061
+ * Returns documentation URL for given modifier and adblocker.
8062
+ *
8063
+ * @param modifiersData Parsed all modifiers data map.
8064
+ * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
8065
+ * @param modifier Parsed modifier AST node.
8066
+ *
8067
+ * @returns Documentation URL or `null` if not found.
8068
+ */
8069
+ const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
8070
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
8071
+ return specificBlockerData?.docs || null;
8072
+ };
8073
+ // TODO: move to modifier.ts and use index.ts only for exporting
7180
8074
  /**
7181
8075
  * Modifier validator class.
7182
8076
  */
@@ -7229,18 +8123,18 @@ class ModifierValidator {
7229
8123
  if (modifier.modifier.value.startsWith(UNDERSCORE)) {
7230
8124
  // check whether the modifier value contains something else besides underscores
7231
8125
  if (!isValidNoopModifier(modifier.modifier.value)) {
7232
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
8126
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
7233
8127
  }
7234
8128
  // otherwise, replace the modifier value with single underscore.
7235
8129
  // it is needed to check whether the modifier is supported by specific adblocker due to the syntax
7236
8130
  modifier.modifier.value = UNDERSCORE;
7237
8131
  }
7238
8132
  if (!this.exists(modifier)) {
7239
- return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
8133
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
7240
8134
  }
7241
8135
  // for 'Common' syntax we cannot check something more
7242
8136
  if (syntax === exports.AdblockSyntax.Common) {
7243
- return { ok: true };
8137
+ return { valid: true };
7244
8138
  }
7245
8139
  return validateForSpecificSyntax(this.modifiersData, syntax, modifier, isException);
7246
8140
  };
@@ -7598,7 +8492,7 @@ const MAX_LENGTH = 'max-length';
7598
8492
  const MIN_LENGTH = 'min-length';
7599
8493
  const MIN_TEXT_LENGTH = 'min-text-length';
7600
8494
  const TAG_CONTENT = 'tag-content';
7601
- const WILDCARD$1 = 'wildcard';
8495
+ const WILDCARD = 'wildcard';
7602
8496
  /**
7603
8497
  * HTML filtering rule converter class
7604
8498
  *
@@ -7669,7 +8563,7 @@ class HtmlRuleConverter extends RuleConverterBase {
7669
8563
  maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
7670
8564
  break;
7671
8565
  case TAG_CONTENT:
7672
- case WILDCARD$1:
8566
+ case WILDCARD:
7673
8567
  CssTree.assertAttributeSelectorHasStringValue(node);
7674
8568
  convertedSelector.children.push(cloneDeep(node));
7675
8569
  break;
@@ -7747,144 +8641,6 @@ class HtmlRuleConverter extends RuleConverterBase {
7747
8641
  }
7748
8642
  }
7749
8643
 
7750
- /**
7751
- * @file Utility functions for working with quotes
7752
- */
7753
- /**
7754
- * Possible quote types for scriptlet parameters
7755
- */
7756
- exports.QuoteType = void 0;
7757
- (function (QuoteType) {
7758
- /**
7759
- * No quotes at all
7760
- */
7761
- QuoteType["None"] = "none";
7762
- /**
7763
- * Single quotes (`'`)
7764
- */
7765
- QuoteType["Single"] = "single";
7766
- /**
7767
- * Double quotes (`"`)
7768
- */
7769
- QuoteType["Double"] = "double";
7770
- })(exports.QuoteType || (exports.QuoteType = {}));
7771
- /**
7772
- * Utility functions for working with quotes
7773
- */
7774
- class QuoteUtils {
7775
- /**
7776
- * Escape all unescaped occurrences of the character
7777
- *
7778
- * @param string String to escape
7779
- * @param char Character to escape
7780
- * @returns Escaped string
7781
- */
7782
- static escapeUnescapedOccurrences(string, char) {
7783
- let result = EMPTY;
7784
- for (let i = 0; i < string.length; i += 1) {
7785
- if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
7786
- result += ESCAPE_CHARACTER;
7787
- }
7788
- result += string[i];
7789
- }
7790
- return result;
7791
- }
7792
- /**
7793
- * Unescape all single escaped occurrences of the character
7794
- *
7795
- * @param string String to unescape
7796
- * @param char Character to unescape
7797
- * @returns Unescaped string
7798
- */
7799
- static unescapeSingleEscapedOccurrences(string, char) {
7800
- let result = EMPTY;
7801
- for (let i = 0; i < string.length; i += 1) {
7802
- if (string[i] === char
7803
- && string[i - 1] === ESCAPE_CHARACTER
7804
- && (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
7805
- result = result.slice(0, -1);
7806
- }
7807
- result += string[i];
7808
- }
7809
- return result;
7810
- }
7811
- /**
7812
- * Get quote type of the string
7813
- *
7814
- * @param string String to check
7815
- * @returns Quote type of the string
7816
- */
7817
- static getStringQuoteType(string) {
7818
- // Don't check 1-character strings to avoid false positives
7819
- if (string.length > 1) {
7820
- if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
7821
- return exports.QuoteType.Single;
7822
- }
7823
- if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
7824
- return exports.QuoteType.Double;
7825
- }
7826
- }
7827
- return exports.QuoteType.None;
7828
- }
7829
- /**
7830
- * Set quote type of the string
7831
- *
7832
- * @param string String to set quote type of
7833
- * @param quoteType Quote type to set
7834
- * @returns String with the specified quote type
7835
- */
7836
- static setStringQuoteType(string, quoteType) {
7837
- const actualQuoteType = QuoteUtils.getStringQuoteType(string);
7838
- switch (quoteType) {
7839
- case exports.QuoteType.None:
7840
- if (actualQuoteType === exports.QuoteType.Single) {
7841
- return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
7842
- }
7843
- if (actualQuoteType === exports.QuoteType.Double) {
7844
- return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
7845
- }
7846
- return string;
7847
- case exports.QuoteType.Single:
7848
- if (actualQuoteType === exports.QuoteType.None) {
7849
- return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
7850
- }
7851
- if (actualQuoteType === exports.QuoteType.Double) {
7852
- return SINGLE_QUOTE
7853
- + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
7854
- }
7855
- return string;
7856
- case exports.QuoteType.Double:
7857
- if (actualQuoteType === exports.QuoteType.None) {
7858
- return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
7859
- }
7860
- if (actualQuoteType !== exports.QuoteType.Double) {
7861
- // eslint-disable-next-line max-len
7862
- return DOUBLE_QUOTE
7863
- + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
7864
- }
7865
- return string;
7866
- default:
7867
- return string;
7868
- }
7869
- }
7870
- /**
7871
- * Removes bounding quotes from a string, if any
7872
- *
7873
- * @param string Input string
7874
- * @returns String without quotes
7875
- */
7876
- static removeQuotes(string) {
7877
- if (
7878
- // We should check for string length to avoid false positives
7879
- string.length > 1
7880
- && (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
7881
- && string[0] === string[string.length - 1]) {
7882
- return string.slice(1, -1);
7883
- }
7884
- return string;
7885
- }
7886
- }
7887
-
7888
8644
  /**
7889
8645
  * @file Utility functions for working with scriptlet nodes
7890
8646
  */
@@ -8781,41 +9537,6 @@ class FilterListConverter extends ConverterBase {
8781
9537
  }
8782
9538
  }
8783
9539
 
8784
- /**
8785
- * @file Utility functions for domain and hostname validation.
8786
- */
8787
- const WILDCARD = ASTERISK; // *
8788
- const WILDCARD_TLD = DOT + WILDCARD; // .*
8789
- const WILDCARD_SUBDOMAIN = WILDCARD + DOT; // *.
8790
- class DomainUtils {
8791
- /**
8792
- * Check if the input is a valid domain or hostname.
8793
- *
8794
- * @param domain Domain to check
8795
- * @returns `true` if the domain is valid, `false` otherwise
8796
- */
8797
- static isValidDomainOrHostname(domain) {
8798
- let domainToCheck = domain;
8799
- // Wildcard-only domain, typically a generic rule
8800
- if (domainToCheck === WILDCARD) {
8801
- return true;
8802
- }
8803
- // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
8804
- if (domainToCheck.endsWith(WILDCARD_TLD)) {
8805
- // Remove the wildcard TLD
8806
- domainToCheck = domainToCheck.substring(0, domainToCheck.length - WILDCARD_TLD.length);
8807
- }
8808
- if (domainToCheck.startsWith(WILDCARD_SUBDOMAIN)) {
8809
- // Remove the wildcard subdomain
8810
- domainToCheck = domainToCheck.substring(WILDCARD_SUBDOMAIN.length);
8811
- }
8812
- // Parse the domain with tldts
8813
- const tldtsResult = tldts.parse(domainToCheck);
8814
- // Check if the domain is valid
8815
- return domainToCheck === tldtsResult.domain || domainToCheck === tldtsResult.hostname;
8816
- }
8817
- }
8818
-
8819
9540
  /**
8820
9541
  * @file Utility functions for logical expression AST.
8821
9542
  */
@@ -8894,7 +9615,7 @@ class LogicalExpressionUtils {
8894
9615
  }
8895
9616
  }
8896
9617
 
8897
- const version$1 = "1.1.2";
9618
+ const version$1 = "1.1.4";
8898
9619
 
8899
9620
  /**
8900
9621
  * @file AGTree version
@@ -8917,13 +9638,13 @@ exports.AGLINT_COMMAND_PREFIX = AGLINT_COMMAND_PREFIX;
8917
9638
  exports.AdblockSyntaxError = AdblockSyntaxError;
8918
9639
  exports.AgentCommentRuleParser = AgentCommentRuleParser;
8919
9640
  exports.AgentParser = AgentParser;
8920
- exports.CLASSIC_DOMAIN_SEPARATOR = CLASSIC_DOMAIN_SEPARATOR;
9641
+ exports.AppListParser = AppListParser;
9642
+ exports.COMMA_DOMAIN_LIST_SEPARATOR = COMMA_DOMAIN_LIST_SEPARATOR;
8921
9643
  exports.CommentRuleParser = CommentRuleParser;
8922
9644
  exports.ConfigCommentRuleParser = ConfigCommentRuleParser;
8923
9645
  exports.CosmeticRuleParser = CosmeticRuleParser;
8924
9646
  exports.CosmeticRuleSeparatorUtils = CosmeticRuleSeparatorUtils;
8925
9647
  exports.CssTree = CssTree;
8926
- exports.DOMAIN_EXCEPTION_MARKER = DOMAIN_EXCEPTION_MARKER;
8927
9648
  exports.DomainListParser = DomainListParser;
8928
9649
  exports.DomainUtils = DomainUtils;
8929
9650
  exports.EXT_CSS_LEGACY_ATTRIBUTES = EXT_CSS_LEGACY_ATTRIBUTES;
@@ -8941,15 +9662,16 @@ exports.LogicalExpressionUtils = LogicalExpressionUtils;
8941
9662
  exports.METADATA_HEADERS = METADATA_HEADERS;
8942
9663
  exports.MODIFIERS_SEPARATOR = MODIFIERS_SEPARATOR;
8943
9664
  exports.MODIFIER_ASSIGN_OPERATOR = MODIFIER_ASSIGN_OPERATOR;
8944
- exports.MODIFIER_DOMAIN_SEPARATOR = MODIFIER_DOMAIN_SEPARATOR;
8945
- exports.MODIFIER_EXCEPTION_MARKER = MODIFIER_EXCEPTION_MARKER;
8946
9665
  exports.MetadataCommentRuleParser = MetadataCommentRuleParser;
9666
+ exports.MethodListParser = MethodListParser;
8947
9667
  exports.ModifierListParser = ModifierListParser;
8948
9668
  exports.ModifierParser = ModifierParser;
9669
+ exports.NEGATION_MARKER = NEGATION_MARKER;
8949
9670
  exports.NETWORK_RULE_EXCEPTION_MARKER = NETWORK_RULE_EXCEPTION_MARKER;
8950
9671
  exports.NETWORK_RULE_SEPARATOR = NETWORK_RULE_SEPARATOR;
8951
9672
  exports.NetworkRuleParser = NetworkRuleParser;
8952
9673
  exports.NotImplementedError = NotImplementedError;
9674
+ exports.PIPE_MODIFIER_SEPARATOR = PIPE_MODIFIER_SEPARATOR;
8953
9675
  exports.PREPROCESSOR_MARKER = PREPROCESSOR_MARKER;
8954
9676
  exports.ParameterListParser = ParameterListParser;
8955
9677
  exports.PreProcessorCommentRuleParser = PreProcessorCommentRuleParser;
@@ -8960,6 +9682,7 @@ exports.RuleConverter = RuleConverter;
8960
9682
  exports.RuleParser = RuleParser;
8961
9683
  exports.SAFARI_CB_AFFINITY = SAFARI_CB_AFFINITY;
8962
9684
  exports.SPECIAL_REGEX_SYMBOLS = SPECIAL_REGEX_SYMBOLS;
9685
+ exports.StealthOptionListParser = StealthOptionListParser;
8963
9686
  exports.UBO_SCRIPTLET_MASK = UBO_SCRIPTLET_MASK;
8964
9687
  exports.locRange = locRange;
8965
9688
  exports.modifierValidator = modifierValidator;