@adguard/agtree 1.0.1 → 1.1.0

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,13 +1,17 @@
1
1
  /*
2
- * AGTree v1.0.1 (build date: Wed, 24 May 2023 17:09:26 GMT)
2
+ * AGTree v1.1.0 (build date: Thu, 10 Aug 2023 15:16:26 GMT)
3
3
  * (c) 2023 AdGuard Software Ltd.
4
4
  * Released under the MIT license
5
- * https://github.com/AdguardTeam/AGTree#readme
5
+ * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
6
6
  */
7
7
  import valid from 'semver/functions/valid.js';
8
8
  import coerce from 'semver/functions/coerce.js';
9
9
  import JSON5 from 'json5';
10
- import { walk, parse, toPlainObject, generate, fromPlainObject, List } from '@adguard/ecss-tree';
10
+ import { walk, parse, toPlainObject, find, generate, fromPlainObject, List } from '@adguard/ecss-tree';
11
+ import * as ecssTree from '@adguard/ecss-tree';
12
+ export { ecssTree as ECSSTree };
13
+ import cloneDeep from 'clone-deep';
14
+ import { redirects } from '@adguard/scriptlets';
11
15
  import { parse as parse$1 } from 'tldts';
12
16
 
13
17
  /**
@@ -74,10 +78,15 @@ const SEMICOLON = ';';
74
78
  const AMPERSAND = '&';
75
79
  const ASTERISK = '*';
76
80
  const AT_SIGN = '@';
81
+ const CARET = '^';
77
82
  const DOLLAR_SIGN = '$';
83
+ const EQUALS = '=';
78
84
  const EXCLAMATION_MARK = '!';
79
85
  const HASHMARK = '#';
80
86
  const PIPE = '|';
87
+ const PLUS = '+';
88
+ const QUESTION_MARK = '?';
89
+ const SLASH = '/';
81
90
  const UNDERSCORE = '_';
82
91
  // Escape characters
83
92
  const BACKSLASH = '\\';
@@ -87,6 +96,8 @@ const CR = '\r';
87
96
  const FF = '\f';
88
97
  const LF = '\n';
89
98
  const CRLF = CR + LF;
99
+ const DOUBLE_QUOTE = '"';
100
+ const SINGLE_QUOTE = '\'';
90
101
  // Brackets
91
102
  const OPEN_PARENTHESIS = '(';
92
103
  const CLOSE_PARENTHESIS = ')';
@@ -103,6 +114,7 @@ const CAPITAL_LETTER_Z = 'Z';
103
114
  // Numbers as strings
104
115
  const NUMBER_0 = '0';
105
116
  const NUMBER_9 = '9';
117
+ const REGEX_MARKER = '/';
106
118
  const ADG_SCRIPTLET_MASK = '//scriptlet';
107
119
  const UBO_SCRIPTLET_MASK = 'js';
108
120
  // Modifiers are separated by ",". For example: "script,domain=example.com"
@@ -120,6 +132,16 @@ const DOMAIN_EXCEPTION_MARKER = '~';
120
132
  * ```
121
133
  */
122
134
  const CLASSIC_DOMAIN_SEPARATOR = ',';
135
+ /**
136
+ * Modifier domain separator.
137
+ *
138
+ * @example
139
+ * ```adblock
140
+ * ! Domains are separated by "|":
141
+ * ads.js^$script,domains=example.com|~example.org
142
+ * ```
143
+ */
144
+ const MODIFIER_DOMAIN_SEPARATOR = '|';
123
145
  const DOMAIN_LIST_TYPE = 'DomainList';
124
146
  const CSS_IMPORTANT = '!important';
125
147
  const HINT_MARKER = '!+';
@@ -176,7 +198,6 @@ function locRange(loc, startOffset, endOffset) {
176
198
  */
177
199
  const SINGLE_QUOTE_MARKER = "'";
178
200
  const DOUBLE_QUOTE_MARKER = '"';
179
- const REGEX_MARKER = '/';
180
201
  class StringUtils {
181
202
  /**
182
203
  * Finds the first occurrence of a character that:
@@ -523,21 +544,6 @@ class StringUtils {
523
544
  }
524
545
  return pattern.length;
525
546
  }
526
- /**
527
- * Checks whether a string is a RegExp pattern.
528
- *
529
- * @param pattern - Pattern to check
530
- * @returns `true` if the string is a RegExp pattern, `false` otherwise
531
- */
532
- static isRegexPattern(pattern) {
533
- const trimmedPattern = pattern.trim();
534
- const lastIndex = trimmedPattern.length - 1;
535
- if (trimmedPattern.length > 2 && trimmedPattern[0] === REGEX_MARKER) {
536
- const last = StringUtils.findNextUnescapedCharacter(trimmedPattern, REGEX_MARKER, 1);
537
- return last === lastIndex;
538
- }
539
- return false;
540
- }
541
547
  /**
542
548
  * Escapes a specified character in the string.
543
549
  *
@@ -568,7 +574,7 @@ class StringUtils {
568
574
  while (i < pattern.length && StringUtils.isWhitespace(pattern[i])) {
569
575
  i += 1;
570
576
  }
571
- return i;
577
+ return Math.min(i, pattern.length);
572
578
  }
573
579
  /**
574
580
  * Searches for the previous non-whitespace character in the source pattern.
@@ -582,22 +588,7 @@ class StringUtils {
582
588
  while (i >= 0 && StringUtils.isWhitespace(pattern[i])) {
583
589
  i -= 1;
584
590
  }
585
- return i;
586
- }
587
- /**
588
- * Finds the next EOL character in the pattern (CR, LF, FF) or the end of the pattern.
589
- *
590
- * @param pattern Pattern to search
591
- * @param start Start index
592
- * @returns Index of the next EOL character or the length of the pattern
593
- */
594
- static findNextEOL(pattern, start = 0) {
595
- for (let i = start; i < pattern.length; i += 1) {
596
- if (StringUtils.isEOL(pattern[i])) {
597
- return i;
598
- }
599
- }
600
- return pattern.length;
591
+ return Math.max(i, -1);
601
592
  }
602
593
  /**
603
594
  * Checks if the given character is a new line character.
@@ -687,6 +678,46 @@ class StringUtils {
687
678
  }
688
679
  return result;
689
680
  }
681
+ /**
682
+ * Helper method to parse a raw string as a number
683
+ *
684
+ * @param raw Raw string to parse
685
+ * @returns Parsed number
686
+ * @throws If the raw string can't be parsed as a number
687
+ */
688
+ static parseNumber(raw) {
689
+ const result = parseInt(raw, 10);
690
+ if (Number.isNaN(result)) {
691
+ throw new Error('Expected a number');
692
+ }
693
+ return result;
694
+ }
695
+ /**
696
+ * Checks if the given value is a string.
697
+ *
698
+ * @param value Value to check
699
+ * @returns `true` if the value is a string, `false` otherwise
700
+ */
701
+ static isString(value) {
702
+ return typeof value === 'string';
703
+ }
704
+ /**
705
+ * Escapes the given characters in the input string.
706
+ *
707
+ * @param input Input string
708
+ * @param characters Characters to escape (by default, no characters are escaped)
709
+ * @returns Escaped string
710
+ */
711
+ static escapeCharacters(input, characters = new Set()) {
712
+ let result = EMPTY;
713
+ for (let i = 0; i < input.length; i += 1) {
714
+ if (characters.has(input[i])) {
715
+ result += ESCAPE_CHARACTER;
716
+ }
717
+ result += input[i];
718
+ }
719
+ return result;
720
+ }
690
721
  }
691
722
 
692
723
  /**
@@ -767,7 +798,89 @@ var CosmeticRuleType;
767
798
  CosmeticRuleType["HtmlFilteringRule"] = "HtmlFilteringRule";
768
799
  CosmeticRuleType["JsInjectionRule"] = "JsInjectionRule";
769
800
  })(CosmeticRuleType || (CosmeticRuleType = {}));
801
+ /**
802
+ * Represents possible cosmetic rule separators.
803
+ */
804
+ var CosmeticRuleSeparator;
805
+ (function (CosmeticRuleSeparator) {
806
+ /**
807
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
808
+ */
809
+ CosmeticRuleSeparator["ElementHiding"] = "##";
810
+ /**
811
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
812
+ */
813
+ CosmeticRuleSeparator["ElementHidingException"] = "#@#";
814
+ /**
815
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
816
+ */
817
+ CosmeticRuleSeparator["ExtendedElementHiding"] = "#?#";
818
+ /**
819
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
820
+ */
821
+ CosmeticRuleSeparator["ExtendedElementHidingException"] = "#@?#";
822
+ /**
823
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
824
+ */
825
+ CosmeticRuleSeparator["AbpSnippet"] = "#$#";
826
+ /**
827
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide_basic}
828
+ */
829
+ CosmeticRuleSeparator["AbpSnippetException"] = "#@$#";
830
+ /**
831
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#cosmetic-css-rules}
832
+ */
833
+ CosmeticRuleSeparator["AdgCssInjection"] = "#$#";
834
+ /**
835
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#cosmetic-css-rules}
836
+ */
837
+ CosmeticRuleSeparator["AdgCssInjectionException"] = "#@$#";
838
+ /**
839
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#cosmetic-css-rules}
840
+ */
841
+ CosmeticRuleSeparator["AdgExtendedCssInjection"] = "#$?#";
842
+ /**
843
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#cosmetic-css-rules}
844
+ */
845
+ CosmeticRuleSeparator["AdgExtendedCssInjectionException"] = "#@$?#";
846
+ /**
847
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#scriptlets}
848
+ */
849
+ CosmeticRuleSeparator["AdgJsInjection"] = "#%#";
850
+ /**
851
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#scriptlets}
852
+ */
853
+ CosmeticRuleSeparator["AdgJsInjectionException"] = "#@%#";
854
+ /**
855
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#html-filtering-rules}
856
+ */
857
+ CosmeticRuleSeparator["AdgHtmlFiltering"] = "$$";
858
+ /**
859
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#html-filtering-rules}
860
+ */
861
+ CosmeticRuleSeparator["AdgHtmlFilteringException"] = "$@$";
862
+ /**
863
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#scriptlet-injection}
864
+ */
865
+ CosmeticRuleSeparator["UboScriptletInjection"] = "##+";
866
+ /**
867
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#scriptlet-injection}
868
+ */
869
+ CosmeticRuleSeparator["UboScriptletInjectionException"] = "#@#+";
870
+ /**
871
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#html-filters}
872
+ */
873
+ CosmeticRuleSeparator["UboHtmlFiltering"] = "##^";
874
+ /**
875
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#html-filters}
876
+ */
877
+ CosmeticRuleSeparator["UboHtmlFilteringException"] = "#@#^";
878
+ })(CosmeticRuleSeparator || (CosmeticRuleSeparator = {}));
770
879
 
880
+ /**
881
+ * @file Customized syntax error class for Adblock Filter Parser.
882
+ */
883
+ const ERROR_NAME$2 = 'AdblockSyntaxError';
771
884
  /**
772
885
  * Customized syntax error class for Adblock Filter Parser,
773
886
  * which contains the location range of the error.
@@ -785,11 +898,48 @@ class AdblockSyntaxError extends SyntaxError {
785
898
  */
786
899
  constructor(message, loc) {
787
900
  super(message);
788
- this.name = 'AdblockSyntaxError';
901
+ this.name = ERROR_NAME$2;
789
902
  this.loc = loc;
790
903
  }
791
904
  }
792
905
 
906
+ const ADG_NAME_MARKERS = new Set([
907
+ 'adguard',
908
+ 'adg',
909
+ ]);
910
+ const UBO_NAME_MARKERS = new Set([
911
+ 'ublock',
912
+ 'ublock origin',
913
+ 'ubo',
914
+ ]);
915
+ const ABP_NAME_MARKERS = new Set([
916
+ 'adblock',
917
+ 'adblock plus',
918
+ 'adblockplus',
919
+ 'abp',
920
+ ]);
921
+ /**
922
+ * Returns the adblock syntax based on the adblock name
923
+ * parsed from the agent type comment.
924
+ * Needed for modifiers validation of network rules by AGLint.
925
+ *
926
+ * @param name Adblock name.
927
+ *
928
+ * @returns Adblock syntax.
929
+ */
930
+ const getAdblockSyntax = (name) => {
931
+ let syntax = AdblockSyntax.Common;
932
+ if (ADG_NAME_MARKERS.has(name.toLowerCase())) {
933
+ syntax = AdblockSyntax.Adg;
934
+ }
935
+ else if (UBO_NAME_MARKERS.has(name.toLowerCase())) {
936
+ syntax = AdblockSyntax.Ubo;
937
+ }
938
+ else if (ABP_NAME_MARKERS.has(name.toLowerCase())) {
939
+ syntax = AdblockSyntax.Abp;
940
+ }
941
+ return syntax;
942
+ };
793
943
  /**
794
944
  * `AgentParser` is responsible for parsing single adblock agent elements.
795
945
  *
@@ -828,6 +978,8 @@ class AgentParser {
828
978
  // Prepare variables for name and version
829
979
  let name = null;
830
980
  let version = null;
981
+ // default value for the syntax
982
+ let syntax = AdblockSyntax.Common;
831
983
  // Get agent parts by splitting it by spaces. The last part may be a version.
832
984
  // Example: "Adblock Plus 2.0"
833
985
  while (offset < raw.length) {
@@ -840,11 +992,12 @@ class AgentParser {
840
992
  if (version !== null) {
841
993
  throw new AdblockSyntaxError('Duplicated versions are not allowed', locRange(loc, offset, partEnd));
842
994
  }
995
+ const parsedNamePart = raw.substring(nameStartIndex, nameEndIndex);
843
996
  // Save name
844
997
  name = {
845
998
  type: 'Value',
846
999
  loc: locRange(loc, nameStartIndex, nameEndIndex),
847
- value: raw.substring(nameStartIndex, nameEndIndex),
1000
+ value: parsedNamePart,
848
1001
  };
849
1002
  // Save version
850
1003
  version = {
@@ -852,6 +1005,8 @@ class AgentParser {
852
1005
  loc: locRange(loc, offset, partEnd),
853
1006
  value: part,
854
1007
  };
1008
+ // Save syntax
1009
+ syntax = getAdblockSyntax(parsedNamePart);
855
1010
  }
856
1011
  else {
857
1012
  nameEndIndex = partEnd;
@@ -861,11 +1016,13 @@ class AgentParser {
861
1016
  }
862
1017
  // If we didn't find a version, the whole string is the name
863
1018
  if (name === null) {
1019
+ const parsedNamePart = raw.substring(nameStartIndex, nameEndIndex);
864
1020
  name = {
865
1021
  type: 'Value',
866
1022
  loc: locRange(loc, nameStartIndex, nameEndIndex),
867
- value: raw.substring(nameStartIndex, nameEndIndex),
1023
+ value: parsedNamePart,
868
1024
  };
1025
+ syntax = getAdblockSyntax(parsedNamePart);
869
1026
  }
870
1027
  // Agent name cannot be empty
871
1028
  if (name.value.length === 0) {
@@ -876,6 +1033,7 @@ class AgentParser {
876
1033
  loc: locRange(loc, 0, raw.length),
877
1034
  adblock: name,
878
1035
  version,
1036
+ syntax,
879
1037
  };
880
1038
  }
881
1039
  /**
@@ -915,6 +1073,18 @@ class CosmeticRuleSeparatorUtils {
915
1073
  // Simply check the second character
916
1074
  return separator[1] === AT_SIGN;
917
1075
  }
1076
+ /**
1077
+ * Checks whether the specified separator is marks an Extended CSS cosmetic rule.
1078
+ *
1079
+ * @param separator Separator to check
1080
+ * @returns `true` if the separator is marks an Extended CSS cosmetic rule, `false` otherwise
1081
+ */
1082
+ static isExtendedCssMarker(separator) {
1083
+ return (separator === CosmeticRuleSeparator.ExtendedElementHiding
1084
+ || separator === CosmeticRuleSeparator.ExtendedElementHidingException
1085
+ || separator === CosmeticRuleSeparator.AdgExtendedCssInjection
1086
+ || separator === CosmeticRuleSeparator.AdgExtendedCssInjectionException);
1087
+ }
918
1088
  /**
919
1089
  * Looks for the cosmetic rule separator in the rule. This is a simplified version that
920
1090
  * masks the recursive function.
@@ -942,64 +1112,80 @@ class CosmeticRuleSeparatorUtils {
942
1112
  if (rule[i] === '#') {
943
1113
  if (rule[i + 1] === '#') {
944
1114
  if (rule[i + 2] === '+') {
945
- return createResult(i, '##+');
1115
+ // ##+
1116
+ return createResult(i, CosmeticRuleSeparator.UboScriptletInjection);
946
1117
  }
947
1118
  if (rule[i + 2] === '^') {
948
- return createResult(i, '##^');
1119
+ // ##^
1120
+ return createResult(i, CosmeticRuleSeparator.UboHtmlFiltering);
949
1121
  }
950
1122
  if (rule[i - 1] !== SPACE) {
951
- return createResult(i, '##');
1123
+ // ##
1124
+ return createResult(i, CosmeticRuleSeparator.ElementHiding);
952
1125
  }
953
1126
  }
954
1127
  if (rule[i + 1] === '?' && rule[i + 2] === '#') {
955
- return createResult(i, '#?#');
1128
+ // #?#
1129
+ return createResult(i, CosmeticRuleSeparator.ExtendedElementHiding);
956
1130
  }
957
1131
  if (rule[i + 1] === '%' && rule[i + 2] === '#') {
958
- return createResult(i, '#%#');
1132
+ // #%#
1133
+ return createResult(i, CosmeticRuleSeparator.AdgJsInjection);
959
1134
  }
960
1135
  if (rule[i + 1] === '$') {
961
1136
  if (rule[i + 2] === '#') {
962
- return createResult(i, '#$#');
1137
+ // #$#
1138
+ return createResult(i, CosmeticRuleSeparator.AdgCssInjection);
963
1139
  }
964
1140
  if (rule[i + 2] === '?' && rule[i + 3] === '#') {
965
- return createResult(i, '#$?#');
1141
+ // #$?#
1142
+ return createResult(i, CosmeticRuleSeparator.AdgExtendedCssInjection);
966
1143
  }
967
1144
  }
968
1145
  // Exceptions
969
1146
  if (rule[i + 1] === '@') {
970
1147
  if (rule[i + 2] === '#') {
971
1148
  if (rule[i + 3] === '+') {
972
- return createResult(i, '#@#+');
1149
+ // #@#+
1150
+ return createResult(i, CosmeticRuleSeparator.UboScriptletInjectionException);
973
1151
  }
974
1152
  if (rule[i + 3] === '^') {
975
- return createResult(i, '#@#^');
1153
+ // #@#^
1154
+ return createResult(i, CosmeticRuleSeparator.UboHtmlFilteringException);
976
1155
  }
977
1156
  if (rule[i - 1] !== SPACE) {
978
- return createResult(i, '#@#');
1157
+ // #@#
1158
+ return createResult(i, CosmeticRuleSeparator.ElementHidingException);
979
1159
  }
980
1160
  }
981
1161
  if (rule[i + 2] === '?' && rule[i + 3] === '#') {
982
- return createResult(i, '#@?#');
1162
+ // #@?#
1163
+ return createResult(i, CosmeticRuleSeparator.ExtendedElementHidingException);
983
1164
  }
984
1165
  if (rule[i + 2] === '%' && rule[i + 3] === '#') {
985
- return createResult(i, '#@%#');
1166
+ // #@%#
1167
+ return createResult(i, CosmeticRuleSeparator.AdgJsInjectionException);
986
1168
  }
987
1169
  if (rule[i + 2] === '$') {
988
1170
  if (rule[i + 3] === '#') {
989
- return createResult(i, '#@$#');
1171
+ // #@$#
1172
+ return createResult(i, CosmeticRuleSeparator.AdgCssInjectionException);
990
1173
  }
991
1174
  if (rule[i + 3] === '?' && rule[i + 4] === '#') {
992
- return createResult(i, '#@$?#');
1175
+ // #@$?#
1176
+ return createResult(i, CosmeticRuleSeparator.AdgExtendedCssInjectionException);
993
1177
  }
994
1178
  }
995
1179
  }
996
1180
  }
997
1181
  if (rule[i] === '$') {
998
1182
  if (rule[i + 1] === '$') {
999
- return createResult(i, '$$');
1183
+ // $$
1184
+ return createResult(i, CosmeticRuleSeparator.AdgHtmlFiltering);
1000
1185
  }
1001
1186
  if (rule[i + 1] === '@' && rule[i + 2] === '$') {
1002
- return createResult(i, '$@$');
1187
+ // $@$
1188
+ return createResult(i, CosmeticRuleSeparator.AdgHtmlFilteringException);
1003
1189
  }
1004
1190
  }
1005
1191
  }
@@ -1798,6 +1984,9 @@ class LogicalExpressionParser {
1798
1984
  if (!token) {
1799
1985
  throw new AdblockSyntaxError(`Expected token of type "${type}", but reached end of input`, locRange(loc, 0, raw.length));
1800
1986
  }
1987
+ // We only use this function internally, so we can safely ignore this
1988
+ // from the coverage report
1989
+ // istanbul ignore next
1801
1990
  if (token.type !== type) {
1802
1991
  throw new AdblockSyntaxError(`Expected token of type "${type}", but got "${token.type}"`,
1803
1992
  // Token location is always shifted, no need locRange
@@ -2448,7 +2637,9 @@ class ModifierParser {
2448
2637
  *
2449
2638
  * @param raw Raw modifier string
2450
2639
  * @param loc Location of the modifier
2640
+ *
2451
2641
  * @returns Parsed modifier
2642
+ * @throws An error if modifier name or value is empty.
2452
2643
  */
2453
2644
  static parse(raw, loc = defaultLocation) {
2454
2645
  let offset = 0;
@@ -2469,7 +2660,7 @@ class ModifierParser {
2469
2660
  // Find assignment operator
2470
2661
  const assignmentIndex = StringUtils.findNextUnescapedCharacter(raw, MODIFIER_ASSIGN_OPERATOR);
2471
2662
  // Find the end of the modifier
2472
- const modifierEnd = StringUtils.skipWSBack(raw) + 1;
2663
+ const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
2473
2664
  // Modifier name can't be empty
2474
2665
  if (modifierNameStart === modifierEnd) {
2475
2666
  throw new AdblockSyntaxError('Modifier name can\'t be empty', locRange(loc, 0, raw.length));
@@ -2559,24 +2750,28 @@ class ModifierListParser {
2559
2750
  loc: locRange(loc, 0, raw.length),
2560
2751
  children: [],
2561
2752
  };
2562
- let offset = 0;
2563
- // Skip whitespace before the modifier list
2564
- offset = StringUtils.skipWS(raw, offset);
2753
+ let offset = StringUtils.skipWS(raw);
2754
+ let separatorIndex = -1;
2565
2755
  // Split modifiers by unescaped commas
2566
2756
  while (offset < raw.length) {
2567
2757
  // Skip whitespace before the modifier
2568
2758
  offset = StringUtils.skipWS(raw, offset);
2569
2759
  const modifierStart = offset;
2570
- // Find the index of the first unescaped "," character
2571
- const separatorStartIndex = StringUtils.findNextUnescapedCharacter(raw, MODIFIERS_SEPARATOR, offset);
2572
- const modifierEnd = separatorStartIndex === -1
2573
- ? StringUtils.skipWSBack(raw) + 1
2574
- : StringUtils.skipWSBack(raw, separatorStartIndex - 1) + 1;
2760
+ // Find the index of the first unescaped comma
2761
+ separatorIndex = StringUtils.findNextUnescapedCharacter(raw, MODIFIERS_SEPARATOR, offset);
2762
+ const modifierEnd = separatorIndex === -1
2763
+ ? raw.length
2764
+ : StringUtils.skipWSBack(raw, separatorIndex - 1) + 1;
2575
2765
  // Parse the modifier
2576
2766
  const modifier = ModifierParser.parse(raw.substring(modifierStart, modifierEnd), shiftLoc(loc, modifierStart));
2577
2767
  result.children.push(modifier);
2578
2768
  // Increment the offset to the next modifier (or the end of the string)
2579
- offset = separatorStartIndex === -1 ? raw.length : separatorStartIndex + 1;
2769
+ offset = separatorIndex === -1 ? raw.length : separatorIndex + 1;
2770
+ }
2771
+ // Check if there are any modifiers after the last separator
2772
+ if (separatorIndex !== -1) {
2773
+ const modifierStart = StringUtils.skipWS(raw, separatorIndex + 1);
2774
+ result.children.push(ModifierParser.parse(raw.substring(modifierStart, raw.length), shiftLoc(loc, modifierStart)));
2580
2775
  }
2581
2776
  return result;
2582
2777
  }
@@ -2594,63 +2789,6 @@ class ModifierListParser {
2594
2789
  }
2595
2790
  }
2596
2791
 
2597
- /**
2598
- * Known ExtendedCSS elements
2599
- */
2600
- const EXTCSS_PSEUDO_CLASSES = [
2601
- // AdGuard
2602
- // https://github.com/AdguardTeam/ExtendedCss
2603
- 'has',
2604
- 'if-not',
2605
- 'contains',
2606
- 'matches-css',
2607
- 'matches-attr',
2608
- 'matches-property',
2609
- 'xpath',
2610
- 'nth-ancestor',
2611
- 'upward',
2612
- 'remove',
2613
- 'is',
2614
- // uBlock Origin
2615
- // https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#procedural-cosmetic-filters
2616
- 'has-text',
2617
- 'matches-css-before',
2618
- 'matches-css-after',
2619
- 'matches-path',
2620
- 'min-text-length',
2621
- 'watch-attr',
2622
- // Adblock Plus
2623
- // https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide-emulation
2624
- '-abp-has',
2625
- '-abp-contains',
2626
- '-abp-properties',
2627
- ];
2628
- const EXTCSS_ATTRIBUTES = [
2629
- // AdGuard
2630
- '-ext-has',
2631
- '-ext-if-not',
2632
- '-ext-contains',
2633
- '-ext-matches-css',
2634
- '-ext-matches-attr',
2635
- '-ext-matches-property',
2636
- '-ext-xpath',
2637
- '-ext-nth-ancestor',
2638
- '-ext-upward',
2639
- '-ext-remove',
2640
- '-ext-is',
2641
- // uBlock Origin
2642
- '-ext-has-text',
2643
- '-ext-matches-css-before',
2644
- '-ext-matches-css-after',
2645
- '-ext-matches-path',
2646
- '-ext-min-text-length',
2647
- '-ext-watch-attr',
2648
- // Adblock Plus
2649
- '-ext-abp-has',
2650
- '-ext-abp-contains',
2651
- '-ext-abp-properties',
2652
- ];
2653
-
2654
2792
  /**
2655
2793
  * @file Helper file for CSSTree to provide better compatibility with TypeScript.
2656
2794
  * @see {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/62536}
@@ -2762,6 +2900,91 @@ var CssTreeParserContext;
2762
2900
  CssTreeParserContext["value"] = "value";
2763
2901
  })(CssTreeParserContext || (CssTreeParserContext = {}));
2764
2902
 
2903
+ /**
2904
+ * @file Known CSS elements and attributes.
2905
+ * TODO: Implement a compatibility table for Extended CSS
2906
+ */
2907
+ const LEGACY_EXT_CSS_ATTRIBUTE_PREFIX = '-ext-';
2908
+ /**
2909
+ * Known Extended CSS pseudo-classes. Please, keep this list sorted.
2910
+ */
2911
+ const EXT_CSS_PSEUDO_CLASSES = new Set([
2912
+ // AdGuard
2913
+ // https://github.com/AdguardTeam/ExtendedCss
2914
+ 'contains',
2915
+ 'has',
2916
+ 'if-not',
2917
+ 'is',
2918
+ 'matches-attr',
2919
+ 'matches-css',
2920
+ 'matches-property',
2921
+ 'nth-ancestor',
2922
+ 'remove',
2923
+ 'upward',
2924
+ 'xpath',
2925
+ // uBlock Origin
2926
+ // https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#procedural-cosmetic-filters
2927
+ 'has-text',
2928
+ 'matches-css-after',
2929
+ 'matches-css-before',
2930
+ 'matches-path',
2931
+ 'min-text-length',
2932
+ 'watch-attr',
2933
+ // Adblock Plus
2934
+ // https://help.eyeo.com/adblockplus/how-to-write-filters#elemhide-emulation
2935
+ '-abp-contains',
2936
+ '-abp-has',
2937
+ '-abp-properties',
2938
+ ]);
2939
+ /**
2940
+ * Known legacy Extended CSS attributes. These attributes are deprecated and
2941
+ * should be replaced with the corresponding pseudo-classes. In a long term,
2942
+ * these attributes will be COMPLETELY removed from the Extended CSS syntax.
2943
+ *
2944
+ * Please, keep this list sorted.
2945
+ */
2946
+ const EXT_CSS_LEGACY_ATTRIBUTES = new Set([
2947
+ // AdGuard
2948
+ '-ext-contains',
2949
+ '-ext-has',
2950
+ '-ext-if-not',
2951
+ '-ext-is',
2952
+ '-ext-matches-attr',
2953
+ '-ext-matches-css',
2954
+ '-ext-matches-property',
2955
+ '-ext-nth-ancestor',
2956
+ '-ext-remove',
2957
+ '-ext-upward',
2958
+ '-ext-xpath',
2959
+ // uBlock Origin
2960
+ '-ext-has-text',
2961
+ '-ext-matches-css-after',
2962
+ '-ext-matches-css-before',
2963
+ '-ext-matches-path',
2964
+ '-ext-min-text-length',
2965
+ '-ext-watch-attr',
2966
+ // Adblock Plus
2967
+ '-ext-abp-contains',
2968
+ '-ext-abp-has',
2969
+ '-ext-abp-properties',
2970
+ ]);
2971
+ /**
2972
+ * Known CSS functions that aren't allowed in CSS injection rules, because they
2973
+ * able to load external resources. Please, keep this list sorted.
2974
+ */
2975
+ const FORBIDDEN_CSS_FUNCTIONS = new Set([
2976
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/cross-fade
2977
+ '-webkit-cross-fade',
2978
+ 'cross-fade',
2979
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/image
2980
+ 'image',
2981
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/image-set
2982
+ '-webkit-image-set',
2983
+ 'image-set',
2984
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/url
2985
+ 'url',
2986
+ ]);
2987
+
2765
2988
  /**
2766
2989
  * @file Additional / helper functions for ECSSTree / CSSTree.
2767
2990
  */
@@ -2775,6 +2998,7 @@ const commonCssTreeOptions = {
2775
2998
  parseCustomProperty: true,
2776
2999
  positions: true,
2777
3000
  };
3001
+ const URL_FUNCTION = 'url';
2778
3002
  /**
2779
3003
  * Additional / helper functions for CSSTree.
2780
3004
  */
@@ -2845,11 +3069,14 @@ class CssTree {
2845
3069
  errorLoc = locRange(loc, error.offset, raw.length);
2846
3070
  }
2847
3071
  else {
3072
+ // istanbul ignore next
2848
3073
  errorLoc = locRange(loc, 0, raw.length);
2849
3074
  }
2850
3075
  throw new AdblockSyntaxError(`ECSSTree parsing error: '${error.message}'`, errorLoc);
2851
3076
  }
2852
- // Pass through
3077
+ // Pass through any other error just in case, but theoretically it should never happen,
3078
+ // so it is ok to ignore it from the coverage
3079
+ // istanbul ignore next
2853
3080
  throw error;
2854
3081
  }
2855
3082
  }
@@ -2862,64 +3089,186 @@ class CssTree {
2862
3089
  * @param loc Base location for the parsed node
2863
3090
  * @returns CSSTree node (AST)
2864
3091
  */
3092
+ // istanbul ignore next
2865
3093
  // eslint-disable-next-line max-len
2866
3094
  static parsePlain(raw, context, tolerant = false, loc = defaultLocation) {
2867
3095
  return toPlainObject(CssTree.parse(raw, context, tolerant, loc));
2868
3096
  }
2869
3097
  /**
2870
- * Walks through the CSSTree node and returns all ExtendedCSS nodes.
3098
+ * Checks if the CSSTree node is an ExtendedCSS node.
2871
3099
  *
2872
- * @param selectorAst CSSTree selector AST
2873
- * @returns Extended CSS nodes (pseudos and attributes)
3100
+ * @param node Node to check
3101
+ * @param pseudoClasses List of the names of the pseudo classes to check
3102
+ * @param attributeSelectors List of the names of the attribute selectors to check
3103
+ * @returns `true` if the node is an ExtendedCSS node, otherwise `false`
2874
3104
  */
2875
- static getSelectorExtendedCssNodes(selectorAst) {
2876
- const pseudos = [];
2877
- const attributes = [];
2878
- walk(selectorAst, (node) => {
2879
- // Pseudo classes
2880
- if (node.type === CssTreeNodeType.PseudoClassSelector) {
2881
- // Check if it's a known ExtendedCSS pseudo class
2882
- if (EXTCSS_PSEUDO_CLASSES.includes(node.name)) {
2883
- pseudos.push(node);
2884
- }
2885
- }
2886
- else if (node.type === CssTreeNodeType.AttributeSelector) {
2887
- // Check if it's a known ExtendedCSS attribute
2888
- if (EXTCSS_ATTRIBUTES.includes(node.name.name)) {
2889
- attributes.push(node);
2890
- }
2891
- }
2892
- });
2893
- return {
2894
- pseudos,
2895
- attributes,
2896
- };
3105
+ static isExtendedCssNode(node, pseudoClasses, attributeSelectors) {
3106
+ return ((node.type === CssTreeNodeType.PseudoClassSelector && pseudoClasses.has(node.name))
3107
+ || (node.type === CssTreeNodeType.AttributeSelector && attributeSelectors.has(node.name.name)));
2897
3108
  }
2898
3109
  /**
2899
- * Generates string representation of the media query list.
3110
+ * Walks through the CSSTree node and returns all ExtendedCSS nodes.
2900
3111
  *
2901
- * @param ast Media query list AST
2902
- * @returns String representation of the media query list
3112
+ * @param selectorList Selector list (can be a string or a CSSTree node)
3113
+ * @param pseudoClasses List of the names of the pseudo classes to check
3114
+ * @param attributeSelectors List of the names of the attribute selectors to check
3115
+ * @returns Extended CSS nodes (pseudos and attributes)
3116
+ * @see {@link https://github.com/csstree/csstree/blob/master/docs/ast.md#selectorlist}
2903
3117
  */
2904
- static generateMediaQueryList(ast) {
2905
- let result = EMPTY;
2906
- if (!ast.children || ast.children.size === 0) {
2907
- throw new Error('Media query list cannot be empty');
3118
+ static getSelectorExtendedCssNodes(selectorList, pseudoClasses = EXT_CSS_PSEUDO_CLASSES, attributeSelectors = EXT_CSS_LEGACY_ATTRIBUTES) {
3119
+ // Parse the block if string is passed
3120
+ let ast;
3121
+ if (StringUtils.isString(selectorList)) {
3122
+ ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
2908
3123
  }
2909
- ast.children.forEach((mediaQuery, listItem) => {
2910
- if (mediaQuery.type !== CssTreeNodeType.MediaQuery) {
2911
- throw new Error(`Unexpected node type: ${mediaQuery.type}`);
2912
- }
2913
- result += this.generateMediaQuery(mediaQuery);
2914
- if (listItem.next !== null) {
2915
- result += COMMA;
2916
- result += SPACE;
3124
+ else {
3125
+ ast = cloneDeep(selectorList);
3126
+ }
3127
+ const nodes = [];
3128
+ // TODO: CSSTree types should be improved, as a workaround we use `any` here
3129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3130
+ walk(ast, (node) => {
3131
+ if (CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) {
3132
+ nodes.push(node);
2917
3133
  }
2918
3134
  });
2919
- return result;
3135
+ return nodes;
2920
3136
  }
2921
3137
  /**
2922
- * Generates string representation of the media query.
3138
+ * Checks if the selector contains any ExtendedCSS nodes. It is a faster alternative to
3139
+ * `getSelectorExtendedCssNodes` if you only need to know if the selector contains any ExtendedCSS nodes,
3140
+ * because it stops the search on the first ExtendedCSS node instead of going through the whole selector
3141
+ * and collecting all ExtendedCSS nodes.
3142
+ *
3143
+ * @param selectorList Selector list (can be a string or a CSSTree node)
3144
+ * @param pseudoClasses List of the names of the pseudo classes to check
3145
+ * @param attributeSelectors List of the names of the attribute selectors to check
3146
+ * @returns `true` if the selector contains any ExtendedCSS nodes
3147
+ * @see {@link https://github.com/csstree/csstree/blob/master/docs/ast.md#selectorlist}
3148
+ * @see {@link https://github.com/csstree/csstree/blob/master/docs/traversal.md#findast-fn}
3149
+ */
3150
+ static hasAnySelectorExtendedCssNode(selectorList, pseudoClasses = EXT_CSS_PSEUDO_CLASSES, attributeSelectors = EXT_CSS_LEGACY_ATTRIBUTES) {
3151
+ // Parse the block if string is passed
3152
+ let ast;
3153
+ if (StringUtils.isString(selectorList)) {
3154
+ ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
3155
+ }
3156
+ else {
3157
+ ast = cloneDeep(selectorList);
3158
+ }
3159
+ // TODO: CSSTree types should be improved, as a workaround we use `any` here
3160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3161
+ return find(ast, (node) => CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) !== null;
3162
+ }
3163
+ /**
3164
+ * Checks if the node is a forbidden function (unsafe resource loading). Typically it is used to check
3165
+ * if the node is a `url()` function, which is a security risk when using filter lists from untrusted
3166
+ * sources.
3167
+ *
3168
+ * @param node Node to check
3169
+ * @param forbiddenFunctions Set of the names of the functions to check
3170
+ * @returns `true` if the node is a forbidden function
3171
+ */
3172
+ static isForbiddenFunction(node, forbiddenFunctions = FORBIDDEN_CSS_FUNCTIONS) {
3173
+ return (
3174
+ // General case: check if it's a forbidden function
3175
+ (node.type === CssTreeNodeType.Function && forbiddenFunctions.has(node.name))
3176
+ // Special case: CSSTree handles `url()` function in a separate node type,
3177
+ // and we also should check if the `url()` are marked as a forbidden function
3178
+ || (node.type === CssTreeNodeType.Url && forbiddenFunctions.has(URL_FUNCTION)));
3179
+ }
3180
+ /**
3181
+ * Gets the list of the forbidden function nodes in the declaration block. Typically it is used to get
3182
+ * the list of the functions that can be used to load external resources, which is a security risk
3183
+ * when using filter lists from untrusted sources.
3184
+ *
3185
+ * @param declarationList Declaration list to check (can be a string or a CSSTree node)
3186
+ * @param forbiddenFunctions Set of the names of the functions to check
3187
+ * @returns List of the forbidden function nodes in the declaration block (can be empty)
3188
+ */
3189
+ static getForbiddenFunctionNodes(declarationList, forbiddenFunctions = FORBIDDEN_CSS_FUNCTIONS) {
3190
+ // Parse the block if string is passed
3191
+ let ast;
3192
+ if (StringUtils.isString(declarationList)) {
3193
+ ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
3194
+ }
3195
+ else {
3196
+ ast = cloneDeep(declarationList);
3197
+ }
3198
+ const nodes = [];
3199
+ // While walking the AST we should skip the nested functions,
3200
+ // for example skip url()s in cross-fade(url(), url()), since
3201
+ // cross-fade() itself is already a forbidden function
3202
+ let inForbiddenFunction = false;
3203
+ // TODO: CSSTree types should be improved, as a workaround we use `any` here
3204
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3205
+ walk(ast, {
3206
+ enter: (node) => {
3207
+ if (!inForbiddenFunction && CssTree.isForbiddenFunction(node, forbiddenFunctions)) {
3208
+ nodes.push(node);
3209
+ inForbiddenFunction = true;
3210
+ }
3211
+ },
3212
+ leave: (node) => {
3213
+ if (inForbiddenFunction && CssTree.isForbiddenFunction(node, forbiddenFunctions)) {
3214
+ inForbiddenFunction = false;
3215
+ }
3216
+ },
3217
+ });
3218
+ return nodes;
3219
+ }
3220
+ /**
3221
+ * Checks if the declaration block contains any forbidden functions. Typically it is used to check
3222
+ * if the declaration block contains any functions that can be used to load external resources,
3223
+ * which is a security risk when using filter lists from untrusted sources.
3224
+ *
3225
+ * @param declarationList Declaration list to check (can be a string or a CSSTree node)
3226
+ * @param forbiddenFunctions Set of the names of the functions to check
3227
+ * @returns `true` if the declaration block contains any forbidden functions
3228
+ * @throws If you pass a string, but it is not a valid CSS
3229
+ * @throws If you pass an invalid CSSTree node / AST
3230
+ * @see {@link https://github.com/csstree/csstree/blob/master/docs/ast.md#declarationlist}
3231
+ * @see {@link https://github.com/AdguardTeam/AdguardBrowserExtension/issues/1196}
3232
+ * @see {@link https://github.com/AdguardTeam/AdguardBrowserExtension/issues/1920}
3233
+ */
3234
+ static hasAnyForbiddenFunction(declarationList, forbiddenFunctions = FORBIDDEN_CSS_FUNCTIONS) {
3235
+ // Parse the block if string is passed
3236
+ let ast;
3237
+ if (StringUtils.isString(declarationList)) {
3238
+ ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
3239
+ }
3240
+ else {
3241
+ ast = cloneDeep(declarationList);
3242
+ }
3243
+ // TODO: CSSTree types should be improved, as a workaround we use `any` here
3244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3245
+ return find(ast, (node) => CssTree.isForbiddenFunction(node, forbiddenFunctions)) !== null;
3246
+ }
3247
+ /**
3248
+ * Generates string representation of the media query list.
3249
+ *
3250
+ * @param ast Media query list AST
3251
+ * @returns String representation of the media query list
3252
+ */
3253
+ static generateMediaQueryList(ast) {
3254
+ let result = EMPTY;
3255
+ if (!ast.children || ast.children.size === 0) {
3256
+ throw new Error('Media query list cannot be empty');
3257
+ }
3258
+ ast.children.forEach((mediaQuery, listItem) => {
3259
+ if (mediaQuery.type !== CssTreeNodeType.MediaQuery) {
3260
+ throw new Error(`Unexpected node type: ${mediaQuery.type}`);
3261
+ }
3262
+ result += this.generateMediaQuery(mediaQuery);
3263
+ if (listItem.next !== null) {
3264
+ result += COMMA;
3265
+ result += SPACE;
3266
+ }
3267
+ });
3268
+ return result;
3269
+ }
3270
+ /**
3271
+ * Generates string representation of the media query.
2923
3272
  *
2924
3273
  * @param ast Media query AST
2925
3274
  * @returns String representation of the media query
@@ -3162,6 +3511,150 @@ class CssTree {
3162
3511
  });
3163
3512
  return result.trim();
3164
3513
  }
3514
+ /**
3515
+ * Helper method to assert that the attribute selector has a value
3516
+ *
3517
+ * @param node Attribute selector node
3518
+ */
3519
+ static assertAttributeSelectorHasStringValue(node) {
3520
+ if (!node.value || node.value.type !== CssTreeNodeType.String) {
3521
+ throw new Error(`Invalid argument '${node.value}' for '${node.name.name}', expected a string, but got '${node.value
3522
+ ? node.value.type
3523
+ : 'undefined'}'`);
3524
+ }
3525
+ }
3526
+ /**
3527
+ * Helper method to assert that the pseudo-class selector has at least one argument
3528
+ *
3529
+ * @param node Pseudo-class selector node
3530
+ */
3531
+ static assertPseudoClassHasAnyArgument(node) {
3532
+ if (!node.children || node.children.length === 0) {
3533
+ throw new Error(`Pseudo class '${node.name}' has no argument`);
3534
+ }
3535
+ }
3536
+ /**
3537
+ * Helper method to parse an attribute selector value as a number
3538
+ *
3539
+ * @param node Attribute selector node
3540
+ * @returns Parsed attribute selector value as a number
3541
+ * @throws If the attribute selector hasn't a string value or the string value is can't be parsed as a number
3542
+ */
3543
+ static parseAttributeSelectorValueAsNumber(node) {
3544
+ CssTree.assertAttributeSelectorHasStringValue(node);
3545
+ return StringUtils.parseNumber(node.value.value);
3546
+ }
3547
+ /**
3548
+ * Helper method to parse a pseudo-class argument as a number
3549
+ *
3550
+ * @param node Pseudo-class selector node to parse
3551
+ * @returns Parsed pseudo-class argument as a number
3552
+ */
3553
+ static parsePseudoClassArgumentAsNumber(node) {
3554
+ // Check if the pseudo-class has at least one child
3555
+ CssTree.assertPseudoClassHasAnyArgument(node);
3556
+ // Check if the pseudo-class has only one child
3557
+ if (node.children.length > 1) {
3558
+ throw new Error(`Invalid argument '${node.name}', expected a number, but got multiple arguments`);
3559
+ }
3560
+ // Check if the pseudo-class argument is a string / number / raw
3561
+ const argument = node.children[0];
3562
+ if (argument.type !== CssTreeNodeType.String
3563
+ && argument.type !== CssTreeNodeType.Number
3564
+ && argument.type !== CssTreeNodeType.Raw) {
3565
+ throw new Error(`Invalid argument '${node.name}', expected a ${CssTreeNodeType.String} or ${CssTreeNodeType.Number} or ${CssTreeNodeType.Raw}, but got '${argument.type}'`);
3566
+ }
3567
+ // Parse the argument as a number
3568
+ return StringUtils.parseNumber(argument.value);
3569
+ }
3570
+ /**
3571
+ * Helper method to create an attribute selector node
3572
+ *
3573
+ * @param name Name of the attribute
3574
+ * @param value Value of the attribute
3575
+ * @param matcher Matcher of the attribute
3576
+ * @param flags Flags of the attribute
3577
+ * @returns Attribute selector node
3578
+ * @see {@link https://github.com/csstree/csstree/blob/master/docs/ast.md#attributeselector}
3579
+ */
3580
+ static createAttributeSelectorNode(name, value, matcher = EQUALS, flags = null) {
3581
+ return {
3582
+ type: CssTreeNodeType.AttributeSelector,
3583
+ name: {
3584
+ type: CssTreeNodeType.Identifier,
3585
+ name,
3586
+ },
3587
+ value: {
3588
+ type: CssTreeNodeType.String,
3589
+ value,
3590
+ },
3591
+ matcher,
3592
+ flags,
3593
+ };
3594
+ }
3595
+ /**
3596
+ * Helper function to rename a CSSTree pseudo-class node
3597
+ *
3598
+ * @param node Node to rename
3599
+ * @param name New name
3600
+ */
3601
+ static renamePseudoClass(node, name) {
3602
+ Object.assign(node, {
3603
+ ...node,
3604
+ name,
3605
+ });
3606
+ }
3607
+ /**
3608
+ * Helper function to generate a raw string from a pseudo-class
3609
+ * selector's children
3610
+ *
3611
+ * @param node Pseudo-class selector node
3612
+ * @returns Generated pseudo-class value
3613
+ * @example
3614
+ * - `:nth-child(2n+1)` -> `2n+1`
3615
+ * - `:matches-path(/foo/bar)` -> `/foo/bar`
3616
+ */
3617
+ static generatePseudoClassValue(node) {
3618
+ let result = EMPTY;
3619
+ node.children?.forEach((child) => {
3620
+ switch (child.type) {
3621
+ case CssTreeNodeType.Selector:
3622
+ result += CssTree.generateSelector(child);
3623
+ break;
3624
+ case CssTreeNodeType.SelectorList:
3625
+ result += CssTree.generateSelectorList(child);
3626
+ break;
3627
+ case CssTreeNodeType.Raw:
3628
+ result += child.value;
3629
+ break;
3630
+ default:
3631
+ // Fallback to CSSTree's default generate function
3632
+ result += generate(child);
3633
+ }
3634
+ });
3635
+ return result;
3636
+ }
3637
+ /**
3638
+ * Helper function to generate a raw string from a function selector's children
3639
+ *
3640
+ * @param node Function node
3641
+ * @returns Generated function value
3642
+ * @example `responseheader(name)` -> `name`
3643
+ */
3644
+ static generateFunctionValue(node) {
3645
+ let result = EMPTY;
3646
+ node.children?.forEach((child) => {
3647
+ switch (child.type) {
3648
+ case CssTreeNodeType.Raw:
3649
+ result += child.value;
3650
+ break;
3651
+ default:
3652
+ // Fallback to CSSTree's default generate function
3653
+ result += generate(child);
3654
+ }
3655
+ });
3656
+ return result;
3657
+ }
3165
3658
  }
3166
3659
 
3167
3660
  /**
@@ -3817,13 +4310,14 @@ class ScriptletInjectionBodyParser {
3817
4310
  const openingParenthesesIndex = offset;
3818
4311
  // Find closing parentheses
3819
4312
  // eslint-disable-next-line max-len
3820
- const closingParenthesesIndex = StringUtils.skipWSBack(raw);
3821
- if (raw[closingParenthesesIndex] !== CLOSE_PARENTHESIS) {
4313
+ const closingParenthesesIndex = StringUtils.findUnescapedNonStringNonRegexChar(raw, CLOSE_PARENTHESIS, openingParenthesesIndex + 1);
4314
+ // Closing parentheses should be present
4315
+ if (closingParenthesesIndex === -1) {
3822
4316
  throw new AdblockSyntaxError(
3823
4317
  // eslint-disable-next-line max-len
3824
4318
  `Invalid AdGuard/uBlock scriptlet call, no closing parentheses '${CLOSE_PARENTHESIS}' found`, locRange(loc, offset, raw.length));
3825
4319
  }
3826
- // No unexpected characters after the closing parentheses
4320
+ // Shouldn't have any characters after the closing parentheses
3827
4321
  if (StringUtils.skipWSBack(raw) !== closingParenthesesIndex) {
3828
4322
  throw new AdblockSyntaxError(
3829
4323
  // eslint-disable-next-line max-len
@@ -3875,17 +4369,8 @@ class ScriptletInjectionBodyParser {
3875
4369
  if (semicolonIndex === -1) {
3876
4370
  semicolonIndex = raw.length;
3877
4371
  }
3878
- const scriptletCallEnd = StringUtils.skipWSBack(raw, semicolonIndex - 1) + 1;
3879
- if (scriptletCallEnd <= scriptletCallStart) {
3880
- break;
3881
- }
4372
+ const scriptletCallEnd = Math.max(StringUtils.skipWSBack(raw, semicolonIndex - 1) + 1, scriptletCallStart);
3882
4373
  const params = ParameterListParser.parse(raw.substring(scriptletCallStart, scriptletCallEnd), SPACE, shiftLoc(loc, scriptletCallStart));
3883
- // Check if the scriptlet name is specified
3884
- if (params.children.length === 0) {
3885
- throw new AdblockSyntaxError(
3886
- // eslint-disable-next-line max-len
3887
- 'Invalid ABP snippet call, no scriptlet name specified', locRange(loc, offset, raw.length));
3888
- }
3889
4374
  // Parse the scriptlet call
3890
4375
  result.children.push(params);
3891
4376
  // Skip the semicolon
@@ -3928,8 +4413,11 @@ class ScriptletInjectionBodyParser {
3928
4413
  * @throws If the rule body is not supported by the specified syntax
3929
4414
  * @throws If the AST is invalid
3930
4415
  */
3931
- static generate(ast, syntax = AdblockSyntax.Adg) {
4416
+ static generate(ast, syntax) {
3932
4417
  let result = EMPTY;
4418
+ if (ast.children.length === 0) {
4419
+ throw new Error('Invalid AST, no scriptlet calls specified');
4420
+ }
3933
4421
  // AdGuard and uBlock doesn't support multiple scriptlet calls in one rule
3934
4422
  if (syntax === AdblockSyntax.Adg || syntax === AdblockSyntax.Ubo) {
3935
4423
  if (ast.children.length > 1) {
@@ -4104,6 +4592,266 @@ class HtmlFilteringBodyParser {
4104
4592
  }
4105
4593
  }
4106
4594
 
4595
+ /**
4596
+ * Checks whether the given value is undefined.
4597
+ *
4598
+ * @param value Value to check.
4599
+ *
4600
+ * @returns True if the value type is not 'undefined'.
4601
+ */
4602
+ const isUndefined = (value) => {
4603
+ return typeof value === 'undefined';
4604
+ };
4605
+
4606
+ /**
4607
+ * @file Utility functions for working with modifier nodes
4608
+ */
4609
+ /**
4610
+ * Creates a modifier node
4611
+ *
4612
+ * @param name Name of the modifier
4613
+ * @param value Value of the modifier
4614
+ * @param exception Whether the modifier is an exception
4615
+ * @returns Modifier node
4616
+ */
4617
+ function createModifierNode(name, value = undefined, exception = false) {
4618
+ const result = {
4619
+ type: 'Modifier',
4620
+ exception,
4621
+ modifier: {
4622
+ type: 'Value',
4623
+ value: name,
4624
+ },
4625
+ };
4626
+ if (!isUndefined(value)) {
4627
+ result.value = {
4628
+ type: 'Value',
4629
+ value,
4630
+ };
4631
+ }
4632
+ return result;
4633
+ }
4634
+ /**
4635
+ * Creates a modifier list node
4636
+ *
4637
+ * @param modifiers Modifiers to put in the list (optional, defaults to an empty list)
4638
+ * @returns Modifier list node
4639
+ */
4640
+ function createModifierListNode(modifiers = []) {
4641
+ const result = {
4642
+ type: 'ModifierList',
4643
+ // We need to clone the modifiers to avoid side effects
4644
+ children: cloneDeep(modifiers),
4645
+ };
4646
+ return result;
4647
+ }
4648
+
4649
+ /**
4650
+ * @file Utility to extract UBO rule modifiers from a selector list
4651
+ *
4652
+ * uBO rule modifiers are special pseudo-classes that are used to specify
4653
+ * the rule's behavior, for example, if you want to apply the rule only
4654
+ * to a specific path, you can use the `:matches-path(...)` pseudo-class.
4655
+ */
4656
+ const UBO_MODIFIERS_INDICATOR = ':matches-';
4657
+ const MATCHES_PATH_OPERATOR = 'matches-path';
4658
+ const NOT_OPERATOR = 'not';
4659
+ /**
4660
+ * List of supported UBO rule modifiers
4661
+ */
4662
+ // TODO: Add support for other modifiers, if needed
4663
+ const SUPPORTED_UBO_RULE_MODIFIERS = new Set([
4664
+ MATCHES_PATH_OPERATOR,
4665
+ ]);
4666
+ /**
4667
+ * Fast check to determine if the selector list contains UBO rule modifiers.
4668
+ * This function helps to avoid unnecessary walk through the selector list.
4669
+ *
4670
+ * @param rawSelectorList Raw selector list to check
4671
+ * @returns `true` if the selector list contains UBO rule modifiers, `false` otherwise
4672
+ */
4673
+ function hasUboModifierIndicator(rawSelectorList) {
4674
+ return rawSelectorList.includes(UBO_MODIFIERS_INDICATOR);
4675
+ }
4676
+ /**
4677
+ * Helper function that always returns the linked list version of the
4678
+ * selector node.
4679
+ *
4680
+ * @param selector Selector to process
4681
+ * @returns Linked list based selector
4682
+ */
4683
+ function convertSelectorToLinkedList(selector) {
4684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4685
+ return fromPlainObject(cloneDeep(selector));
4686
+ }
4687
+ /**
4688
+ * Helper function that always returns the linked list version of the
4689
+ * selector list node.
4690
+ *
4691
+ * @param selectorList Selector list to process
4692
+ * @returns Linked list based selector list
4693
+ */
4694
+ function convertSelectorListToLinkedList(selectorList) {
4695
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4696
+ return fromPlainObject(cloneDeep(selectorList));
4697
+ }
4698
+ /**
4699
+ * Helper function for checking and removing bounding combinators
4700
+ *
4701
+ * @param ref Reference to the CSSTree node
4702
+ * @param name Name to error message
4703
+ */
4704
+ function handleBoundingCombinators(ref, name) {
4705
+ // Check preceding combinator
4706
+ if (ref.item.prev?.data.type === CssTreeNodeType.Combinator) {
4707
+ // Special case is space combinator, it's allowed, but should be removed
4708
+ if (ref.item.prev.data.name === SPACE) {
4709
+ // Remove the combinator
4710
+ ref.list?.remove(ref.item.prev);
4711
+ }
4712
+ else {
4713
+ // Throw an error for other combinator types
4714
+ throw new Error(`Unexpected combinator before '${name}'`);
4715
+ }
4716
+ }
4717
+ // Check following combinator
4718
+ if (ref.item.next?.data.type === CssTreeNodeType.Combinator) {
4719
+ // Special case is space combinator, it's allowed, but should be removed
4720
+ if (ref.item.next.data.name === SPACE) {
4721
+ // Remove the combinator
4722
+ ref.list?.remove(ref.item.next);
4723
+ }
4724
+ else {
4725
+ // Throw an error for other combinator types
4726
+ throw new Error(`Unexpected combinator after '${name}'`);
4727
+ }
4728
+ }
4729
+ }
4730
+ /**
4731
+ * Extract UBO rule modifiers from the selector and clean the selector AST from them.
4732
+ *
4733
+ * @param selector Selector to process (can be linked list or array based)
4734
+ * @returns Extracted UBO rule modifiers and cleaned selector list
4735
+ */
4736
+ function extractUboModifiersFromSelector(selector) {
4737
+ // We need a linked list based AST here
4738
+ const cleaned = convertSelectorToLinkedList(selector);
4739
+ // Prepare the modifiers list, we should add the modifiers to it
4740
+ const modifiers = {
4741
+ type: 'ModifierList',
4742
+ children: [],
4743
+ };
4744
+ let depth = 0;
4745
+ let notRef;
4746
+ // Walk through the selector nodes
4747
+ walk(cleaned, {
4748
+ enter: (node, item, list) => {
4749
+ // Don't take into account selectors and selector lists
4750
+ if (node.type === CssTreeNodeType.Selector || node.type === CssTreeNodeType.SelectorList) {
4751
+ return;
4752
+ }
4753
+ // Set the :not() reference if we are on the top level
4754
+ if (node.type === CssTreeNodeType.PseudoClassSelector && node.name === NOT_OPERATOR && depth === 0) {
4755
+ notRef = {
4756
+ node,
4757
+ item,
4758
+ list,
4759
+ };
4760
+ }
4761
+ depth += 1;
4762
+ },
4763
+ leave: (node, item, list) => {
4764
+ // Don't take into account selectors and selector lists
4765
+ if (node.type === CssTreeNodeType.Selector || node.type === CssTreeNodeType.SelectorList) {
4766
+ return;
4767
+ }
4768
+ if (node.type === CssTreeNodeType.PseudoClassSelector) {
4769
+ if (SUPPORTED_UBO_RULE_MODIFIERS.has(node.name)) {
4770
+ // depth should be 1 for :matches-path(...) and 2 for :not(:matches-path(...))
4771
+ if (depth !== (notRef ? 2 : 1)) {
4772
+ throw new Error(`Unexpected depth for ':${node.name}(...)'`);
4773
+ }
4774
+ // uBO modifier can't be preceded nor followed by a combinator
4775
+ handleBoundingCombinators({ node, item, list }, `:${node.name}(...)`);
4776
+ // if we have :not() ref, then we should check if the uBO modifier is the only child of :not()
4777
+ if (notRef && list.size !== 1) {
4778
+ throw new Error(`Unexpected nodes inside ':not(:${node.name}(...))'`);
4779
+ }
4780
+ // Add the modifier to the modifiers list node
4781
+ modifiers.children.push(createModifierNode(node.name, CssTree.generatePseudoClassValue(node),
4782
+ // :not(:matches-path(...)) should be an exception modifier
4783
+ !isUndefined(notRef)));
4784
+ if (notRef) {
4785
+ // If we have :not() ref, then we should remove the :not() node
4786
+ // (which also removes the uBO modifier node, since it's the parent
4787
+ // of the uBO modifier node).
4788
+ // But before removing the :not() node, we should check
4789
+ // :not() isn't preceded nor followed by a combinator.
4790
+ handleBoundingCombinators(notRef, `:not(:${node.name}(...))`);
4791
+ notRef.list?.remove(notRef.item);
4792
+ }
4793
+ else {
4794
+ // Otherwise just remove the uBO modifier node
4795
+ list?.remove(item);
4796
+ }
4797
+ }
4798
+ }
4799
+ depth -= 1;
4800
+ // Reset the :not() ref if we're leaving the :not() node at the top level
4801
+ if (node.type === CssTreeNodeType.PseudoClassSelector && node.name === NOT_OPERATOR && depth === 0) {
4802
+ notRef = undefined;
4803
+ }
4804
+ },
4805
+ });
4806
+ return {
4807
+ modifiers,
4808
+ cleaned: toPlainObject(cleaned),
4809
+ };
4810
+ }
4811
+ /**
4812
+ * Extract UBO rule modifiers from the selector list and clean the selector
4813
+ * list AST from them.
4814
+ *
4815
+ * @param selectorList Selector list to process (can be linked list or array based)
4816
+ * @returns Extracted UBO rule modifiers and cleaned selector list
4817
+ * @example
4818
+ * If you have the following adblock rule:
4819
+ * ```adblock
4820
+ * ##:matches-path(/path) .foo > .bar:has(.baz)
4821
+ * ```
4822
+ * Then this function extracts the `:matches-path(/path)` pseudo-class as
4823
+ * a rule modifier with key `matches-path` and value `/path` and and returns
4824
+ * the following selector list:
4825
+ * ```css
4826
+ * .foo > .bar:has(.baz)
4827
+ * ```
4828
+ * (this is the 'cleaned' selector list - a selector list without the
4829
+ * special uBO pseudo-classes)
4830
+ */
4831
+ function extractUboModifiersFromSelectorList(selectorList) {
4832
+ // We need a linked list based AST here
4833
+ const cleaned = convertSelectorListToLinkedList(selectorList);
4834
+ // Prepare the modifiers list, we should add the modifiers to it
4835
+ const modifiers = {
4836
+ type: 'ModifierList',
4837
+ children: [],
4838
+ };
4839
+ // Walk through the selector list nodes
4840
+ cleaned.children.forEach((child) => {
4841
+ if (child.type === CssTreeNodeType.Selector) {
4842
+ const result = extractUboModifiersFromSelector(child);
4843
+ // Add the modifiers to the modifiers list
4844
+ modifiers.children.push(...result.modifiers.children);
4845
+ // Replace the selector with the cleaned one
4846
+ Object.assign(child, result.cleaned);
4847
+ }
4848
+ });
4849
+ return {
4850
+ modifiers,
4851
+ cleaned: toPlainObject(cleaned),
4852
+ };
4853
+ }
4854
+
4107
4855
  /**
4108
4856
  * `CosmeticRuleParser` is responsible for parsing cosmetic rules.
4109
4857
  *
@@ -4118,6 +4866,7 @@ class HtmlFilteringBodyParser {
4118
4866
  * compatible with the given adblocker. This is a completely natural behavior, meaningful
4119
4867
  * checking of compatibility is not done at the parser level.
4120
4868
  */
4869
+ // TODO: Make raw body parsing optional
4121
4870
  class CosmeticRuleParser {
4122
4871
  /**
4123
4872
  * Determines whether a rule is a cosmetic rule. The rule is considered cosmetic if it
@@ -4205,7 +4954,7 @@ class CosmeticRuleParser {
4205
4954
  if (syntax === AdblockSyntax.Adg) {
4206
4955
  throw new AdblockSyntaxError('AdGuard modifier list is not supported in uBO CSS injection rules', locRange(loc, patternStart, patternEnd));
4207
4956
  }
4208
- return {
4957
+ const uboCssInjectionRuleNode = {
4209
4958
  category: RuleCategory.Cosmetic,
4210
4959
  type: CosmeticRuleType.CssInjectionRule,
4211
4960
  loc: locRange(loc, 0, raw.length),
@@ -4217,10 +4966,29 @@ class CosmeticRuleParser {
4217
4966
  modifiers,
4218
4967
  domains,
4219
4968
  separator,
4220
- body: CssInjectionBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
4969
+ body: {
4970
+ ...CssInjectionBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
4971
+ raw: rawBody,
4972
+ },
4221
4973
  };
4974
+ if (hasUboModifierIndicator(rawBody)) {
4975
+ const extractedUboModifiers = extractUboModifiersFromSelectorList(uboCssInjectionRuleNode.body.selectorList);
4976
+ if (extractedUboModifiers.modifiers.children.length > 0) {
4977
+ if (!uboCssInjectionRuleNode.modifiers) {
4978
+ uboCssInjectionRuleNode.modifiers = {
4979
+ type: 'ModifierList',
4980
+ children: [],
4981
+ };
4982
+ }
4983
+ uboCssInjectionRuleNode.modifiers.children.push(...extractedUboModifiers.modifiers.children);
4984
+ uboCssInjectionRuleNode.body.selectorList = extractedUboModifiers.cleaned;
4985
+ uboCssInjectionRuleNode.syntax = AdblockSyntax.Ubo;
4986
+ }
4987
+ }
4988
+ return uboCssInjectionRuleNode;
4222
4989
  }
4223
- return {
4990
+ // eslint-disable-next-line no-case-declarations
4991
+ const elementHidingRuleNode = {
4224
4992
  category: RuleCategory.Cosmetic,
4225
4993
  type: CosmeticRuleType.ElementHidingRule,
4226
4994
  loc: locRange(loc, 0, raw.length),
@@ -4232,8 +5000,26 @@ class CosmeticRuleParser {
4232
5000
  modifiers,
4233
5001
  domains,
4234
5002
  separator,
4235
- body: ElementHidingBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5003
+ body: {
5004
+ ...ElementHidingBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5005
+ raw: rawBody,
5006
+ },
4236
5007
  };
5008
+ if (hasUboModifierIndicator(rawBody)) {
5009
+ const extractedUboModifiers = extractUboModifiersFromSelectorList(elementHidingRuleNode.body.selectorList);
5010
+ if (extractedUboModifiers.modifiers.children.length > 0) {
5011
+ if (!elementHidingRuleNode.modifiers) {
5012
+ elementHidingRuleNode.modifiers = {
5013
+ type: 'ModifierList',
5014
+ children: [],
5015
+ };
5016
+ }
5017
+ elementHidingRuleNode.modifiers.children.push(...extractedUboModifiers.modifiers.children);
5018
+ elementHidingRuleNode.body.selectorList = extractedUboModifiers.cleaned;
5019
+ elementHidingRuleNode.syntax = AdblockSyntax.Ubo;
5020
+ }
5021
+ }
5022
+ return elementHidingRuleNode;
4237
5023
  // ADG CSS injection / ABP snippet injection
4238
5024
  case '#$#':
4239
5025
  case '#@$#':
@@ -4253,7 +5039,10 @@ class CosmeticRuleParser {
4253
5039
  modifiers,
4254
5040
  domains,
4255
5041
  separator,
4256
- body: CssInjectionBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5042
+ body: {
5043
+ ...CssInjectionBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5044
+ raw: rawBody,
5045
+ },
4257
5046
  };
4258
5047
  }
4259
5048
  // ABP snippet injection
@@ -4273,7 +5062,10 @@ class CosmeticRuleParser {
4273
5062
  modifiers,
4274
5063
  domains,
4275
5064
  separator,
4276
- body: ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Abp, shiftLoc(loc, bodyStart)),
5065
+ body: {
5066
+ ...ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Abp, shiftLoc(loc, bodyStart)),
5067
+ raw: rawBody,
5068
+ },
4277
5069
  };
4278
5070
  }
4279
5071
  // ABP snippet injection is not supported for #$?# and #@$?#
@@ -4297,7 +5089,10 @@ class CosmeticRuleParser {
4297
5089
  modifiers,
4298
5090
  domains,
4299
5091
  separator,
4300
- body: ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Ubo, shiftLoc(loc, bodyStart)),
5092
+ body: {
5093
+ ...ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Ubo, shiftLoc(loc, bodyStart)),
5094
+ raw: rawBody,
5095
+ },
4301
5096
  };
4302
5097
  // ADG JS / scriptlet injection
4303
5098
  case '#%#':
@@ -4317,7 +5112,10 @@ class CosmeticRuleParser {
4317
5112
  modifiers,
4318
5113
  domains,
4319
5114
  separator,
4320
- body: ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Ubo, shiftLoc(loc, bodyStart)),
5115
+ body: {
5116
+ ...ScriptletInjectionBodyParser.parse(rawBody, AdblockSyntax.Ubo, shiftLoc(loc, bodyStart)),
5117
+ raw: rawBody,
5118
+ },
4321
5119
  };
4322
5120
  }
4323
5121
  // Don't allow empty body
@@ -4341,6 +5139,7 @@ class CosmeticRuleParser {
4341
5139
  type: 'Value',
4342
5140
  loc: locRange(loc, bodyStart, bodyEnd),
4343
5141
  value: rawBody,
5142
+ raw: rawBody,
4344
5143
  },
4345
5144
  };
4346
5145
  // uBO HTML filtering
@@ -4349,7 +5148,8 @@ class CosmeticRuleParser {
4349
5148
  if (syntax === AdblockSyntax.Adg) {
4350
5149
  throw new AdblockSyntaxError('AdGuard modifier list is not supported in uBO HTML filtering rules', locRange(loc, patternStart, patternEnd));
4351
5150
  }
4352
- return {
5151
+ // eslint-disable-next-line no-case-declarations
5152
+ const uboHtmlRuleNode = {
4353
5153
  category: RuleCategory.Cosmetic,
4354
5154
  type: CosmeticRuleType.HtmlFilteringRule,
4355
5155
  loc: locRange(loc, 0, raw.length),
@@ -4361,12 +5161,33 @@ class CosmeticRuleParser {
4361
5161
  modifiers,
4362
5162
  domains,
4363
5163
  separator,
4364
- body: HtmlFilteringBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5164
+ body: {
5165
+ ...HtmlFilteringBodyParser.parse(rawBody, shiftLoc(loc, bodyStart)),
5166
+ raw: rawBody,
5167
+ },
4365
5168
  };
5169
+ if (hasUboModifierIndicator(rawBody)
5170
+ && uboHtmlRuleNode.body.body.type === CssTreeNodeType.SelectorList) {
5171
+ // eslint-disable-next-line max-len
5172
+ const extractedUboModifiers = extractUboModifiersFromSelectorList(uboHtmlRuleNode.body.body);
5173
+ if (extractedUboModifiers.modifiers.children.length > 0) {
5174
+ if (!uboHtmlRuleNode.modifiers) {
5175
+ uboHtmlRuleNode.modifiers = {
5176
+ type: 'ModifierList',
5177
+ children: [],
5178
+ };
5179
+ }
5180
+ uboHtmlRuleNode.modifiers.children.push(...extractedUboModifiers.modifiers.children);
5181
+ uboHtmlRuleNode.body.body = extractedUboModifiers.cleaned;
5182
+ uboHtmlRuleNode.syntax = AdblockSyntax.Ubo;
5183
+ }
5184
+ }
5185
+ return uboHtmlRuleNode;
4366
5186
  // ADG HTML filtering
4367
5187
  case '$$':
4368
5188
  case '$@$':
4369
5189
  body = HtmlFilteringBodyParser.parse(rawBody, shiftLoc(loc, bodyStart));
5190
+ body.raw = rawBody;
4370
5191
  if (body.body.type === 'Function') {
4371
5192
  throw new AdblockSyntaxError('Functions are not supported in ADG HTML filtering rules', locRange(loc, bodyStart, bodyEnd));
4372
5193
  }
@@ -4389,12 +5210,16 @@ class CosmeticRuleParser {
4389
5210
  }
4390
5211
  }
4391
5212
  /**
4392
- * Converts a cosmetic rule AST into a string.
5213
+ * Generates the rule pattern from the AST.
4393
5214
  *
4394
5215
  * @param ast Cosmetic rule AST
4395
- * @returns Raw string
5216
+ * @returns Raw rule pattern
5217
+ * @example
5218
+ * - '##.foo' → ''
5219
+ * - 'example.com,example.org##.foo' → 'example.com,example.org'
5220
+ * - '[$path=/foo/bar]example.com##.foo' → '[$path=/foo/bar]example.com'
4396
5221
  */
4397
- static generate(ast) {
5222
+ static generatePattern(ast) {
4398
5223
  let result = EMPTY;
4399
5224
  // AdGuard modifiers (if any)
4400
5225
  if (ast.syntax === AdblockSyntax.Adg && ast.modifiers && ast.modifiers.children.length > 0) {
@@ -4405,49 +5230,93 @@ class CosmeticRuleParser {
4405
5230
  }
4406
5231
  // Domain list (if any)
4407
5232
  result += DomainListParser.generate(ast.domains);
4408
- // Separator
4409
- result += ast.separator.value;
5233
+ return result;
5234
+ }
5235
+ /**
5236
+ * Generates the rule body from the AST.
5237
+ *
5238
+ * @param ast Cosmetic rule AST
5239
+ * @returns Raw rule body
5240
+ * @example
5241
+ * - '##.foo' → '.foo'
5242
+ * - 'example.com,example.org##.foo' → '.foo'
5243
+ * - 'example.com#%#//scriptlet('foo')' → '//scriptlet('foo')'
5244
+ */
5245
+ static generateBody(ast) {
5246
+ let result = EMPTY;
4410
5247
  // Body
4411
5248
  switch (ast.type) {
4412
5249
  case CosmeticRuleType.ElementHidingRule:
4413
- result += ElementHidingBodyParser.generate(ast.body);
5250
+ result = ElementHidingBodyParser.generate(ast.body);
4414
5251
  break;
4415
5252
  case CosmeticRuleType.CssInjectionRule:
4416
- result += CssInjectionBodyParser.generate(ast.body, ast.syntax);
5253
+ result = CssInjectionBodyParser.generate(ast.body, ast.syntax);
4417
5254
  break;
4418
5255
  case CosmeticRuleType.HtmlFilteringRule:
4419
- result += HtmlFilteringBodyParser.generate(ast.body, ast.syntax);
5256
+ result = HtmlFilteringBodyParser.generate(ast.body, ast.syntax);
4420
5257
  break;
4421
5258
  case CosmeticRuleType.JsInjectionRule:
4422
5259
  // Native JS code
4423
- result += ast.body.value;
5260
+ result = ast.body.value;
4424
5261
  break;
4425
5262
  case CosmeticRuleType.ScriptletInjectionRule:
4426
- result += ScriptletInjectionBodyParser.generate(ast.body, ast.syntax);
5263
+ result = ScriptletInjectionBodyParser.generate(ast.body, ast.syntax);
4427
5264
  break;
4428
5265
  default:
4429
5266
  throw new Error('Unknown cosmetic rule type');
4430
5267
  }
4431
5268
  return result;
4432
5269
  }
4433
- }
4434
-
4435
- /**
4436
- * `NetworkRuleParser` is responsible for parsing network rules.
4437
- *
4438
- * Please note that this will parse all syntactically correct network rules.
4439
- * Modifier compatibility is not checked at the parser level.
4440
- *
4441
- * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#basic-rules}
4442
- * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#basic}
4443
- */
4444
- class NetworkRuleParser {
4445
5270
  /**
4446
- * Parses a network rule (also known as basic rule).
5271
+ * Converts a cosmetic rule AST into a string.
4447
5272
  *
4448
- * @param raw Raw rule
4449
- * @param loc Location of the rule
4450
- * @returns Network rule AST
5273
+ * @param ast Cosmetic rule AST
5274
+ * @returns Raw string
5275
+ */
5276
+ static generate(ast) {
5277
+ let result = EMPTY;
5278
+ // Pattern
5279
+ result += CosmeticRuleParser.generatePattern(ast);
5280
+ // Separator
5281
+ result += ast.separator.value;
5282
+ // uBO rule modifiers
5283
+ if (ast.syntax === AdblockSyntax.Ubo && ast.modifiers) {
5284
+ ast.modifiers.children.forEach((modifier) => {
5285
+ result += COLON;
5286
+ result += modifier.modifier.value;
5287
+ if (modifier.value) {
5288
+ result += OPEN_PARENTHESIS;
5289
+ result += modifier.value.value;
5290
+ result += CLOSE_PARENTHESIS;
5291
+ }
5292
+ });
5293
+ // If there are at least one modifier, add a space
5294
+ if (ast.modifiers.children.length) {
5295
+ result += SPACE;
5296
+ }
5297
+ }
5298
+ // Body
5299
+ result += CosmeticRuleParser.generateBody(ast);
5300
+ return result;
5301
+ }
5302
+ }
5303
+
5304
+ /**
5305
+ * `NetworkRuleParser` is responsible for parsing network rules.
5306
+ *
5307
+ * Please note that this will parse all syntactically correct network rules.
5308
+ * Modifier compatibility is not checked at the parser level.
5309
+ *
5310
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#basic-rules}
5311
+ * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#basic}
5312
+ */
5313
+ class NetworkRuleParser {
5314
+ /**
5315
+ * Parses a network rule (also known as basic rule).
5316
+ *
5317
+ * @param raw Raw rule
5318
+ * @param loc Location of the rule
5319
+ * @returns Network rule AST
4451
5320
  */
4452
5321
  static parse(raw, loc = defaultLocation) {
4453
5322
  let offset = 0;
@@ -4836,6 +5705,3037 @@ class FilterListParser {
4836
5705
  }
4837
5706
  }
4838
5707
 
5708
+ /**
5709
+ * @file Customized error class for not implemented features.
5710
+ */
5711
+ const ERROR_NAME$1 = 'NotImplementedError';
5712
+ const BASE_MESSAGE = 'Not implemented';
5713
+ /**
5714
+ * Customized error class for not implemented features.
5715
+ */
5716
+ class NotImplementedError extends Error {
5717
+ /**
5718
+ * Constructs a new `NotImplementedError` instance.
5719
+ *
5720
+ * @param message Additional error message (optional)
5721
+ */
5722
+ constructor(message = undefined) {
5723
+ // Prepare the full error message
5724
+ const fullMessage = message
5725
+ ? `${BASE_MESSAGE}: ${message}`
5726
+ : BASE_MESSAGE;
5727
+ super(fullMessage);
5728
+ this.name = ERROR_NAME$1;
5729
+ }
5730
+ }
5731
+
5732
+ /**
5733
+ * @file Customized error class for conversion errors.
5734
+ */
5735
+ const ERROR_NAME = 'RuleConversionError';
5736
+ /**
5737
+ * Customized error class for conversion errors.
5738
+ */
5739
+ class RuleConversionError extends Error {
5740
+ /**
5741
+ * Constructs a new `RuleConversionError` instance.
5742
+ *
5743
+ * @param message Error message
5744
+ */
5745
+ constructor(message) {
5746
+ super(message);
5747
+ this.name = ERROR_NAME;
5748
+ }
5749
+ }
5750
+
5751
+ var data$T = { adg_os_any:{ name:"all",
5752
+ description:"$all modifier is made of $document, $popup, and all content-type modifiers combined.",
5753
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
5754
+ negatable:false,
5755
+ block_only:true },
5756
+ adg_ext_any:{ name:"all",
5757
+ description:"$all modifier is made of $document, $popup, and all content-type modifiers combined.",
5758
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
5759
+ negatable:false,
5760
+ block_only:true },
5761
+ adg_cb_ios:{ name:"all",
5762
+ description:"The `$all` modifier is made of `$document`, `$popup`, and all content-type modifiers combined.",
5763
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
5764
+ negatable:false,
5765
+ block_only:true },
5766
+ adg_cb_safari:{ name:"all",
5767
+ description:"The `$all` modifier is made of `$document`, `$popup`, and all content-type modifiers combined.",
5768
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
5769
+ negatable:false,
5770
+ block_only:true },
5771
+ ubo_ext_any:{ name:"all",
5772
+ description:"The `all` option is equivalent to specifying all network-based types\n+ `popup`, `document`, `inline-font` and `inline-script`.",
5773
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#all",
5774
+ negatable:false,
5775
+ block_only:false } };
5776
+
5777
+ var data$S = { adg_os_any:{ name:"app",
5778
+ 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.",
5779
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier",
5780
+ assignable:true,
5781
+ negatable:false } };
5782
+
5783
+ var data$R = { adg_os_any:{ name:"badfilter",
5784
+ 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).",
5785
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
5786
+ negatable:false },
5787
+ adg_ext_any:{ name:"badfilter",
5788
+ 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).",
5789
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
5790
+ negatable:false },
5791
+ adg_cb_ios:{ name:"badfilter",
5792
+ 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).",
5793
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
5794
+ negatable:false },
5795
+ adg_cb_safari:{ name:"badfilter",
5796
+ 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).",
5797
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
5798
+ negatable:false },
5799
+ ubo_ext_any:{ name:"badfilter",
5800
+ 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).",
5801
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#badfilter",
5802
+ negatable:false } };
5803
+
5804
+ var data$Q = { ubo_ext_any:{ name:"cname",
5805
+ description:"When used in an exception filter,\nit will bypass blocking CNAME uncloaked requests for the current (specified) document.",
5806
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#cname",
5807
+ negatable:false,
5808
+ exception_only:true } };
5809
+
5810
+ var data$P = { adg_os_any:{ name:"content",
5811
+ description:"Disables HTML filtering and `$replace` rules on the pages that match the rule.",
5812
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#content-modifier",
5813
+ negatable:false,
5814
+ exception_only:true } };
5815
+
5816
+ var data$O = { adg_os_any:{ name:"cookie",
5817
+ description:"The `$cookie` modifier completely changes rule behavior.\nInstead of blocking a request, this modifier makes us suppress or modify the Cookie and Set-Cookie headers.",
5818
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#cookie-modifier",
5819
+ assignable:true,
5820
+ negatable:false,
5821
+ value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" },
5822
+ adg_ext_any:{ name:"cookie",
5823
+ description:"The `$cookie` modifier completely changes rule behavior.\nInstead of blocking a request, this modifier makes us suppress or modify the Cookie and Set-Cookie headers.",
5824
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#cookie-modifier",
5825
+ assignable:true,
5826
+ negatable:false,
5827
+ value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" } };
5828
+
5829
+ var data$N = { adg_os_any:{ name:"csp",
5830
+ 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.",
5831
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
5832
+ conflicts:[ "domain",
5833
+ "important",
5834
+ "subdocument",
5835
+ "badfilter" ],
5836
+ inverse_conflicts:true,
5837
+ assignable:true,
5838
+ negatable:false,
5839
+ value_format:"/[^,$]+/" },
5840
+ adg_ext_any:{ name:"csp",
5841
+ 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.",
5842
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
5843
+ conflicts:[ "domain",
5844
+ "important",
5845
+ "subdocument",
5846
+ "badfilter" ],
5847
+ inverse_conflicts:true,
5848
+ assignable:true,
5849
+ negatable:false,
5850
+ value_format:"/[^,$]+/" },
5851
+ abp_ext_any:{ name:"csp",
5852
+ 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.",
5853
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#content-security-policies",
5854
+ conflicts:[ "domain",
5855
+ "subdocument" ],
5856
+ inverse_conflicts:true,
5857
+ assignable:true,
5858
+ negatable:false,
5859
+ value_format:"/[^,$]+/" },
5860
+ ubo_ext_any:{ name:"csp",
5861
+ 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.",
5862
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#csp",
5863
+ conflicts:[ "1p",
5864
+ "3p",
5865
+ "domain",
5866
+ "badfilter" ],
5867
+ inverse_conflicts:true,
5868
+ assignable:true,
5869
+ negatable:false,
5870
+ value_format:"/[^,$]+/" } };
5871
+
5872
+ var data$M = { adg_os_any:{ name:"denyallow",
5873
+ 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.",
5874
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5875
+ conflicts:[ "to" ],
5876
+ assignable:true,
5877
+ negatable:false,
5878
+ value_format:"pipe_separated_domains" },
5879
+ adg_ext_any:{ name:"denyallow",
5880
+ 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
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5882
+ conflicts:[ "to" ],
5883
+ assignable:true,
5884
+ negatable:false,
5885
+ value_format:"pipe_separated_domains" },
5886
+ adg_cb_ios:{ name:"denyallow",
5887
+ 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
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5889
+ conflicts:[ "to" ],
5890
+ assignable:true,
5891
+ negatable:false,
5892
+ value_format:"pipe_separated_domains" },
5893
+ adg_cb_safari:{ name:"denyallow",
5894
+ 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
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
5896
+ conflicts:[ "to" ],
5897
+ assignable:true,
5898
+ negatable:false,
5899
+ value_format:"pipe_separated_domains" },
5900
+ ubo_ext_any:{ name:"denyallow",
5901
+ 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
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#denyallow",
5903
+ conflicts:[ "to" ],
5904
+ assignable:true,
5905
+ negatable:false,
5906
+ value_format:"pipe_separated_domains" } };
5907
+
5908
+ var data$L = { adg_os_any:{ name:"document",
5909
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5910
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
5911
+ negatable:false },
5912
+ adg_ext_any:{ name:"document",
5913
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5914
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
5915
+ negatable:false },
5916
+ adg_cb_ios:{ name:"document",
5917
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5918
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
5919
+ negatable:false },
5920
+ adg_cb_safari:{ name:"document",
5921
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5922
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
5923
+ negatable:false },
5924
+ abp_ext_any:{ name:"document",
5925
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5926
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#allowlist",
5927
+ negatable:false },
5928
+ ubo_ext_any:{ name:"document",
5929
+ aliases:[ "doc" ],
5930
+ description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
5931
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#document",
5932
+ negatable:false } };
5933
+
5934
+ var data$K = { adg_any:{ name:"domain",
5935
+ aliases:[ "from" ],
5936
+ description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
5937
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#domain-modifier",
5938
+ assignable:true,
5939
+ negatable:false,
5940
+ value_format:"pipe_separated_domains" },
5941
+ abp_any:{ name:"domain",
5942
+ description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
5943
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#domain-restrictions",
5944
+ assignable:true,
5945
+ negatable:false,
5946
+ value_format:"pipe_separated_domains" },
5947
+ ubo_any:{ name:"domain",
5948
+ aliases:[ "from" ],
5949
+ description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
5950
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#from",
5951
+ assignable:true,
5952
+ negatable:false,
5953
+ value_format:"pipe_separated_domains" } };
5954
+
5955
+ var data$J = { adg_any:{ name:"elemhide",
5956
+ aliases:[ "ehide" ],
5957
+ description:"Disables any cosmetic rules on the pages matching the rule.",
5958
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#elemhide-modifier",
5959
+ negatable:false,
5960
+ exception_only:true },
5961
+ abp_any:{ name:"elemhide",
5962
+ aliases:[ "ehide" ],
5963
+ description:"Disables any cosmetic rules on the pages matching the rule.",
5964
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
5965
+ negatable:false,
5966
+ exception_only:true },
5967
+ ubo_any:{ name:"elemhide",
5968
+ aliases:[ "ehide" ],
5969
+ negatable:false,
5970
+ exception_only:true,
5971
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#elemhide-1",
5972
+ description:"Disables any cosmetic rules on the pages matching the rule." } };
5973
+
5974
+ var data$I = { adg_os_any:{ name:"empty",
5975
+ description:"This modifier is deprecated in favor of the $redirect modifier.\nRules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
5976
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#empty-modifier",
5977
+ deprecated:true,
5978
+ deprecation_message:"Rules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
5979
+ negatable:false },
5980
+ adg_ext_any:{ name:"empty",
5981
+ description:"This modifier is deprecated in favor of the $redirect modifier.\nRules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
5982
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#empty-modifier",
5983
+ deprecated:true,
5984
+ deprecation_message:"Rules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
5985
+ negatable:false },
5986
+ ubo_ext_any:{ name:"empty",
5987
+ description:"This modifier is deprecated in favor of the $redirect modifier.\nRules with `$empty` are supported and being converted into `$redirect=nooptext`.",
5988
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#empty",
5989
+ negatable:false } };
5990
+
5991
+ var data$H = { adg_any:{ name:"first-party",
5992
+ aliases:[ "1p",
5993
+ "~third-party" ],
5994
+ description:"A restriction of first-party requests. Equal to `~third-party`.",
5995
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#third-party-modifier",
5996
+ negatable:false },
5997
+ ubo_any:{ name:"first-party",
5998
+ aliases:[ "1p",
5999
+ "~third-party" ],
6000
+ description:"A restriction of first-party requests. Equal to `~third-party`.",
6001
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#1p",
6002
+ negatable:false } };
6003
+
6004
+ var data$G = { adg_os_any:{ name:"extension",
6005
+ description:"Disables all userscripts on the pages matching this rule.",
6006
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#extension-modifier",
6007
+ conflicts:[ "domain",
6008
+ "specifichide",
6009
+ "generichide",
6010
+ "elemhide",
6011
+ "genericblock",
6012
+ "urlblock",
6013
+ "jsinject",
6014
+ "content",
6015
+ "xmlhttprequest",
6016
+ "badfilter" ],
6017
+ inverse_conflicts:true,
6018
+ exception_only:true } };
6019
+
6020
+ var data$F = { adg_any:{ name:"font",
6021
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#font-modifier",
6022
+ description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
6023
+ abp_any:{ name:"font",
6024
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
6025
+ description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
6026
+ ubo_any:{ name:"font",
6027
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
6028
+ description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." } };
6029
+
6030
+ var data$E = { adg_os_any:{ name:"genericblock",
6031
+ description:"Disables generic basic rules on pages that correspond to exception rule.",
6032
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6033
+ conflicts:[ "domain",
6034
+ "specifichide",
6035
+ "generichide",
6036
+ "elemhide",
6037
+ "extension",
6038
+ "jsinject",
6039
+ "content",
6040
+ "badfilter" ],
6041
+ inverse_conflicts:true,
6042
+ negatable:false,
6043
+ exception_only:true },
6044
+ adg_ext_any:{ name:"genericblock",
6045
+ description:"Disables generic basic rules on pages that correspond to exception rule.",
6046
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6047
+ conflicts:[ "domain",
6048
+ "specifichide",
6049
+ "generichide",
6050
+ "elemhide",
6051
+ "jsinject",
6052
+ "badfilter" ],
6053
+ inverse_conflicts:true,
6054
+ negatable:false,
6055
+ exception_only:true },
6056
+ adg_cb_ios:{ name:"genericblock",
6057
+ description:"Disables generic basic rules on pages that correspond to exception rule.",
6058
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6059
+ conflicts:[ "domain",
6060
+ "specifichide",
6061
+ "generichide",
6062
+ "elemhide",
6063
+ "jsinject",
6064
+ "badfilter" ],
6065
+ inverse_conflicts:true,
6066
+ negatable:false,
6067
+ exception_only:true },
6068
+ adg_cb_safari:{ name:"genericblock",
6069
+ description:"Disables generic basic rules on pages that correspond to exception rule.",
6070
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6071
+ conflicts:[ "domain",
6072
+ "specifichide",
6073
+ "generichide",
6074
+ "elemhide",
6075
+ "jsinject",
6076
+ "badfilter" ],
6077
+ inverse_conflicts:true,
6078
+ negatable:false,
6079
+ exception_only:true },
6080
+ abp_ext_any:{ name:"genericblock",
6081
+ description:"Disables generic basic rules on pages that correspond to exception rule.",
6082
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6083
+ negatable:false,
6084
+ exception_only:true } };
6085
+
6086
+ var data$D = { adg_any:{ name:"generichide",
6087
+ aliases:[ "ghide" ],
6088
+ description:"Disables all generic cosmetic rules.",
6089
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#generichide-modifier",
6090
+ conflicts:[ "domain",
6091
+ "genericblock",
6092
+ "urlblock",
6093
+ "extension",
6094
+ "jsinject",
6095
+ "content",
6096
+ "xmlhttprequest",
6097
+ "badfilter" ],
6098
+ inverse_conflicts:true,
6099
+ negatable:false,
6100
+ exception_only:true },
6101
+ ubo_any:{ name:"generichide",
6102
+ aliases:[ "ghide" ],
6103
+ description:"Disables all generic cosmetic rules.",
6104
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#generichide",
6105
+ conflicts:[ "domain",
6106
+ "badfilter" ],
6107
+ inverse_conflicts:true,
6108
+ negatable:false,
6109
+ exception_only:true },
6110
+ abp_any:{ name:"generichide",
6111
+ description:"Disables all generic cosmetic rules.",
6112
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6113
+ conflicts:[ "domain" ],
6114
+ inverse_conflicts:true,
6115
+ negatable:false,
6116
+ exception_only:true } };
6117
+
6118
+ var data$C = { adg_os_any:{ name:"header",
6119
+ description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6120
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6121
+ assignable:true,
6122
+ value_format:"/^[A-z0-9-]+(:.+|)$/" },
6123
+ adg_ext_any:{ name:"header",
6124
+ description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6125
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6126
+ assignable:true,
6127
+ value_format:"/^[A-z0-9-]+(:.+|)$/" },
6128
+ ubo_ext_any:{ name:"header",
6129
+ description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6130
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#header",
6131
+ assignable:true,
6132
+ value_format:"/^[A-z0-9-]+(:.+|)$/" } };
6133
+
6134
+ var data$B = { adg_os_any:{ name:"hls",
6135
+ 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).",
6136
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#hls-modifier",
6137
+ version_added:"CoreLibs 1.10",
6138
+ conflicts:[ "domain",
6139
+ "third-party",
6140
+ "app",
6141
+ "important",
6142
+ "match-case",
6143
+ "xmlhttprequest" ],
6144
+ inverse_conflicts:true,
6145
+ assignable:true,
6146
+ negatable:false,
6147
+ value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6148
+
6149
+ var data$A = { adg_any:{ name:"image",
6150
+ description:"The rule corresponds to images requests.",
6151
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#image-modifier" },
6152
+ abp_any:{ name:"image",
6153
+ description:"The rule corresponds to images requests.",
6154
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" },
6155
+ ubo_any:{ name:"image",
6156
+ description:"The rule corresponds to images requests.",
6157
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6158
+
6159
+ var data$z = { adg_any:{ name:"important",
6160
+ description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
6161
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#important-modifier",
6162
+ negatable:false },
6163
+ ubo_any:{ name:"important",
6164
+ description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
6165
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#important",
6166
+ negatable:false } };
6167
+
6168
+ var data$y = { adg_os_any:{ name:"inline-font",
6169
+ description:"The `$inline-font` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-font` is converting into:\n```adblock\n||example.org^$csp=font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6170
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
6171
+ adg_ext_any:{ name:"inline-font",
6172
+ description:"The `$inline-font` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-font` is converting into:\n```adblock\n||example.org^$csp=font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6173
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
6174
+ ubo_ext_any:{ name:"inline-font",
6175
+ description:"The `$inline-font` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-font` is converting into:\n```adblock\n||example.org^$csp=font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6176
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-font" } };
6177
+
6178
+ var data$x = { adg_os_any:{ name:"inline-script",
6179
+ description:"The `$inline-script` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-script` is converting into:\n```adblock\n||example.org^$csp=script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6180
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
6181
+ adg_ext_any:{ name:"inline-script",
6182
+ description:"The `$inline-script` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-script` is converting into:\n```adblock\n||example.org^$csp=script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6183
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
6184
+ ubo_ext_any:{ name:"inline-script",
6185
+ description:"The `$inline-script` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-script` is converting into:\n```adblock\n||example.org^$csp=script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6186
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-script" } };
6187
+
6188
+ var data$w = { adg_os_any:{ name:"jsinject",
6189
+ description:"Forbids adding of javascript code to the page.",
6190
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6191
+ conflicts:[ "domain",
6192
+ "specifichide",
6193
+ "generichide",
6194
+ "elemhide",
6195
+ "genericblock",
6196
+ "urlblock",
6197
+ "extension",
6198
+ "content",
6199
+ "xmlhttprequest",
6200
+ "badfilter" ],
6201
+ inverse_conflicts:true,
6202
+ negatable:false,
6203
+ exception_only:true },
6204
+ adg_ext_any:{ name:"jsinject",
6205
+ description:"Forbids adding of javascript code to the page.",
6206
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6207
+ conflicts:[ "domain",
6208
+ "specifichide",
6209
+ "generichide",
6210
+ "elemhide",
6211
+ "genericblock",
6212
+ "urlblock",
6213
+ "xmlhttprequest",
6214
+ "badfilter" ],
6215
+ inverse_conflicts:true,
6216
+ negatable:false,
6217
+ exception_only:true },
6218
+ adg_cb_ios:{ name:"jsinject",
6219
+ description:"Forbids adding of javascript code to the page.",
6220
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6221
+ conflicts:[ "domain",
6222
+ "specifichide",
6223
+ "generichide",
6224
+ "elemhide",
6225
+ "genericblock",
6226
+ "urlblock",
6227
+ "xmlhttprequest",
6228
+ "badfilter" ],
6229
+ inverse_conflicts:true,
6230
+ negatable:false,
6231
+ exception_only:true },
6232
+ adg_cb_safari:{ name:"jsinject",
6233
+ description:"Forbids adding of javascript code to the page.",
6234
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6235
+ conflicts:[ "domain",
6236
+ "specifichide",
6237
+ "generichide",
6238
+ "elemhide",
6239
+ "genericblock",
6240
+ "urlblock",
6241
+ "xmlhttprequest",
6242
+ "badfilter" ],
6243
+ inverse_conflicts:true,
6244
+ negatable:false,
6245
+ exception_only:true } };
6246
+
6247
+ var data$v = { adg_os_any:{ name:"jsonprune",
6248
+ description:"The `$jsonprune` rules modify the response to a matching request\nby removing JSON items that match a modified JSONPath expression.\nThey do not modify responses which are not valid JSON documents.",
6249
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
6250
+ assignable:true,
6251
+ negatable:false,
6252
+ value_format:"/^\\\\\\$\\.(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6253
+
6254
+ var data$u = { adg_any:{ name:"match-case",
6255
+ description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6256
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#match-case-modifier" },
6257
+ abp_any:{ name:"match-case",
6258
+ description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6259
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6260
+ ubo_any:{ name:"match-case",
6261
+ description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6262
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#match-case" } };
6263
+
6264
+ var data$t = { adg_any:{ name:"media",
6265
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6266
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#media-modifier" },
6267
+ abp_any:{ name:"media",
6268
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6269
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" },
6270
+ ubo_any:{ name:"media",
6271
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6272
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6273
+
6274
+ var data$s = { adg_os_any:{ name:"method",
6275
+ description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6276
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6277
+ negatable:false,
6278
+ 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|(\\|?|~?))+(?<!\\|)$" },
6280
+ adg_ext_any:{ name:"method",
6281
+ description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6282
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6283
+ negatable:false,
6284
+ 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|(\\|?|~?))+(?<!\\|)$" },
6286
+ ubo_ext_any:{ name:"method",
6287
+ description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6288
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#method",
6289
+ negatable:false,
6290
+ 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|(\\|?|~?))+(?<!\\|)$" } };
6292
+
6293
+ var data$r = { adg_os_any:{ name:"mp4",
6294
+ 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.",
6295
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#mp4-modifier",
6296
+ deprecated:true,
6297
+ deprecation_message:"Rules with `$mp4` are still supported and being converted into `$redirect=noopmp4-1s` now\nbut the support shall be removed in the future.",
6298
+ negatable:false },
6299
+ adg_ext_any:{ name:"mp4",
6300
+ 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.",
6301
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#mp4-modifier",
6302
+ deprecated:true,
6303
+ deprecation_message:"Rules with `$mp4` are still supported and being converted into `$redirect=noopmp4-1s` now\nbut the support shall be removed in the future.",
6304
+ negatable:false },
6305
+ ubo_ext_any:{ name:"mp4",
6306
+ description:"As a response to blocked request a short video placeholder is returned.\nRules with `$mp4` are supported and being converted into `$redirect=noopmp4-1s`.",
6307
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#mp4",
6308
+ negatable:false } };
6309
+
6310
+ var data$q = { adg_os_any:{ name:"network",
6311
+ description:"This is basically a Firewall-kind of rules allowing to fully block\nor unblock access to a specified remote address.",
6312
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#network-modifier",
6313
+ conflicts:[ "app",
6314
+ "important" ],
6315
+ inverse_conflicts:true,
6316
+ negatable:false } };
6317
+
6318
+ var data$p = { adg_os_any:{ name:"_",
6319
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6320
+ description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6321
+ negatable:false },
6322
+ adg_ext_any:{ name:"_",
6323
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6324
+ description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6325
+ negatable:false },
6326
+ adg_cb_ios:{ name:"_",
6327
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6328
+ description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6329
+ negatable:false },
6330
+ adg_cb_safari:{ name:"_",
6331
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6332
+ description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6333
+ negatable:false },
6334
+ ubo_ext_any:{ name:"_",
6335
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#_-aka-noop",
6336
+ description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6337
+ negatable:false } };
6338
+
6339
+ var data$o = { adg_any:{ name:"object-subrequest",
6340
+ description:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid.\nThe rule corresponds to requests by browser plugins (it is usually Flash).",
6341
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-subrequest-modifier",
6342
+ removed:true,
6343
+ removal_message:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid." } };
6344
+
6345
+ var data$n = { adg_any:{ name:"object",
6346
+ description:"The rule corresponds to browser plugins resources, e.g. Java or Flash",
6347
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-modifier" },
6348
+ abp_any:{ name:"object",
6349
+ description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
6350
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6351
+ ubo_any:{ name:"object",
6352
+ description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
6353
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6354
+
6355
+ var data$m = { adg_any:{ name:"other",
6356
+ description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6357
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#other-modifier" },
6358
+ abp_any:{ name:"other",
6359
+ description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6360
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6361
+ ubo_any:{ name:"other",
6362
+ description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6363
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6364
+
6365
+ var data$l = { adg_os_any:{ name:"permissions",
6366
+ description:"For the requests matching a `$permissions` rule, ad blocker strengthens response's feature policy\nby adding additional feature policy equal to the `$permissions` modifier contents.\n`$permissions` rules are applied independently from any other rule type.",
6367
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier",
6368
+ version_added:"CoreLibs 1.11",
6369
+ conflicts:[ "domain",
6370
+ "important",
6371
+ "subdocument" ],
6372
+ inverse_conflicts:true,
6373
+ assignable:true,
6374
+ 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 )=\\(\\)(\\\\, )?)+(?<!,)$" } };
6376
+
6377
+ var data$k = { adg_any:{ name:"ping",
6378
+ description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6379
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#ping-modifier" },
6380
+ abp_any:{ name:"ping",
6381
+ description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6382
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6383
+ ubo_any:{ name:"ping",
6384
+ description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6385
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6386
+
6387
+ var data$j = { ubo_ext_any:{ name:"popunder",
6388
+ description:"To block \"popunders\" windows/tabs where the original page redirects to an advertisement\nand the desired content loads in the newly created one.\nTo be used in the same manner as the popup filter option, except that it will block popunders.",
6389
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#popunder",
6390
+ negatable:false,
6391
+ block_only:true } };
6392
+
6393
+ var data$i = { adg_any:{ name:"popup",
6394
+ description:"Pages opened in a new tab or window.\nNote: Filters will not block pop-ups by default, only if the `$popup` type option is specified.",
6395
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#popup-modifier",
6396
+ negatable:false },
6397
+ abp_any:{ name:"popup",
6398
+ description:"Pages opened in a new tab or window.\nNote: Filters will not block pop-ups by default, only if the `$popup` type option is specified.",
6399
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6400
+ negatable:false },
6401
+ ubo_any:{ name:"popup",
6402
+ description:"Pages opened in a new tab or window.\nNote: Filters will not block pop-ups by default, only if the `$popup` type option is specified.",
6403
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6404
+ negatable:false } };
6405
+
6406
+ var data$h = { adg_os_any:{ name:"redirect-rule",
6407
+ description:"This is basically an alias to `$redirect`\nsince it has the same \"redirection\" values and the logic is almost similar.\nThe difference is that `$redirect-rule` is applied only in the case\nwhen the target request is blocked by a different basic rule.",
6408
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
6409
+ conflicts:[ "domain",
6410
+ "to",
6411
+ "third-party",
6412
+ "popup",
6413
+ "match-case",
6414
+ "header",
6415
+ "first-party",
6416
+ "document",
6417
+ "image",
6418
+ "stylesheet",
6419
+ "script",
6420
+ "object",
6421
+ "font",
6422
+ "media",
6423
+ "subdocument",
6424
+ "ping",
6425
+ "xmlhttprequest",
6426
+ "websocket",
6427
+ "other",
6428
+ "webrtc",
6429
+ "important",
6430
+ "badfilter",
6431
+ "app" ],
6432
+ inverse_conflicts:true,
6433
+ assignable:true,
6434
+ negatable:false,
6435
+ 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 )?$" },
6436
+ adg_ext_any:{ name:"redirect-rule",
6437
+ description:"This is basically an alias to `$redirect`\nsince it has the same \"redirection\" values and the logic is almost similar.\nThe difference is that `$redirect-rule` is applied only in the case\nwhen the target request is blocked by a different basic rule.",
6438
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
6439
+ conflicts:[ "domain",
6440
+ "to",
6441
+ "third-party",
6442
+ "popup",
6443
+ "match-case",
6444
+ "header",
6445
+ "first-party",
6446
+ "document",
6447
+ "image",
6448
+ "stylesheet",
6449
+ "script",
6450
+ "object",
6451
+ "font",
6452
+ "media",
6453
+ "subdocument",
6454
+ "ping",
6455
+ "xmlhttprequest",
6456
+ "websocket",
6457
+ "other",
6458
+ "webrtc",
6459
+ "important",
6460
+ "badfilter" ],
6461
+ inverse_conflicts:true,
6462
+ assignable:true,
6463
+ negatable:false,
6464
+ 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 )?$" },
6465
+ ubo_ext_any:{ name:"redirect-rule",
6466
+ description:"This is basically an alias to `$redirect`\nsince it has the same \"redirection\" values and the logic is almost similar.\nThe difference is that `$redirect-rule` is applied only in the case\nwhen the target request is blocked by a different basic rule.",
6467
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect-rule",
6468
+ conflicts:[ "domain",
6469
+ "to",
6470
+ "third-party",
6471
+ "popup",
6472
+ "match-case",
6473
+ "header",
6474
+ "first-party",
6475
+ "document",
6476
+ "image",
6477
+ "stylesheet",
6478
+ "script",
6479
+ "object",
6480
+ "font",
6481
+ "media",
6482
+ "subdocument",
6483
+ "ping",
6484
+ "xmlhttprequest",
6485
+ "websocket",
6486
+ "other",
6487
+ "webrtc",
6488
+ "important",
6489
+ "badfilter" ],
6490
+ inverse_conflicts:true,
6491
+ assignable:true,
6492
+ negatable:false,
6493
+ value_format:"(?x)\n ^(\n 1x1\\.gif|\n 2x2\\.png|\n 3x2\\.png|\n 32x32\\.png|\n noop\\.css|\n noop\\.html|\n noopframe|\n noop\\.js|\n noop\\.txt|\n noop-0\\.1s\\.mp3|\n noop-0\\.5s\\.mp3|\n noop-1s\\.mp4|\n none|\n click2load\\.html|\n addthis_widget\\.js|\n amazon_ads\\.js|\n amazon_apstag\\.js|\n monkeybroker\\.js|\n doubleclick_instream_ad_status|\n google-analytics_ga\\.js|\n google-analytics_analytics\\.js|\n google-analytics_inpage_linkid\\.js|\n google-analytics_cx_api\\.js|\n google-ima\\.js|\n googletagservices_gpt\\.js|\n googletagmanager_gtm\\.js|\n googlesyndication_adsbygoogle\\.js|\n scorecardresearch_beacon\\.js|\n outbrain-widget\\.js|\n hd-main\\.js\n )\n (:[0-9]+)?$" } };
6494
+
6495
+ var data$g = { adg_os_any:{ name:"redirect",
6496
+ description:"Used to redirect web requests to a local \"resource\".",
6497
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6498
+ assignable:true,
6499
+ negatable:false,
6500
+ 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 )?$" },
6501
+ adg_ext_any:{ name:"redirect",
6502
+ description:"Used to redirect web requests to a local \"resource\".",
6503
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6504
+ assignable:true,
6505
+ negatable:false,
6506
+ 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 )?$" },
6507
+ ubo_ext_any:{ name:"redirect",
6508
+ description:"Used to redirect web requests to a local \"resource\".",
6509
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect",
6510
+ assignable:true,
6511
+ negatable:false,
6512
+ value_format:"(?x)\n ^(\n 1x1\\.gif|\n 2x2\\.png|\n 3x2\\.png|\n 32x32\\.png|\n noop\\.css|\n noop\\.html|\n noopframe|\n noop\\.js|\n noop\\.txt|\n noop-0\\.1s\\.mp3|\n noop-0\\.5s\\.mp3|\n noop-1s\\.mp4|\n none|\n click2load\\.html|\n addthis_widget\\.js|\n amazon_ads\\.js|\n amazon_apstag\\.js|\n monkeybroker\\.js|\n doubleclick_instream_ad_status|\n google-analytics_ga\\.js|\n google-analytics_analytics\\.js|\n google-analytics_inpage_linkid\\.js|\n google-analytics_cx_api\\.js|\n google-ima\\.js|\n googletagservices_gpt\\.js|\n googletagmanager_gtm\\.js|\n googlesyndication_adsbygoogle\\.js|\n scorecardresearch_beacon\\.js|\n outbrain-widget\\.js|\n hd-main\\.js\n )\n (:[0-9]+)?$" } };
6513
+
6514
+ var data$f = { adg_os_any:{ name:"removeheader",
6515
+ description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
6516
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
6517
+ conflicts:[ "domain",
6518
+ "third-party",
6519
+ "first-party",
6520
+ "app",
6521
+ "important",
6522
+ "match-case",
6523
+ "document",
6524
+ "image",
6525
+ "stylesheet",
6526
+ "script",
6527
+ "object",
6528
+ "font",
6529
+ "media",
6530
+ "subdocument",
6531
+ "ping",
6532
+ "xmlhttpreqeust",
6533
+ "websocket",
6534
+ "other",
6535
+ "webrtc" ],
6536
+ inverse_conflicts:true,
6537
+ assignable:true,
6538
+ negatable:false,
6539
+ value_format:"(?xi)\n ^\n # Value may start with \"request:\"\n (request:)?\n\n # Forbidden header names\n (?!\n (\n access-control-allow-origin|\n access-control-allow-credentials|\n access-control-allow-headers|\n access-control-allow-methods|\n access-control-expose-headers|\n access-control-max-age|\n access-control-request-headers|\n access-control-request-method|\n origin|\n timing-allow-origin|\n allow|\n cross-origin-embedder-policy|\n cross-origin-opener-policy|\n cross-origin-resource-policy|\n content-security-policy|\n content-security-policy-report-only|\n expect-ct|\n feature-policy|\n origin-isolation|\n strict-transport-security|\n upgrade-insecure-requests|\n x-content-type-options|\n x-download-options|\n x-frame-options|\n x-permitted-cross-domain-policies|\n x-powered-by|\n x-xss-protection|\n public-key-pins|\n public-key-pins-report-only|\n sec-websocket-key|\n sec-websocket-extensions|\n sec-websocket-accept|\n sec-websocket-protocol|\n sec-websocket-version|\n p3p|\n sec-fetch-mode|\n sec-fetch-dest|\n sec-fetch-site|\n sec-fetch-user|\n referrer-policy|\n content-type|\n content-length|\n accept|\n accept-encoding|\n host|\n connection|\n transfer-encoding|\n upgrade\n )\n $)\n\n # Any other header name is allowed, if it matches the following regex\n [A-z0-9-]+\n $" },
6540
+ adg_ext_any:{ name:"removeheader",
6541
+ description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
6542
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
6543
+ conflicts:[ "domain",
6544
+ "third-party",
6545
+ "first-party",
6546
+ "app",
6547
+ "important",
6548
+ "match-case",
6549
+ "document",
6550
+ "image",
6551
+ "stylesheet",
6552
+ "script",
6553
+ "object",
6554
+ "font",
6555
+ "media",
6556
+ "subdocument",
6557
+ "ping",
6558
+ "xmlhttpreqeust",
6559
+ "websocket",
6560
+ "other",
6561
+ "webrtc" ],
6562
+ inverse_conflicts:true,
6563
+ assignable:true,
6564
+ negatable:false,
6565
+ value_format:"(?xi)\n ^\n # Value may start with \"request:\"\n (request:)?\n\n # Forbidden header names\n (?!\n (\n access-control-allow-origin|\n access-control-allow-credentials|\n access-control-allow-headers|\n access-control-allow-methods|\n access-control-expose-headers|\n access-control-max-age|\n access-control-request-headers|\n access-control-request-method|\n origin|\n timing-allow-origin|\n allow|\n cross-origin-embedder-policy|\n cross-origin-opener-policy|\n cross-origin-resource-policy|\n content-security-policy|\n content-security-policy-report-only|\n expect-ct|\n feature-policy|\n origin-isolation|\n strict-transport-security|\n upgrade-insecure-requests|\n x-content-type-options|\n x-download-options|\n x-frame-options|\n x-permitted-cross-domain-policies|\n x-powered-by|\n x-xss-protection|\n public-key-pins|\n public-key-pins-report-only|\n sec-websocket-key|\n sec-websocket-extensions|\n sec-websocket-accept|\n sec-websocket-protocol|\n sec-websocket-version|\n p3p|\n sec-fetch-mode|\n sec-fetch-dest|\n sec-fetch-site|\n sec-fetch-user|\n referrer-policy|\n content-type|\n content-length|\n accept|\n accept-encoding|\n host|\n connection|\n transfer-encoding|\n upgrade\n )\n $)\n\n # Any other header name is allowed, if it matches the following regex\n [A-z0-9-]+\n $" } };
6566
+
6567
+ var data$e = { adg_os_any:{ name:"removeparam",
6568
+ description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6569
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
6570
+ assignable:true,
6571
+ negatable:false,
6572
+ value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6573
+ adg_ext_any:{ name:"removeparam",
6574
+ description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6575
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
6576
+ assignable:true,
6577
+ negatable:false,
6578
+ value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
6579
+ ubo_ext_any:{ name:"removeparam",
6580
+ description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
6581
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#removeparam",
6582
+ assignable:true,
6583
+ negatable:false,
6584
+ value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
6585
+
6586
+ var data$d = { adg_os_any:{ name:"replace",
6587
+ 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.",
6588
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
6589
+ conflicts:[ "app",
6590
+ "domain",
6591
+ "document",
6592
+ "subdocument",
6593
+ "script",
6594
+ "stylesheet",
6595
+ "other",
6596
+ "xmlhttprequest",
6597
+ "first-party",
6598
+ "third-party",
6599
+ "important",
6600
+ "badfilter" ],
6601
+ inverse_conflicts:true,
6602
+ assignable:true,
6603
+ negatable:false,
6604
+ value_format:"/^\\/.+\\/.*\\/$/" },
6605
+ adg_ext_firefox:{ name:"replace",
6606
+ 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.",
6607
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
6608
+ conflicts:[ "domain",
6609
+ "document",
6610
+ "subdocument",
6611
+ "script",
6612
+ "stylesheet",
6613
+ "other",
6614
+ "xmlhttprequest",
6615
+ "first-party",
6616
+ "third-party",
6617
+ "important",
6618
+ "badfilter" ],
6619
+ inverse_conflicts:true,
6620
+ assignable:true,
6621
+ negatable:false,
6622
+ value_format:"/^\\/.+\\/.*\\/$/" } };
6623
+
6624
+ var data$c = { adg_any:{ name:"script",
6625
+ description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
6626
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#script-modifier" },
6627
+ abp_any:{ name:"script",
6628
+ description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
6629
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6630
+ ubo_any:{ name:"script",
6631
+ description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
6632
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6633
+
6634
+ var data$b = { adg_any:{ name:"specifichide",
6635
+ aliases:[ "shide" ],
6636
+ description:"Disables all specific element hiding and CSS rules, but not general ones.\nHas an opposite effect to `$generichide`.",
6637
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#specifichide-modifier",
6638
+ conflicts:[ "domain",
6639
+ "genericblock",
6640
+ "urlblock",
6641
+ "extension",
6642
+ "jsinject",
6643
+ "content",
6644
+ "xmlhttprequest",
6645
+ "badfilter" ],
6646
+ inverse_conflicts:true,
6647
+ negatable:false,
6648
+ exception_only:true },
6649
+ ubo_any:{ name:"specifichide",
6650
+ aliases:[ "shide" ],
6651
+ description:"Disables all specific element hiding and CSS rules, but not general ones.\nHas an opposite effect to `$generichide`.",
6652
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#specifichide",
6653
+ conflicts:[ "domain",
6654
+ "badfilter" ],
6655
+ inverse_conflicts:true,
6656
+ negatable:false,
6657
+ exception_only:true } };
6658
+
6659
+ var data$a = { adg_os_any:{ name:"stealth",
6660
+ description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6661
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
6662
+ assignable:true,
6663
+ negatable:false,
6664
+ exception_only:true,
6665
+ 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)+(?<!\\|)$" },
6666
+ adg_ext_chrome:{ name:"stealth",
6667
+ description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6668
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
6669
+ assignable:true,
6670
+ negatable:false,
6671
+ exception_only:true,
6672
+ value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n xclientdata\n |\\|?\n )\\b)+(?<!\\|)$" },
6673
+ adg_ext_firefox:{ name:"stealth",
6674
+ description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6675
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
6676
+ assignable:true,
6677
+ negatable:false,
6678
+ exception_only:true,
6679
+ value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
6680
+ adg_ext_opera:{ name:"stealth",
6681
+ description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6682
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
6683
+ assignable:true,
6684
+ negatable:false,
6685
+ exception_only:true,
6686
+ value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
6687
+ adg_ext_edge:{ name:"stealth",
6688
+ description:"Disables the Stealth Mode module for all corresponding pages and requests.",
6689
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
6690
+ assignable:true,
6691
+ negatable:false,
6692
+ exception_only:true,
6693
+ value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" } };
6694
+
6695
+ var data$9 = { ubo_any:{ name:"strict1p",
6696
+ 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.",
6697
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#strict1p" } };
6698
+
6699
+ var data$8 = { ubo_any:{ name:"strict3p",
6700
+ description:"This new `strict3p` option can check for strict partyness.\nFor example, a network request qualifies as 3rd-party if the context and the request hostnames are different.",
6701
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#strict3p" } };
6702
+
6703
+ var data$7 = { adg_any:{ name:"stylesheet",
6704
+ description:"The rule corresponds to CSS files requests.",
6705
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stylesheet-modifier" },
6706
+ abp_any:{ name:"stylesheet",
6707
+ description:"The rule corresponds to CSS files requests.",
6708
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6709
+ ubo_any:{ name:"stylesheet",
6710
+ aliases:[ "css" ],
6711
+ description:"The rule corresponds to CSS files requests.",
6712
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#css" } };
6713
+
6714
+ var data$6 = { adg_any:{ name:"subdocument",
6715
+ description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
6716
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#subdocument-modifier" },
6717
+ abp_any:{ name:"subdocument",
6718
+ description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
6719
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6720
+ ubo_any:{ name:"subdocument",
6721
+ aliases:[ "frame" ],
6722
+ description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
6723
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#frame" } };
6724
+
6725
+ var data$5 = { adg_any:{ name:"third-party",
6726
+ aliases:[ "3p" ],
6727
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6728
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#third-party-modifier" },
6729
+ ubo_any:{ name:"3p",
6730
+ aliases:[ "third-party" ],
6731
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6732
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#3p" },
6733
+ abp_any:{ name:"third-party",
6734
+ description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6735
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#party-requests" } };
6736
+
6737
+ var data$4 = { ubo_ext_any:{ name:"to",
6738
+ description:"The main motivation of this option is\nto give static network filtering engine an equivalent of DNR's `requestDomains` and `excludedRequestDomains`.",
6739
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#to",
6740
+ assignable:true,
6741
+ negatable:false,
6742
+ value_format:"pipe_separated_domains" } };
6743
+
6744
+ var data$3 = { adg_any:{ name:"urlblock",
6745
+ description:"Disables blocking of all requests sent from the pages matching the rule.",
6746
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#urlblock-modifier",
6747
+ conflicts:[ "domain",
6748
+ "specifichide",
6749
+ "generichide",
6750
+ "elemhide",
6751
+ "extension",
6752
+ "jsinject",
6753
+ "content",
6754
+ "badfilter" ],
6755
+ inverse_conflicts:true,
6756
+ negatable:false,
6757
+ exception_only:true } };
6758
+
6759
+ var data$2 = { adg_any:{ name:"webrtc",
6760
+ description:"The rule applies only to WebRTC connections.",
6761
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#webrtc-modifier",
6762
+ removed:true,
6763
+ removal_message:"This modifier is removed and is no longer supported.\nRules with it are considered as invalid. If you need to suppress WebRTC, consider using\nthe [nowebrtc scriptlet](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#nowebrtc)." },
6764
+ ubo_any:{ name:"webrtc",
6765
+ description:"The rule applies only to WebRTC connections.",
6766
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax",
6767
+ removed:true,
6768
+ removal_message:"This modifier is removed and is no longer supported.\nIf you need to suppress WebRTC, consider using\nthe [nowebrtc scriptlet](https://github.com/gorhill/uBlock/wiki/Resources-Library#nowebrtcjs-)." },
6769
+ abp_any:{ name:"webrtc",
6770
+ description:"The rule applies only to WebRTC connections.",
6771
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6772
+ version_added:"1.13.3" } };
6773
+
6774
+ var data$1 = { adg_os_any:{ name:"websocket",
6775
+ description:"The rule applies only to WebSocket connections.",
6776
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
6777
+ adg_ext_any:{ name:"websocket",
6778
+ description:"The rule applies only to WebSocket connections.",
6779
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
6780
+ adg_cb_ios:{ name:"websocket",
6781
+ description:"The rule applies only to WebSocket connections.",
6782
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
6783
+ adg_cb_safari:{ name:"websocket",
6784
+ description:"The rule applies only to WebSocket connections.",
6785
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
6786
+ abp_ext_any:{ name:"websocket",
6787
+ description:"The rule applies only to WebSocket connections.",
6788
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6789
+ ubo_ext_any:{ name:"websocket",
6790
+ description:"The rule applies only to WebSocket connections.",
6791
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6792
+
6793
+ var data = { adg_any:{ name:"xmlhttprequest",
6794
+ aliases:[ "xhr" ],
6795
+ description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
6796
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#xmlhttprequest-modifier" },
6797
+ abp_any:{ name:"xmlhttprequest",
6798
+ description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
6799
+ docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
6800
+ ubo_any:{ name:"xhr",
6801
+ aliases:[ "xmlhttprequest" ],
6802
+ description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
6803
+ docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#xhr" } };
6804
+
6805
+ /**
6806
+ * @file Raw compatibility tables data reexport from yaml files.
6807
+ *
6808
+ * '@ts-nocheck' is used here once instead of adding @ts-ignore for each import.
6809
+ */
6810
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6811
+ // @ts-nocheck
6812
+ // Please keep imports and exports in alphabetical order
6813
+ const rawModifiersData = {
6814
+ all: data$T,
6815
+ app: data$S,
6816
+ badfilter: data$R,
6817
+ cname: data$Q,
6818
+ content: data$P,
6819
+ cookie: data$O,
6820
+ csp: data$N,
6821
+ denyallow: data$M,
6822
+ document: data$L,
6823
+ domain: data$K,
6824
+ elemhide: data$J,
6825
+ empty: data$I,
6826
+ firstParty: data$H,
6827
+ extension: data$G,
6828
+ font: data$F,
6829
+ genericblock: data$E,
6830
+ generichide: data$D,
6831
+ header: data$C,
6832
+ hls: data$B,
6833
+ image: data$A,
6834
+ important: data$z,
6835
+ inlineFont: data$y,
6836
+ inlineScript: data$x,
6837
+ jsinject: data$w,
6838
+ jsonprune: data$v,
6839
+ matchCase: data$u,
6840
+ media: data$t,
6841
+ method: data$s,
6842
+ mp4: data$r,
6843
+ network: data$q,
6844
+ noop: data$p,
6845
+ objectSubrequest: data$o,
6846
+ object: data$n,
6847
+ other: data$m,
6848
+ permissions: data$l,
6849
+ ping: data$k,
6850
+ popunder: data$j,
6851
+ popup: data$i,
6852
+ redirectRule: data$h,
6853
+ redirect: data$g,
6854
+ removeheader: data$f,
6855
+ removeparam: data$e,
6856
+ replace: data$d,
6857
+ script: data$c,
6858
+ specifichide: data$b,
6859
+ stealth: data$a,
6860
+ strict1p: data$9,
6861
+ strict3p: data$8,
6862
+ stylesheet: data$7,
6863
+ subdocument: data$6,
6864
+ thirdParty: data$5,
6865
+ to: data$4,
6866
+ urlblock: data$3,
6867
+ webrtc: data$2,
6868
+ websocket: data$1,
6869
+ xmlhttprequest: data,
6870
+ };
6871
+
6872
+ /**
6873
+ * @file Compatibility tables types.
6874
+ */
6875
+ /**
6876
+ * List of properties names for modifier data.
6877
+ *
6878
+ * @see {@link https://github.com/AdguardTeam/tsurlfilter/blob/master/packages/agtree/src/compatibility-tables/modifiers/README.md#file-structure}
6879
+ */
6880
+ var SpecificKey;
6881
+ (function (SpecificKey) {
6882
+ SpecificKey["Name"] = "name";
6883
+ SpecificKey["Aliases"] = "aliases";
6884
+ SpecificKey["Description"] = "description";
6885
+ SpecificKey["Docs"] = "docs";
6886
+ SpecificKey["Deprecated"] = "deprecated";
6887
+ SpecificKey["DeprecationMessage"] = "deprecation_message";
6888
+ SpecificKey["Removed"] = "removed";
6889
+ SpecificKey["RemovalMessage"] = "removal_message";
6890
+ SpecificKey["Conflicts"] = "conflicts";
6891
+ SpecificKey["InverseConflicts"] = "inverse_conflicts";
6892
+ SpecificKey["Assignable"] = "assignable";
6893
+ SpecificKey["Negatable"] = "negatable";
6894
+ SpecificKey["BlockOnly"] = "block_only";
6895
+ SpecificKey["ExceptionOnly"] = "exception_only";
6896
+ SpecificKey["ValueFormat"] = "value_format";
6897
+ // TODO: following fields should be handled later
6898
+ // VersionAdded = 'version_added',
6899
+ // VersionRemoved = 'version_removed',
6900
+ })(SpecificKey || (SpecificKey = {}));
6901
+
6902
+ /**
6903
+ * Prepares specific platform modifier data from raw modifiers data —
6904
+ * sets [default values] to properties that are not defined in raw data.
6905
+ *
6906
+ * [default values]: ./modifiers/README.md "Check File structure table for default values."
6907
+ *
6908
+ * @param blockerId Key in ModifierData, i.e. 'adg_os_any', 'ubo_ext_any', etc.
6909
+ * @param rawModifierData Specific platform modifier data from raw modifiers data.
6910
+ *
6911
+ * @returns Prepared specific platform modifier data where properties cannot be undefined.
6912
+ */
6913
+ const prepareBlockerData = (blockerId, rawModifierData) => {
6914
+ const rawData = rawModifierData[blockerId];
6915
+ const blockerData = {
6916
+ [SpecificKey.Name]: rawData[SpecificKey.Name],
6917
+ [SpecificKey.Aliases]: rawData[SpecificKey.Aliases] || null,
6918
+ [SpecificKey.Description]: rawData[SpecificKey.Description] || null,
6919
+ [SpecificKey.Docs]: rawData[SpecificKey.Docs] || null,
6920
+ [SpecificKey.Deprecated]: rawData[SpecificKey.Deprecated] || false,
6921
+ [SpecificKey.DeprecationMessage]: rawData[SpecificKey.DeprecationMessage] || null,
6922
+ [SpecificKey.Removed]: rawData[SpecificKey.Removed] || false,
6923
+ [SpecificKey.RemovalMessage]: rawData[SpecificKey.RemovalMessage] || null,
6924
+ [SpecificKey.Conflicts]: rawData[SpecificKey.Conflicts] || null,
6925
+ [SpecificKey.InverseConflicts]: rawData[SpecificKey.InverseConflicts] || false,
6926
+ [SpecificKey.Assignable]: rawData[SpecificKey.Assignable] || false,
6927
+ // 'negatable' should be checked whether it is undefined or not
6928
+ // because if it is 'false', default value 'true' will override it
6929
+ [SpecificKey.Negatable]: isUndefined(rawData[SpecificKey.Negatable])
6930
+ ? true
6931
+ : rawData[SpecificKey.Negatable],
6932
+ [SpecificKey.BlockOnly]: rawData[SpecificKey.BlockOnly] || false,
6933
+ [SpecificKey.ExceptionOnly]: rawData[SpecificKey.ExceptionOnly] || false,
6934
+ [SpecificKey.ValueFormat]: rawData[SpecificKey.ValueFormat] || null,
6935
+ };
6936
+ return blockerData;
6937
+ };
6938
+ /**
6939
+ * Prepares raw modifiers data into a data map with default values for properties
6940
+ * that are not defined in raw data.
6941
+ *
6942
+ * @returns Map of parsed and prepared modifiers data.
6943
+ */
6944
+ const getModifiersData = () => {
6945
+ const dataMap = new Map();
6946
+ Object.keys(rawModifiersData).forEach((modifierId) => {
6947
+ const rawModifierData = rawModifiersData[modifierId];
6948
+ const modifierData = {};
6949
+ Object.keys(rawModifierData).forEach((blockerId) => {
6950
+ modifierData[blockerId] = prepareBlockerData(blockerId, rawModifierData);
6951
+ });
6952
+ dataMap.set(modifierId, modifierData);
6953
+ });
6954
+ return dataMap;
6955
+ };
6956
+
6957
+ const INVALID_ERROR_PREFIX = {
6958
+ NOT_EXISTENT: 'Not existent modifier',
6959
+ NOT_SUPPORTED: 'The adblocker does not support the modifier',
6960
+ REMOVED: 'Removed and no longer supported modifier',
6961
+ BLOCK_ONLY: 'Only blocking rules may contain the modifier',
6962
+ EXCEPTION_ONLY: 'Only exception rules may contain the modifier',
6963
+ NOT_NEGATABLE: 'Not negatable modifier',
6964
+ VALUE_REQUIRED: 'Value is required for the modifier',
6965
+ VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
6966
+ INVALID_NOOP: 'Invalid noop modifier',
6967
+ };
6968
+
6969
+ /**
6970
+ * @file Validator for modifiers.
6971
+ */
6972
+ const BLOCKER_PREFIX = {
6973
+ [AdblockSyntax.Adg]: 'adg_',
6974
+ [AdblockSyntax.Ubo]: 'ubo_',
6975
+ [AdblockSyntax.Abp]: 'abp_',
6976
+ };
6977
+ /**
6978
+ * Collects names and aliases for all supported modifiers.
6979
+ * Deprecated and removed modifiers are included because they are known and existent
6980
+ * and they should be validated properly.
6981
+ *
6982
+ * @param dataMap Parsed all modifiers data.
6983
+ *
6984
+ * @returns Set of all modifier names (and their aliases).
6985
+ */
6986
+ const getAllModifierNames = (dataMap) => {
6987
+ const names = new Set();
6988
+ dataMap.forEach((modifierData) => {
6989
+ Object.keys(modifierData).forEach((blockerId) => {
6990
+ const blockerData = modifierData[blockerId];
6991
+ names.add(blockerData.name);
6992
+ if (!blockerData.aliases) {
6993
+ return;
6994
+ }
6995
+ blockerData.aliases.forEach((alias) => names.add(alias));
6996
+ });
6997
+ });
6998
+ return names;
6999
+ };
7000
+ /**
7001
+ * Returns modifier data for given modifier name and adblocker.
7002
+ *
7003
+ * @param modifiersData Parsed all modifiers data map.
7004
+ * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
7005
+ * @param modifierName Modifier name.
7006
+ *
7007
+ * @returns Modifier data or `null` if not found.
7008
+ */
7009
+ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
7010
+ let specificBlockerData = null;
7011
+ modifiersData.forEach((modifierData) => {
7012
+ Object.keys(modifierData).forEach((blockerId) => {
7013
+ const blockerData = modifierData[blockerId];
7014
+ if (blockerData.name === modifierName
7015
+ || (blockerData.aliases && blockerData.aliases.includes(modifierName))) {
7016
+ // modifier is found by name or alias
7017
+ // so its support by specific adblocker should be checked
7018
+ if (blockerId.startsWith(blockerPrefix)) {
7019
+ // so maybe other data objects should be checked as well (not sure)
7020
+ specificBlockerData = blockerData;
7021
+ }
7022
+ }
7023
+ });
7024
+ });
7025
+ return specificBlockerData;
7026
+ };
7027
+ /**
7028
+ * Returns invalid validation result with given error message.
7029
+ *
7030
+ * @param error Error message.
7031
+ *
7032
+ * @returns Validation result `{ ok: false, error }`.
7033
+ */
7034
+ const getInvalidValidationResult = (error) => {
7035
+ return {
7036
+ ok: false,
7037
+ error,
7038
+ };
7039
+ };
7040
+ /**
7041
+ * Fully checks whether the given `modifier` valid for given blocker `syntax`:
7042
+ * is it supported by the blocker, deprecated, assignable, negatable, etc.
7043
+ *
7044
+ * @param modifiersData Parsed all modifiers data map.
7045
+ * @param syntax Adblock syntax to check the modifier for.
7046
+ * 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
7047
+ * @param modifier Parsed modifier AST node.
7048
+ * @param isException Whether the modifier is used in exception rule.
7049
+ * Needed to check whether the modifier is allowed only in blocking or exception rules.
7050
+ *
7051
+ * @returns Result of modifier validation.
7052
+ */
7053
+ const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
7054
+ if (syntax === AdblockSyntax.Common) {
7055
+ throw new Error(`Syntax should be specific, '${AdblockSyntax.Common}' is not supported`);
7056
+ }
7057
+ const modifierName = modifier.modifier.value;
7058
+ const blockerPrefix = BLOCKER_PREFIX[syntax];
7059
+ if (!blockerPrefix) {
7060
+ throw new Error(`Unknown syntax: ${syntax}`);
7061
+ }
7062
+ // needed for validation of negation, assignment, etc.
7063
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
7064
+ // if no specific blocker data is found
7065
+ if (!specificBlockerData) {
7066
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
7067
+ }
7068
+ // e.g. 'object-subrequest'
7069
+ if (specificBlockerData.removed) {
7070
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
7071
+ }
7072
+ if (specificBlockerData.deprecated) {
7073
+ if (!specificBlockerData.deprecation_message) {
7074
+ throw new Error('Deprecation notice is required for deprecated modifier');
7075
+ }
7076
+ return {
7077
+ ok: true,
7078
+ warn: specificBlockerData.deprecation_message,
7079
+ };
7080
+ }
7081
+ if (specificBlockerData.block_only && isException) {
7082
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
7083
+ }
7084
+ if (specificBlockerData.exception_only && !isException) {
7085
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
7086
+ }
7087
+ // e.g. '~domain=example.com'
7088
+ if (!specificBlockerData.negatable && modifier.exception) {
7089
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_NEGATABLE}: '${modifierName}'`);
7090
+ }
7091
+ // e.g. 'domain'
7092
+ if (specificBlockerData.assignable) {
7093
+ /**
7094
+ * exception_only modifier 'stealth' is assignable
7095
+ * but it also may be used without value as well -- `$stealth` or `$stealth=dpi`
7096
+ */
7097
+ if (!modifier.value
7098
+ /**
7099
+ * TODO: consider to return `{ ok: true, warn: 'Modifier value may be specified' }` (???)
7100
+ * after the extension will support stealth mode with value
7101
+ * https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
7102
+ */
7103
+ && !specificBlockerData.exception_only) {
7104
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
7105
+ }
7106
+ }
7107
+ else if (modifier?.value) {
7108
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
7109
+ }
7110
+ return { ok: true };
7111
+ };
7112
+ /**
7113
+ * Returns documentation URL for given modifier and adblocker.
7114
+ *
7115
+ * @param modifiersData Parsed all modifiers data map.
7116
+ * @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
7117
+ * @param modifier Parsed modifier AST node.
7118
+ *
7119
+ * @returns Documentation URL or `null` if not found.
7120
+ */
7121
+ const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
7122
+ const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
7123
+ return specificBlockerData?.docs || null;
7124
+ };
7125
+ /**
7126
+ * Validates the noop modifier (i.e. only underscores).
7127
+ *
7128
+ * @param value Value of the modifier.
7129
+ *
7130
+ * @returns True if the modifier is valid, false otherwise.
7131
+ */
7132
+ const isValidNoopModifier = (value) => {
7133
+ return value.split('').every((char) => char === UNDERSCORE);
7134
+ };
7135
+ /**
7136
+ * Modifier validator class.
7137
+ */
7138
+ class ModifierValidator {
7139
+ /**
7140
+ * Map of all modifiers data parsed from yaml files.
7141
+ */
7142
+ modifiersData;
7143
+ /**
7144
+ * List of all modifier names for any adblocker.
7145
+ *
7146
+ * Please note that **deprecated** modifiers are **included** as well.
7147
+ */
7148
+ allModifierNames;
7149
+ constructor() {
7150
+ // data map based on yaml files
7151
+ this.modifiersData = getModifiersData();
7152
+ this.allModifierNames = getAllModifierNames(this.modifiersData);
7153
+ }
7154
+ /**
7155
+ * Simply checks whether the modifier exists in any adblocker.
7156
+ *
7157
+ * **Deprecated** and **removed** modifiers are considered as **existent**.
7158
+ *
7159
+ * @param modifier Already parsed modifier AST node.
7160
+ *
7161
+ * @returns True if modifier exists, false otherwise.
7162
+ */
7163
+ exists = (modifier) => {
7164
+ return this.allModifierNames.has(modifier.modifier.value);
7165
+ };
7166
+ /**
7167
+ * Checks whether the given `modifier` is valid for specified `syntax`.
7168
+ *
7169
+ * For `Common` syntax it simply checks whether the modifier exists.
7170
+ * For specific syntax the validation is more complex —
7171
+ * deprecated, assignable, negatable and other requirements are checked.
7172
+ *
7173
+ * @param syntax Adblock syntax to check the modifier for.
7174
+ * @param rawModifier Modifier AST node.
7175
+ * @param isException Whether the modifier is used in exception rule, default to false.
7176
+ * Needed to check whether the modifier is allowed only in blocking or exception rules.
7177
+ *
7178
+ * @returns Result of modifier validation.
7179
+ */
7180
+ validate = (syntax, rawModifier, isException = false) => {
7181
+ const modifier = cloneDeep(rawModifier);
7182
+ // special case: handle noop modifier which may be used as multiple underscores (not just one)
7183
+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
7184
+ if (modifier.modifier.value.startsWith(UNDERSCORE)) {
7185
+ // check whether the modifier value contains something else besides underscores
7186
+ if (!isValidNoopModifier(modifier.modifier.value)) {
7187
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
7188
+ }
7189
+ // otherwise, replace the modifier value with single underscore.
7190
+ // it is needed to check whether the modifier is supported by specific adblocker due to the syntax
7191
+ modifier.modifier.value = UNDERSCORE;
7192
+ }
7193
+ if (!this.exists(modifier)) {
7194
+ return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
7195
+ }
7196
+ // for 'Common' syntax we cannot check something more
7197
+ if (syntax === AdblockSyntax.Common) {
7198
+ return { ok: true };
7199
+ }
7200
+ return validateForSpecificSyntax(this.modifiersData, syntax, modifier, isException);
7201
+ };
7202
+ /**
7203
+ * Returns AdGuard documentation URL for given modifier.
7204
+ *
7205
+ * @param modifier Parsed modifier AST node.
7206
+ *
7207
+ * @returns AdGuard documentation URL or `null` if not found.
7208
+ */
7209
+ getAdgDocumentationLink = (modifier) => {
7210
+ if (!this.exists(modifier)) {
7211
+ return null;
7212
+ }
7213
+ return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Adg], modifier);
7214
+ };
7215
+ /**
7216
+ * Returns Ublock Origin documentation URL for given modifier.
7217
+ *
7218
+ * @param modifier Parsed modifier AST node.
7219
+ *
7220
+ * @returns Ublock Origin documentation URL or `null` if not found.
7221
+ */
7222
+ getUboDocumentationLink = (modifier) => {
7223
+ if (!this.exists(modifier)) {
7224
+ return null;
7225
+ }
7226
+ return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Ubo], modifier);
7227
+ };
7228
+ /**
7229
+ * Returns AdBlock Plus documentation URL for given modifier.
7230
+ *
7231
+ * @param modifier Parsed modifier AST node.
7232
+ *
7233
+ * @returns AdBlock Plus documentation URL or `null` if not found.
7234
+ */
7235
+ getAbpDocumentationLink = (modifier) => {
7236
+ if (!this.exists(modifier)) {
7237
+ return null;
7238
+ }
7239
+ return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Abp], modifier);
7240
+ };
7241
+ }
7242
+ const modifierValidator = new ModifierValidator();
7243
+
7244
+ /**
7245
+ * @file Base class for converters
7246
+ *
7247
+ * TS doesn't support abstract static methods, so we should use
7248
+ * a workaround and extend this class instead of implementing it
7249
+ */
7250
+ /* eslint-disable jsdoc/require-returns-check */
7251
+ /* eslint-disable @typescript-eslint/no-unused-vars */
7252
+ /**
7253
+ * Basic class for rule converters
7254
+ */
7255
+ class ConverterBase {
7256
+ /**
7257
+ * Converts some data to AdGuard format
7258
+ *
7259
+ * @param data Data to convert
7260
+ * @returns Converted data
7261
+ * @throws If the data is invalid or incompatible
7262
+ */
7263
+ static convertToAdg(data) {
7264
+ throw new NotImplementedError();
7265
+ }
7266
+ /**
7267
+ * Converts some data to Adblock Plus format
7268
+ *
7269
+ * @param data Data to convert
7270
+ * @returns Converted data
7271
+ * @throws If the data is invalid or incompatible
7272
+ */
7273
+ static convertToAbp(data) {
7274
+ throw new NotImplementedError();
7275
+ }
7276
+ /**
7277
+ * Converts some data to uBlock Origin format
7278
+ *
7279
+ * @param data Data to convert
7280
+ * @returns Converted data
7281
+ * @throws If the data is invalid or incompatible
7282
+ */
7283
+ static convertToUbo(data) {
7284
+ throw new NotImplementedError();
7285
+ }
7286
+ }
7287
+
7288
+ /**
7289
+ * @file Base class for rule converters
7290
+ *
7291
+ * TS doesn't support abstract static methods, so we should use
7292
+ * a workaround and extend this class instead of implementing it
7293
+ */
7294
+ /* eslint-disable jsdoc/require-returns-check */
7295
+ /* eslint-disable @typescript-eslint/no-unused-vars */
7296
+ /**
7297
+ * Basic class for rule converters
7298
+ */
7299
+ class RuleConverterBase extends ConverterBase {
7300
+ /**
7301
+ * Converts an adblock filtering rule to AdGuard format, if possible.
7302
+ *
7303
+ * @param rule Rule node to convert
7304
+ * @returns Array of converted rule nodes
7305
+ * @throws If the rule is invalid or cannot be converted
7306
+ */
7307
+ static convertToAdg(rule) {
7308
+ throw new NotImplementedError();
7309
+ }
7310
+ /**
7311
+ * Converts an adblock filtering rule to Adblock Plus format, if possible.
7312
+ *
7313
+ * @param rule Rule node to convert
7314
+ * @returns Array of converted rule nodes
7315
+ * @throws If the rule is invalid or cannot be converted
7316
+ */
7317
+ static convertToAbp(rule) {
7318
+ throw new NotImplementedError();
7319
+ }
7320
+ /**
7321
+ * Converts an adblock filtering rule to uBlock Origin format, if possible.
7322
+ *
7323
+ * @param rule Rule node to convert
7324
+ * @returns Array of converted rule nodes
7325
+ * @throws If the rule is invalid or cannot be converted
7326
+ */
7327
+ static convertToUbo(rule) {
7328
+ throw new NotImplementedError();
7329
+ }
7330
+ }
7331
+
7332
+ /**
7333
+ * @file Comment rule converter
7334
+ */
7335
+ /**
7336
+ * Comment rule converter class
7337
+ *
7338
+ * @todo Implement `convertToUbo` and `convertToAbp`
7339
+ */
7340
+ class CommentRuleConverter extends RuleConverterBase {
7341
+ /**
7342
+ * Converts a comment rule to AdGuard format, if possible.
7343
+ *
7344
+ * @param rule Rule node to convert
7345
+ * @returns Array of converted rule nodes
7346
+ * @throws If the rule is invalid or cannot be converted
7347
+ */
7348
+ static convertToAdg(rule) {
7349
+ // Clone the provided AST node to avoid side effects
7350
+ const ruleNode = cloneDeep(rule);
7351
+ // TODO: Add support for other comment types, if needed
7352
+ // Main task is # -> ! conversion
7353
+ switch (ruleNode.type) {
7354
+ case CommentRuleType.CommentRule:
7355
+ // 'Comment' uBO style comments
7356
+ if (ruleNode.type === CommentRuleType.CommentRule
7357
+ && ruleNode.marker.value === CommentMarker.Hashmark) {
7358
+ ruleNode.marker.value = CommentMarker.Regular;
7359
+ // Add the hashmark to the beginning of the comment
7360
+ ruleNode.text.value = `${SPACE}${CommentMarker.Hashmark}${ruleNode.text.value}`;
7361
+ }
7362
+ return [ruleNode];
7363
+ // Leave any other comment rule as is
7364
+ default:
7365
+ return [ruleNode];
7366
+ }
7367
+ }
7368
+ }
7369
+
7370
+ /**
7371
+ * @file Regular expression utilities
7372
+ */
7373
+ // Special RegExp constants
7374
+ const REGEX_START = CARET; // '^'
7375
+ const REGEX_END = DOLLAR_SIGN; // '$'
7376
+ const REGEX_ANY_CHARACTERS = DOT + ASTERISK; // '.*'
7377
+ // Special adblock pattern symbols and their RegExp equivalents
7378
+ const ADBLOCK_URL_START = PIPE + PIPE; // '||'
7379
+ const ADBLOCK_URL_START_REGEX = '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?';
7380
+ const ADBLOCK_URL_SEPARATOR = CARET; // '^'
7381
+ const ADBLOCK_URL_SEPARATOR_REGEX = '([^ a-zA-Z0-9.%_-]|$)';
7382
+ const ADBLOCK_WILDCARD = ASTERISK; // '*'
7383
+ const ADBLOCK_WILDCARD_REGEX = REGEX_ANY_CHARACTERS;
7384
+ // Negation wrapper for RegExp patterns
7385
+ const REGEX_NEGATION_PREFIX = '^((?!';
7386
+ const REGEX_NEGATION_SUFFIX = ').)*$';
7387
+ /**
7388
+ * Special RegExp symbols
7389
+ *
7390
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-escape
7391
+ */
7392
+ const SPECIAL_REGEX_SYMBOLS = new Set([
7393
+ ASTERISK,
7394
+ CARET,
7395
+ CLOSE_CURLY_BRACKET,
7396
+ CLOSE_PARENTHESIS,
7397
+ CLOSE_SQUARE_BRACKET,
7398
+ DOLLAR_SIGN,
7399
+ DOT,
7400
+ ESCAPE_CHARACTER,
7401
+ OPEN_CURLY_BRACKET,
7402
+ OPEN_PARENTHESIS,
7403
+ OPEN_SQUARE_BRACKET,
7404
+ PIPE,
7405
+ PLUS,
7406
+ QUESTION_MARK,
7407
+ SLASH,
7408
+ ]);
7409
+ /**
7410
+ * Utility functions for working with RegExp patterns
7411
+ */
7412
+ class RegExpUtils {
7413
+ /**
7414
+ * Checks whether a string is a RegExp pattern.
7415
+ * Flags are not supported.
7416
+ *
7417
+ * @param pattern - Pattern to check
7418
+ * @returns `true` if the string is a RegExp pattern, `false` otherwise
7419
+ */
7420
+ static isRegexPattern(pattern) {
7421
+ const trimmedPattern = pattern.trim();
7422
+ // Avoid false positives
7423
+ if (trimmedPattern.length > REGEX_MARKER.length * 2 && trimmedPattern.startsWith(REGEX_MARKER)) {
7424
+ const last = StringUtils.findNextUnescapedCharacter(trimmedPattern, REGEX_MARKER, REGEX_MARKER.length);
7425
+ return last === trimmedPattern.length - 1;
7426
+ }
7427
+ return false;
7428
+ }
7429
+ /**
7430
+ * Negates a RegExp pattern. Technically, this method wraps the pattern in `^((?!` and `).)*$`.
7431
+ *
7432
+ * RegExp modifiers are not supported.
7433
+ *
7434
+ * @param pattern Pattern to negate (can be wrapped in slashes or not)
7435
+ * @returns Negated RegExp pattern
7436
+ */
7437
+ static negateRegexPattern(pattern) {
7438
+ let result = pattern.trim();
7439
+ let slashes = false;
7440
+ // Remove the leading and trailing slashes (/)
7441
+ if (RegExpUtils.isRegexPattern(result)) {
7442
+ result = result.substring(REGEX_MARKER.length, result.length - REGEX_MARKER.length);
7443
+ slashes = true;
7444
+ }
7445
+ // Only negate the pattern if it's not already negated
7446
+ if (!(result.startsWith(REGEX_NEGATION_PREFIX) && result.endsWith(REGEX_NEGATION_SUFFIX))) {
7447
+ // Remove leading caret (^)
7448
+ if (result.startsWith(REGEX_START)) {
7449
+ result = result.substring(REGEX_START.length);
7450
+ }
7451
+ // Remove trailing dollar sign ($)
7452
+ if (result.endsWith(REGEX_END)) {
7453
+ result = result.substring(0, result.length - REGEX_END.length);
7454
+ }
7455
+ // Wrap the pattern in the negation
7456
+ result = `${REGEX_NEGATION_PREFIX}${result}${REGEX_NEGATION_SUFFIX}`;
7457
+ }
7458
+ // Add the leading and trailing slashes back if they were there
7459
+ if (slashes) {
7460
+ result = `${REGEX_MARKER}${result}${REGEX_MARKER}`;
7461
+ }
7462
+ return result;
7463
+ }
7464
+ /**
7465
+ * Converts a basic adblock rule pattern to a RegExp pattern. Based on
7466
+ * https://github.com/AdguardTeam/tsurlfilter/blob/9b26e0b4a0e30b87690bc60f7cf377d112c3085c/packages/tsurlfilter/src/rules/simple-regex.ts#L219
7467
+ *
7468
+ * @param pattern Pattern to convert
7469
+ * @returns RegExp equivalent of the pattern
7470
+ * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#basic-rules}
7471
+ */
7472
+ static patternToRegexp(pattern) {
7473
+ const trimmed = pattern.trim();
7474
+ // Return regex for any character sequence if the pattern is just |, ||, * or empty
7475
+ if (trimmed === ADBLOCK_URL_START
7476
+ || trimmed === PIPE
7477
+ || trimmed === ADBLOCK_WILDCARD
7478
+ || trimmed === EMPTY) {
7479
+ return REGEX_ANY_CHARACTERS;
7480
+ }
7481
+ // If the pattern is already a RegExp, just return it, but remove the leading and trailing slashes
7482
+ if (RegExpUtils.isRegexPattern(pattern)) {
7483
+ return pattern.substring(REGEX_MARKER.length, pattern.length - REGEX_MARKER.length);
7484
+ }
7485
+ let result = EMPTY;
7486
+ let offset = 0;
7487
+ let len = trimmed.length;
7488
+ // Handle leading pipes
7489
+ if (trimmed[0] === PIPE) {
7490
+ if (trimmed[1] === PIPE) {
7491
+ // Replace adblock url start (||) with its RegExp equivalent
7492
+ result += ADBLOCK_URL_START_REGEX;
7493
+ offset = ADBLOCK_URL_START.length;
7494
+ }
7495
+ else {
7496
+ // Replace single pipe (|) with the RegExp start symbol (^)
7497
+ result += REGEX_START;
7498
+ offset = REGEX_START.length;
7499
+ }
7500
+ }
7501
+ // Handle trailing pipes
7502
+ let trailingPipe = false;
7503
+ if (trimmed.endsWith(PIPE)) {
7504
+ trailingPipe = true;
7505
+ len -= PIPE.length;
7506
+ }
7507
+ // Handle the rest of the pattern, if any
7508
+ for (; offset < len; offset += 1) {
7509
+ if (trimmed[offset] === ADBLOCK_WILDCARD) {
7510
+ // Replace adblock wildcard (*) with its RegExp equivalent
7511
+ result += ADBLOCK_WILDCARD_REGEX;
7512
+ }
7513
+ else if (trimmed[offset] === ADBLOCK_URL_SEPARATOR) {
7514
+ // Replace adblock url separator (^) with its RegExp equivalent
7515
+ result += ADBLOCK_URL_SEPARATOR_REGEX;
7516
+ }
7517
+ else if (SPECIAL_REGEX_SYMBOLS.has(trimmed[offset])) {
7518
+ // Escape special RegExp symbols (we handled pipe (|) and asterisk (*) already)
7519
+ result += ESCAPE_CHARACTER + trimmed[offset];
7520
+ }
7521
+ else {
7522
+ // Just add any other character
7523
+ result += trimmed[offset];
7524
+ }
7525
+ }
7526
+ // Handle trailing pipes
7527
+ if (trailingPipe) {
7528
+ // Replace trailing pipe (|) with the RegExp end symbol ($)
7529
+ result += REGEX_END;
7530
+ }
7531
+ return result;
7532
+ }
7533
+ }
7534
+
7535
+ /**
7536
+ * @file HTML filtering rule converter
7537
+ */
7538
+ /**
7539
+ * From the AdGuard docs:
7540
+ * Specifies the maximum length for content of HTML element. If this parameter is
7541
+ * set and the content length exceeds the value, a rule does not apply to the element.
7542
+ * If this parameter is not specified, the max-length is considered to be 8192 (8 KB).
7543
+ * When converting from other formats, we set the max-length to 262144 (256 KB).
7544
+ *
7545
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
7546
+ */
7547
+ const ADGUARD_HTML_DEFAULT_MAX_LENGTH = 8192;
7548
+ const ADGUARD_HTML_CONVERSION_MAX_LENGTH = ADGUARD_HTML_DEFAULT_MAX_LENGTH * 32;
7549
+ const NOT_SPECIFIED = -1;
7550
+ const CONTAINS$1 = 'contains';
7551
+ const HAS_TEXT$1 = 'has-text';
7552
+ const MAX_LENGTH = 'max-length';
7553
+ const MIN_LENGTH = 'min-length';
7554
+ const MIN_TEXT_LENGTH = 'min-text-length';
7555
+ const TAG_CONTENT = 'tag-content';
7556
+ const WILDCARD$1 = 'wildcard';
7557
+ /**
7558
+ * HTML filtering rule converter class
7559
+ *
7560
+ * @todo Implement `convertToUbo` (ABP currently doesn't support HTML filtering rules)
7561
+ */
7562
+ class HtmlRuleConverter extends RuleConverterBase {
7563
+ /**
7564
+ * Converts a HTML rule to AdGuard syntax, if possible. Also can be used to convert
7565
+ * AdGuard rules to AdGuard syntax to validate them.
7566
+ *
7567
+ * _Note:_ uBlock Origin supports multiple selectors within a single rule, but AdGuard doesn't,
7568
+ * so the following rule
7569
+ * ```
7570
+ * example.com##^div[attr1="value1"][attr2="value2"], script:has-text(value)
7571
+ * ```
7572
+ * will be converted to multiple AdGuard rules:
7573
+ * ```
7574
+ * example.com$$div[attr1="value1"][attr2="value2"][max-length="262144"]
7575
+ * example.com$$script[tag-content="value"][max-length="262144"]
7576
+ * ```
7577
+ *
7578
+ * @param rule Rule node to convert
7579
+ * @returns Array of converted rule nodes
7580
+ * @throws If the rule is invalid or cannot be converted
7581
+ */
7582
+ static convertToAdg(rule) {
7583
+ // Clone the provided AST node to avoid side effects
7584
+ const ruleNode = cloneDeep(rule);
7585
+ // Prepare the conversion result
7586
+ const conversionResult = [];
7587
+ // Iterate over selector list
7588
+ for (const selector of ruleNode.body.body.children) {
7589
+ // Check selector, just in case
7590
+ if (selector.type !== CssTreeNodeType.Selector) {
7591
+ throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
7592
+ }
7593
+ // At least one child is required, and first child may be a tag selector
7594
+ if (selector.children.length === 0) {
7595
+ throw new RuleConversionError('Invalid selector, no children are present');
7596
+ }
7597
+ // Prepare bounds
7598
+ let minLength = NOT_SPECIFIED;
7599
+ let maxLength = NOT_SPECIFIED;
7600
+ // Prepare the converted selector
7601
+ const convertedSelector = {
7602
+ type: CssTreeNodeType.Selector,
7603
+ children: [],
7604
+ };
7605
+ for (let i = 0; i < selector.children.length; i += 1) {
7606
+ // Current node within the current selector
7607
+ const node = selector.children[i];
7608
+ switch (node.type) {
7609
+ case CssTreeNodeType.TypeSelector:
7610
+ // First child in the selector may be a tag selector
7611
+ if (i !== 0) {
7612
+ throw new RuleConversionError('Tag selector should be the first child, if present');
7613
+ }
7614
+ // Simply store the tag selector
7615
+ convertedSelector.children.push(cloneDeep(node));
7616
+ break;
7617
+ case CssTreeNodeType.AttributeSelector:
7618
+ // Check if the attribute selector is a special AdGuard attribute
7619
+ switch (node.name.name) {
7620
+ case MIN_LENGTH:
7621
+ minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
7622
+ break;
7623
+ case MAX_LENGTH:
7624
+ maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
7625
+ break;
7626
+ case TAG_CONTENT:
7627
+ case WILDCARD$1:
7628
+ CssTree.assertAttributeSelectorHasStringValue(node);
7629
+ convertedSelector.children.push(cloneDeep(node));
7630
+ break;
7631
+ default:
7632
+ convertedSelector.children.push(cloneDeep(node));
7633
+ }
7634
+ break;
7635
+ case CssTreeNodeType.PseudoClassSelector:
7636
+ CssTree.assertPseudoClassHasAnyArgument(node);
7637
+ // eslint-disable-next-line no-case-declarations
7638
+ const arg = node.children[0];
7639
+ if (arg.type !== CssTreeNodeType.String
7640
+ && arg.type !== CssTreeNodeType.Raw
7641
+ && arg.type !== CssTreeNodeType.Number) {
7642
+ throw new RuleConversionError(`Unsupported argument type '${arg.type}' for pseudo class '${node.name}'`);
7643
+ }
7644
+ // Process the pseudo class based on its name
7645
+ switch (node.name) {
7646
+ case HAS_TEXT$1:
7647
+ case CONTAINS$1:
7648
+ // Check if the argument is a RegExp
7649
+ if (RegExpUtils.isRegexPattern(arg.value)) {
7650
+ // TODO: Add some support for RegExp patterns later
7651
+ // Need to find a way to convert some RegExp patterns to glob patterns
7652
+ throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
7653
+ }
7654
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(TAG_CONTENT, arg.value));
7655
+ break;
7656
+ // https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
7657
+ case MIN_TEXT_LENGTH:
7658
+ minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
7659
+ break;
7660
+ default:
7661
+ throw new RuleConversionError(`Unsupported pseudo class '${node.name}'`);
7662
+ }
7663
+ break;
7664
+ default:
7665
+ throw new RuleConversionError(`Unsupported node type '${node.type}'`);
7666
+ }
7667
+ }
7668
+ if (minLength !== NOT_SPECIFIED) {
7669
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(MIN_LENGTH, String(minLength)));
7670
+ }
7671
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(MAX_LENGTH, String(maxLength === NOT_SPECIFIED
7672
+ ? ADGUARD_HTML_CONVERSION_MAX_LENGTH
7673
+ : maxLength)));
7674
+ // Create the converted rule
7675
+ conversionResult.push({
7676
+ category: RuleCategory.Cosmetic,
7677
+ type: CosmeticRuleType.HtmlFilteringRule,
7678
+ syntax: AdblockSyntax.Adg,
7679
+ // Convert the separator based on the exception status
7680
+ separator: {
7681
+ type: 'Value',
7682
+ value: ruleNode.exception
7683
+ ? CosmeticRuleSeparator.AdgHtmlFilteringException
7684
+ : CosmeticRuleSeparator.AdgHtmlFiltering,
7685
+ },
7686
+ // Create the body based on the converted selector
7687
+ body: {
7688
+ type: 'HtmlFilteringRuleBody',
7689
+ body: {
7690
+ type: CssTreeNodeType.SelectorList,
7691
+ children: [{
7692
+ type: CssTreeNodeType.Selector,
7693
+ children: [convertedSelector],
7694
+ }],
7695
+ },
7696
+ },
7697
+ exception: ruleNode.exception,
7698
+ domains: ruleNode.domains,
7699
+ });
7700
+ }
7701
+ return conversionResult;
7702
+ }
7703
+ }
7704
+
7705
+ /**
7706
+ * @file Utility functions for working with quotes
7707
+ */
7708
+ /**
7709
+ * Possible quote types for scriptlet parameters
7710
+ */
7711
+ var QuoteType;
7712
+ (function (QuoteType) {
7713
+ /**
7714
+ * No quotes at all
7715
+ */
7716
+ QuoteType["None"] = "none";
7717
+ /**
7718
+ * Single quotes (`'`)
7719
+ */
7720
+ QuoteType["Single"] = "single";
7721
+ /**
7722
+ * Double quotes (`"`)
7723
+ */
7724
+ QuoteType["Double"] = "double";
7725
+ })(QuoteType || (QuoteType = {}));
7726
+ /**
7727
+ * Utility functions for working with quotes
7728
+ */
7729
+ class QuoteUtils {
7730
+ /**
7731
+ * Escape all unescaped occurrences of the character
7732
+ *
7733
+ * @param string String to escape
7734
+ * @param char Character to escape
7735
+ * @returns Escaped string
7736
+ */
7737
+ static escapeUnescapedOccurrences(string, char) {
7738
+ let result = EMPTY;
7739
+ for (let i = 0; i < string.length; i += 1) {
7740
+ if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
7741
+ result += ESCAPE_CHARACTER;
7742
+ }
7743
+ result += string[i];
7744
+ }
7745
+ return result;
7746
+ }
7747
+ /**
7748
+ * Unescape all single escaped occurrences of the character
7749
+ *
7750
+ * @param string String to unescape
7751
+ * @param char Character to unescape
7752
+ * @returns Unescaped string
7753
+ */
7754
+ static unescapeSingleEscapedOccurrences(string, char) {
7755
+ let result = EMPTY;
7756
+ for (let i = 0; i < string.length; i += 1) {
7757
+ if (string[i] === char
7758
+ && string[i - 1] === ESCAPE_CHARACTER
7759
+ && (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
7760
+ result = result.slice(0, -1);
7761
+ }
7762
+ result += string[i];
7763
+ }
7764
+ return result;
7765
+ }
7766
+ /**
7767
+ * Get quote type of the string
7768
+ *
7769
+ * @param string String to check
7770
+ * @returns Quote type of the string
7771
+ */
7772
+ static getStringQuoteType(string) {
7773
+ // Don't check 1-character strings to avoid false positives
7774
+ if (string.length > 1) {
7775
+ if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
7776
+ return QuoteType.Single;
7777
+ }
7778
+ if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
7779
+ return QuoteType.Double;
7780
+ }
7781
+ }
7782
+ return QuoteType.None;
7783
+ }
7784
+ /**
7785
+ * Set quote type of the string
7786
+ *
7787
+ * @param string String to set quote type of
7788
+ * @param quoteType Quote type to set
7789
+ * @returns String with the specified quote type
7790
+ */
7791
+ static setStringQuoteType(string, quoteType) {
7792
+ const actualQuoteType = QuoteUtils.getStringQuoteType(string);
7793
+ switch (quoteType) {
7794
+ case QuoteType.None:
7795
+ if (actualQuoteType === QuoteType.Single) {
7796
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
7797
+ }
7798
+ if (actualQuoteType === QuoteType.Double) {
7799
+ return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
7800
+ }
7801
+ return string;
7802
+ case QuoteType.Single:
7803
+ if (actualQuoteType === QuoteType.None) {
7804
+ return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
7805
+ }
7806
+ if (actualQuoteType === QuoteType.Double) {
7807
+ return SINGLE_QUOTE
7808
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
7809
+ }
7810
+ return string;
7811
+ case QuoteType.Double:
7812
+ if (actualQuoteType === QuoteType.None) {
7813
+ return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
7814
+ }
7815
+ if (actualQuoteType !== QuoteType.Double) {
7816
+ // eslint-disable-next-line max-len
7817
+ return DOUBLE_QUOTE
7818
+ + QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
7819
+ }
7820
+ return string;
7821
+ default:
7822
+ return string;
7823
+ }
7824
+ }
7825
+ /**
7826
+ * Removes bounding quotes from a string, if any
7827
+ *
7828
+ * @param string Input string
7829
+ * @returns String without quotes
7830
+ */
7831
+ static removeQuotes(string) {
7832
+ if (
7833
+ // We should check for string length to avoid false positives
7834
+ string.length > 1
7835
+ && (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
7836
+ && string[0] === string[string.length - 1]) {
7837
+ return string.slice(1, -1);
7838
+ }
7839
+ return string;
7840
+ }
7841
+ }
7842
+
7843
+ /**
7844
+ * @file Utility functions for working with scriptlet nodes
7845
+ */
7846
+ /**
7847
+ * Get name of the scriptlet from the scriptlet node
7848
+ *
7849
+ * @param scriptletNode Scriptlet node to get name of
7850
+ * @returns Name of the scriptlet
7851
+ * @throws If the scriptlet is empty
7852
+ */
7853
+ function getScriptletName(scriptletNode) {
7854
+ if (scriptletNode.children.length === 0) {
7855
+ throw new Error('Empty scriptlet');
7856
+ }
7857
+ return scriptletNode.children[0].value;
7858
+ }
7859
+ /**
7860
+ * Set name of the scriptlet
7861
+ *
7862
+ * @param scriptletNode Scriptlet node to set name of
7863
+ * @param name Name to set
7864
+ * @returns Scriptlet node with the specified name
7865
+ * @throws If the scriptlet is empty
7866
+ */
7867
+ function setScriptletName(scriptletNode, name) {
7868
+ if (scriptletNode.children.length === 0) {
7869
+ throw new Error('Empty scriptlet');
7870
+ }
7871
+ const scriptletNodeClone = cloneDeep(scriptletNode);
7872
+ scriptletNodeClone.children[0].value = name;
7873
+ return scriptletNodeClone;
7874
+ }
7875
+ /**
7876
+ * Set quote type of the scriptlet parameters
7877
+ *
7878
+ * @param scriptletNode Scriptlet node to set quote type of
7879
+ * @param quoteType Preferred quote type
7880
+ * @returns Scriptlet node with the specified quote type
7881
+ */
7882
+ function setScriptletQuoteType(scriptletNode, quoteType) {
7883
+ if (scriptletNode.children.length === 0) {
7884
+ throw new Error('Empty scriptlet');
7885
+ }
7886
+ const scriptletNodeClone = cloneDeep(scriptletNode);
7887
+ for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
7888
+ scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
7889
+ }
7890
+ return scriptletNodeClone;
7891
+ }
7892
+
7893
+ /**
7894
+ * @file Scriptlet conversions from ABP and uBO to ADG
7895
+ */
7896
+ const ABP_SCRIPTLET_PREFIX = 'abp-';
7897
+ const UBO_SCRIPTLET_PREFIX = 'ubo-';
7898
+ /**
7899
+ * Helper class for converting scriptlets from ABP and uBO to ADG
7900
+ */
7901
+ class AdgScriptletConverter {
7902
+ /**
7903
+ * Helper function to convert scriptlets to ADG. We implement the core
7904
+ * logic here to avoid code duplication.
7905
+ *
7906
+ * @param scriptletNode Scriptlet parameter list node to convert
7907
+ * @param prefix Prefix to add to the scriptlet name
7908
+ * @returns Converted scriptlet parameter list node
7909
+ */
7910
+ static convertToAdg(scriptletNode, prefix) {
7911
+ // Remove possible quotes just to make it easier to work with the scriptlet name
7912
+ const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), QuoteType.None);
7913
+ // Clone the node to avoid any side effects
7914
+ let result = cloneDeep(scriptletNode);
7915
+ // Only add prefix if it's not already there
7916
+ if (!scriptletName.startsWith(prefix)) {
7917
+ result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
7918
+ }
7919
+ // ADG scriptlet parameters should be quoted, and single quoted are preferred
7920
+ result = setScriptletQuoteType(result, QuoteType.Single);
7921
+ return result;
7922
+ }
7923
+ /**
7924
+ * Converts an ABP snippet node to ADG scriptlet node, if possible.
7925
+ *
7926
+ * @param scriptletNode Scriptlet node to convert
7927
+ * @returns Converted scriptlet node
7928
+ * @throws If the scriptlet isn't supported by ADG or is invalid
7929
+ * @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
7930
+ */
7931
+ static convertFromAbp = (scriptletNode) => {
7932
+ return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
7933
+ };
7934
+ /**
7935
+ * Convert a uBO scriptlet node to ADG scriptlet node, if possible.
7936
+ *
7937
+ * @param scriptletNode Scriptlet node to convert
7938
+ * @returns Converted scriptlet node
7939
+ * @throws If the scriptlet isn't supported by ADG or is invalid
7940
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
7941
+ */
7942
+ static convertFromUbo = (scriptletNode) => {
7943
+ return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
7944
+ };
7945
+ }
7946
+
7947
+ /**
7948
+ * @file Scriptlet injection rule converter
7949
+ */
7950
+ /**
7951
+ * Scriptlet injection rule converter class
7952
+ *
7953
+ * @todo Implement `convertToUbo` and `convertToAbp`
7954
+ */
7955
+ class ScriptletRuleConverter extends RuleConverterBase {
7956
+ /**
7957
+ * Converts a scriptlet injection rule to AdGuard format, if possible.
7958
+ *
7959
+ * @param rule Rule node to convert
7960
+ * @returns Array of converted rule nodes
7961
+ * @throws If the rule is invalid or cannot be converted
7962
+ */
7963
+ static convertToAdg(rule) {
7964
+ // Clone the provided AST node to avoid side effects
7965
+ const ruleNode = cloneDeep(rule);
7966
+ const convertedScriptlets = [];
7967
+ for (const scriptlet of ruleNode.body.children) {
7968
+ if (ruleNode.syntax === AdblockSyntax.Abp) {
7969
+ convertedScriptlets.push(AdgScriptletConverter.convertFromAbp(scriptlet));
7970
+ }
7971
+ else if (ruleNode.syntax === AdblockSyntax.Ubo) {
7972
+ convertedScriptlets.push(AdgScriptletConverter.convertFromUbo(scriptlet));
7973
+ }
7974
+ else if (ruleNode.syntax === AdblockSyntax.Adg) {
7975
+ convertedScriptlets.push(scriptlet);
7976
+ }
7977
+ }
7978
+ ruleNode.separator.value = ruleNode.exception
7979
+ ? CosmeticRuleSeparator.AdgJsInjectionException
7980
+ : CosmeticRuleSeparator.AdgJsInjection;
7981
+ // ADG doesn't support multiple scriptlets in one rule, so we should split them
7982
+ return convertedScriptlets.map((scriptlet) => {
7983
+ return {
7984
+ ...ruleNode,
7985
+ syntax: AdblockSyntax.Adg,
7986
+ body: {
7987
+ ...ruleNode.body,
7988
+ children: [scriptlet],
7989
+ },
7990
+ };
7991
+ });
7992
+ }
7993
+ }
7994
+
7995
+ /**
7996
+ * @file Cosmetic rule modifier converter from uBO to ADG
7997
+ */
7998
+ const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
7999
+ const ADG_PATH_MODIFIER = 'path';
8000
+ /**
8001
+ * Special characters in modifier regexps that should be escaped
8002
+ */
8003
+ const SPECIAL_MODIFIER_REGEX_CHARS = new Set([
8004
+ OPEN_SQUARE_BRACKET,
8005
+ CLOSE_SQUARE_BRACKET,
8006
+ COMMA,
8007
+ ESCAPE_CHARACTER,
8008
+ ]);
8009
+ /**
8010
+ * Helper class for converting cosmetic rule modifiers from uBO to ADG
8011
+ */
8012
+ class AdgCosmeticRuleModifierConverter {
8013
+ /**
8014
+ * Converts a uBO cosmetic rule modifier list to ADG, if possible.
8015
+ *
8016
+ * @param modifierList Cosmetic rule modifier list node to convert
8017
+ * @returns Converted cosmetic rule modifier list node
8018
+ * @throws If the modifier list cannot be converted
8019
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
8020
+ */
8021
+ static convertFromUbo = (modifierList) => {
8022
+ const convertedModifierList = createModifierListNode();
8023
+ for (const modifier of modifierList.children) {
8024
+ let modifierValue;
8025
+ switch (modifier.modifier.value) {
8026
+ case UBO_MATCHES_PATH_OPERATOR:
8027
+ // :matches-path() should have a value
8028
+ if (!modifier.value) {
8029
+ throw new RuleConversionError('Missing value for :matches-path(...)');
8030
+ }
8031
+ modifierValue = RegExpUtils.isRegexPattern(modifier.value.value)
8032
+ ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
8033
+ : modifier.value.value;
8034
+ // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
8035
+ convertedModifierList.children.push(createModifierNode(ADG_PATH_MODIFIER,
8036
+ // We should negate the regexp if the modifier is an exception
8037
+ modifier.exception
8038
+ // eslint-disable-next-line max-len
8039
+ ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
8040
+ : modifierValue));
8041
+ break;
8042
+ default:
8043
+ // Leave the modifier as-is
8044
+ convertedModifierList.children.push(modifier);
8045
+ }
8046
+ }
8047
+ return convertedModifierList;
8048
+ };
8049
+ }
8050
+
8051
+ // Constants for pseudo-classes (please keep them sorted alphabetically)
8052
+ const ABP_CONTAINS = '-abp-contains';
8053
+ const ABP_HAS = '-abp-has';
8054
+ const CONTAINS = 'contains';
8055
+ const HAS = 'has';
8056
+ const HAS_TEXT = 'has-text';
8057
+ const MATCHES_CSS = 'matches-css';
8058
+ const MATCHES_CSS_AFTER = 'matches-css-after';
8059
+ const MATCHES_CSS_BEFORE = 'matches-css-before';
8060
+ const NOT = 'not';
8061
+ // Constants for pseudo-elements (please keep them sorted alphabetically)
8062
+ const AFTER = 'after';
8063
+ const BEFORE = 'before';
8064
+ /**
8065
+ * Converts some pseudo-classes to pseudo-elements. For example:
8066
+ * - `:before` → `::before`
8067
+ *
8068
+ * @param selectorList Selector list to convert
8069
+ * @returns Converted selector list
8070
+ */
8071
+ function convertToPseudoElements(selectorList) {
8072
+ // Prepare conversion result
8073
+ const selectorListClone = cloneDeep(selectorList);
8074
+ walk(selectorListClone, {
8075
+ leave: (node) => {
8076
+ if (node.type === CssTreeNodeType.PseudoClassSelector) {
8077
+ // :after → ::after
8078
+ // :before → ::before
8079
+ if (node.name === AFTER || node.name === BEFORE) {
8080
+ Object.assign(node, {
8081
+ ...node,
8082
+ type: CssTreeNodeType.PseudoElementSelector,
8083
+ });
8084
+ }
8085
+ }
8086
+ },
8087
+ });
8088
+ return selectorListClone;
8089
+ }
8090
+ /**
8091
+ * Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
8092
+ * pseudo-classes to the new 'matches-css' pseudo-class:
8093
+ * - `:matches-css-before(...)` → `:matches-css(before, ...)`
8094
+ * - `:matches-css-after(...)` → `:matches-css(after, ...)`
8095
+ *
8096
+ * @param node Node to convert
8097
+ * @throws If the node is invalid
8098
+ */
8099
+ function convertLegacyMatchesCss(node) {
8100
+ const nodeClone = cloneDeep(node);
8101
+ if (nodeClone.type === CssTreeNodeType.PseudoClassSelector
8102
+ && [MATCHES_CSS_BEFORE, MATCHES_CSS_AFTER].includes(nodeClone.name)) {
8103
+ if (!nodeClone.children || nodeClone.children.size < 1) {
8104
+ throw new Error(`Invalid ${nodeClone.name} pseudo-class: missing argument`);
8105
+ }
8106
+ // Remove the 'matches-css-' prefix to get the direction
8107
+ const direction = nodeClone.name.substring(MATCHES_CSS.length + 1);
8108
+ // Rename the pseudo-class
8109
+ nodeClone.name = MATCHES_CSS;
8110
+ // Add the direction to the first raw argument
8111
+ const arg = nodeClone.children.first;
8112
+ // Check argument
8113
+ if (!arg) {
8114
+ throw new Error(`Invalid ${nodeClone.name} pseudo-class: argument shouldn't be null`);
8115
+ }
8116
+ if (arg.type !== CssTreeNodeType.Raw) {
8117
+ throw new Error(`Invalid ${nodeClone.name} pseudo-class: unexpected argument type`);
8118
+ }
8119
+ // Add the direction as the first argument
8120
+ arg.value = `${direction},${arg.value}`;
8121
+ // Replace the original node with the converted one
8122
+ Object.assign(node, nodeClone);
8123
+ }
8124
+ }
8125
+ /**
8126
+ * Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
8127
+ * For example:
8128
+ * - `[-ext-has=...]` → `:has(...)`
8129
+ * - `[-ext-contains=...]` → `:contains(...)`
8130
+ * - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
8131
+ *
8132
+ * @param selectorList Selector list AST to convert
8133
+ * @returns Converted selector list
8134
+ */
8135
+ function convertFromLegacyExtendedCss(selectorList) {
8136
+ // Prepare conversion result
8137
+ const selectorListClone = cloneDeep(selectorList);
8138
+ walk(selectorListClone, {
8139
+ leave: (node) => {
8140
+ // :matches-css-before(arg) → :matches-css(before,arg)
8141
+ // :matches-css-after(arg) → :matches-css(after,arg)
8142
+ convertLegacyMatchesCss(node);
8143
+ // [-ext-name=...] → :name(...)
8144
+ // [-ext-name='...'] → :name(...)
8145
+ // [-ext-name="..."] → :name(...)
8146
+ if (node.type === CssTreeNodeType.AttributeSelector
8147
+ && node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)
8148
+ && node.matcher === EQUALS) {
8149
+ // Node value should be exist
8150
+ if (!node.value) {
8151
+ throw new Error(`Invalid ${node.name} attribute selector: missing value`);
8152
+ }
8153
+ // Remove the '-ext-' prefix to get the pseudo-class name
8154
+ const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
8155
+ // Prepare the children list for the pseudo-class node
8156
+ const children = new List();
8157
+ // TODO: Change String node to Raw node to drop the quotes.
8158
+ // The structure of the node is the same, just the type
8159
+ // is different and generate() will generate the quotes
8160
+ // for String node. See:
8161
+ // - https://github.com/csstree/csstree/blob/master/docs/ast.md#string
8162
+ // - https://github.com/csstree/csstree/blob/master/docs/ast.md#raw
8163
+ // if (node.value.type === "String") {
8164
+ // node.value.type = "Raw";
8165
+ // }
8166
+ // For example, if the input is [-ext-has="> .selector"], then
8167
+ // we need to parse "> .selector" as a selector instead of string
8168
+ // it as a raw value
8169
+ if ([HAS, NOT].includes(name)) {
8170
+ // Get the value of the attribute selector
8171
+ const { value } = node;
8172
+ // If the value is an identifier, then simply push it to the
8173
+ // children list, otherwise parse it as a selector list before
8174
+ if (value.type === CssTreeNodeType.Identifier) {
8175
+ children.push(value);
8176
+ }
8177
+ else if (value.type === CssTreeNodeType.String) {
8178
+ // Parse the value as a selector
8179
+ const parsedChildren = CssTree.parse(value.value, CssTreeParserContext.selectorList);
8180
+ // Don't forget convert the parsed AST again, because
8181
+ // it was a raw string before
8182
+ children.push(convertFromLegacyExtendedCss(parsedChildren));
8183
+ }
8184
+ }
8185
+ else {
8186
+ let value = EMPTY;
8187
+ if (node.value.type === CssTreeNodeType.String) {
8188
+ // If the value is a string, then use its value
8189
+ value = node.value.value;
8190
+ }
8191
+ else if (node.value.type === CssTreeNodeType.Identifier) {
8192
+ // If the value is an identifier, then use its name
8193
+ value = node.value.name;
8194
+ }
8195
+ // In other cases, convert value to raw
8196
+ children.push({
8197
+ type: CssTreeNodeType.Raw,
8198
+ value,
8199
+ });
8200
+ }
8201
+ // Create a pseudo-class node with the data from the attribute
8202
+ // selector
8203
+ const pseudoNode = {
8204
+ type: CssTreeNodeType.PseudoClassSelector,
8205
+ name,
8206
+ children,
8207
+ };
8208
+ // Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
8209
+ convertLegacyMatchesCss(pseudoNode);
8210
+ // Convert attribute selector to pseudo-class selector, but
8211
+ // keep the reference to the original node
8212
+ Object.assign(node, pseudoNode);
8213
+ }
8214
+ },
8215
+ });
8216
+ return selectorListClone;
8217
+ }
8218
+ /**
8219
+ * CSS selector converter
8220
+ *
8221
+ * @todo Implement `convertToUbo` and `convertToAbp`
8222
+ */
8223
+ class CssSelectorConverter extends ConverterBase {
8224
+ /**
8225
+ * Converts Extended CSS elements to AdGuard-compatible ones
8226
+ *
8227
+ * @param selectorList Selector list to convert
8228
+ * @returns Converted selector list
8229
+ * @throws If the rule is invalid or incompatible
8230
+ */
8231
+ static convertToAdg(selectorList) {
8232
+ // First, convert
8233
+ // - legacy Extended CSS selectors to the modern Extended CSS syntax and
8234
+ // - some pseudo-classes to pseudo-elements
8235
+ const selectorListClone = convertToPseudoElements(convertFromLegacyExtendedCss(cloneDeep(selectorList)));
8236
+ // Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
8237
+ walk(selectorListClone, {
8238
+ leave: (node) => {
8239
+ if (node.type === CssTreeNodeType.PseudoClassSelector) {
8240
+ // :-abp-contains(...) → :contains(...)
8241
+ // :has-text(...) → :contains(...)
8242
+ if (node.name === ABP_CONTAINS || node.name === HAS_TEXT) {
8243
+ CssTree.renamePseudoClass(node, CONTAINS);
8244
+ }
8245
+ // :-abp-has(...) → :has(...)
8246
+ if (node.name === ABP_HAS) {
8247
+ CssTree.renamePseudoClass(node, HAS);
8248
+ }
8249
+ // TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
8250
+ }
8251
+ },
8252
+ });
8253
+ return selectorListClone;
8254
+ }
8255
+ }
8256
+
8257
+ /**
8258
+ * @file CSS injection rule converter
8259
+ */
8260
+ /**
8261
+ * CSS injection rule converter class
8262
+ *
8263
+ * @todo Implement `convertToUbo` and `convertToAbp`
8264
+ */
8265
+ class CssInjectionRuleConverter extends RuleConverterBase {
8266
+ /**
8267
+ * Converts a CSS injection rule to AdGuard format, if possible.
8268
+ *
8269
+ * @param rule Rule node to convert
8270
+ * @returns Array of converted rule nodes
8271
+ * @throws If the rule is invalid or cannot be converted
8272
+ */
8273
+ static convertToAdg(rule) {
8274
+ // Clone the provided AST node to avoid side effects
8275
+ const ruleNode = cloneDeep(rule);
8276
+ // Change the separator if the rule contains ExtendedCSS selectors
8277
+ if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList) || ruleNode.body.remove) {
8278
+ ruleNode.separator.value = ruleNode.exception
8279
+ ? CosmeticRuleSeparator.AdgExtendedCssInjectionException
8280
+ : CosmeticRuleSeparator.AdgExtendedCssInjection;
8281
+ }
8282
+ else {
8283
+ ruleNode.separator.value = ruleNode.exception
8284
+ ? CosmeticRuleSeparator.AdgCssInjectionException
8285
+ : CosmeticRuleSeparator.AdgCssInjection;
8286
+ }
8287
+ // Convert CSS selector list
8288
+ Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
8289
+ ruleNode.syntax = AdblockSyntax.Adg;
8290
+ return [ruleNode];
8291
+ }
8292
+ }
8293
+
8294
+ /**
8295
+ * @file Element hiding rule converter
8296
+ */
8297
+ /**
8298
+ * Element hiding rule converter class
8299
+ *
8300
+ * @todo Implement `convertToUbo` and `convertToAbp`
8301
+ */
8302
+ class ElementHidingRuleConverter extends RuleConverterBase {
8303
+ /**
8304
+ * Converts an element hiding rule to AdGuard format, if possible.
8305
+ *
8306
+ * @param rule Rule node to convert
8307
+ * @returns Array of converted rule nodes
8308
+ * @throws If the rule is invalid or cannot be converted
8309
+ */
8310
+ static convertToAdg(rule) {
8311
+ // Clone the provided AST node to avoid side effects
8312
+ const ruleNode = cloneDeep(rule);
8313
+ // Change the separator if the rule contains ExtendedCSS selectors
8314
+ if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList)) {
8315
+ ruleNode.separator.value = ruleNode.exception
8316
+ ? CosmeticRuleSeparator.ExtendedElementHidingException
8317
+ : CosmeticRuleSeparator.ExtendedElementHiding;
8318
+ }
8319
+ else {
8320
+ ruleNode.separator.value = ruleNode.exception
8321
+ ? CosmeticRuleSeparator.ElementHidingException
8322
+ : CosmeticRuleSeparator.ElementHiding;
8323
+ }
8324
+ // Convert CSS selector list
8325
+ Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
8326
+ ruleNode.syntax = AdblockSyntax.Adg;
8327
+ return [ruleNode];
8328
+ }
8329
+ }
8330
+
8331
+ /**
8332
+ * @file Utility functions for working with network rule nodes
8333
+ */
8334
+ /**
8335
+ * Creates a network rule node
8336
+ *
8337
+ * @param pattern Rule pattern
8338
+ * @param modifiers Rule modifiers (optional, default: undefined)
8339
+ * @param exception Exception rule flag (optional, default: false)
8340
+ * @param syntax Adblock syntax (optional, default: Common)
8341
+ * @returns Network rule node
8342
+ */
8343
+ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false, syntax = AdblockSyntax.Common) {
8344
+ const result = {
8345
+ category: RuleCategory.Network,
8346
+ type: 'NetworkRule',
8347
+ syntax,
8348
+ exception,
8349
+ pattern: {
8350
+ type: 'Value',
8351
+ value: pattern,
8352
+ },
8353
+ };
8354
+ if (!isUndefined(modifiers)) {
8355
+ result.modifiers = cloneDeep(modifiers);
8356
+ }
8357
+ return result;
8358
+ }
8359
+
8360
+ /**
8361
+ * @file Converter for request header removal rules
8362
+ */
8363
+ const UBO_RESPONSEHEADER_MARKER = 'responseheader';
8364
+ const ADG_REMOVEHEADER_MODIFIER = 'removeheader';
8365
+ /**
8366
+ * Converter for request header removal rules
8367
+ *
8368
+ * @todo Implement `convertToUbo` (ABP currently doesn't support header removal rules)
8369
+ */
8370
+ class HeaderRemovalRuleConverter extends RuleConverterBase {
8371
+ /**
8372
+ * Converts a header removal rule to AdGuard syntax, if possible.
8373
+ *
8374
+ * @param rule Rule node to convert
8375
+ * @returns Array of converted rule nodes
8376
+ * @throws If the rule is invalid or cannot be converted
8377
+ */
8378
+ static convertToAdg(rule) {
8379
+ // Clone the provided AST node to avoid side effects
8380
+ const ruleNode = cloneDeep(rule);
8381
+ // TODO: Add support for ABP syntax once it starts supporting header removal rules
8382
+ // Check the input rule
8383
+ if (ruleNode.category !== RuleCategory.Cosmetic
8384
+ || ruleNode.type !== CosmeticRuleType.HtmlFilteringRule
8385
+ || ruleNode.body.body.type !== CssTreeNodeType.Function
8386
+ || ruleNode.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
8387
+ throw new RuleConversionError('Not a response header rule');
8388
+ }
8389
+ // Prepare network rule pattern
8390
+ let pattern = EMPTY;
8391
+ if (ruleNode.domains.children.length === 1) {
8392
+ // If the rule has only one domain, we can use a simple network rule pattern:
8393
+ // ||single-domain-from-the-rule^
8394
+ pattern = [
8395
+ ADBLOCK_URL_START,
8396
+ ruleNode.domains.children[0].value,
8397
+ ADBLOCK_URL_SEPARATOR,
8398
+ ].join(EMPTY);
8399
+ }
8400
+ else if (ruleNode.domains.children.length > 1) {
8401
+ // TODO: Add support for multiple domains, for example:
8402
+ // example.com,example.org,example.net##^responseheader(header-name)
8403
+ // We should consider allowing $domain with $removeheader modifier,
8404
+ // for example:
8405
+ // $removeheader=header-name,domain=example.com|example.org|example.net
8406
+ throw new RuleConversionError('Multiple domains are not supported yet');
8407
+ }
8408
+ // Prepare network rule modifiers
8409
+ const modifiers = createModifierListNode();
8410
+ modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionValue(fromPlainObject(ruleNode.body.body))));
8411
+ // Construct the network rule
8412
+ return [
8413
+ createNetworkRuleNode(pattern, modifiers,
8414
+ // Copy the exception flag
8415
+ ruleNode.exception, AdblockSyntax.Adg),
8416
+ ];
8417
+ }
8418
+ }
8419
+
8420
+ /**
8421
+ * @file Cosmetic rule converter
8422
+ */
8423
+ /**
8424
+ * Cosmetic rule converter class (also known as "non-basic rule converter")
8425
+ *
8426
+ * @todo Implement `convertToUbo` and `convertToAbp`
8427
+ */
8428
+ class CosmeticRuleConverter extends RuleConverterBase {
8429
+ /**
8430
+ * Converts a cosmetic rule to AdGuard syntax, if possible.
8431
+ *
8432
+ * @param rule Rule node to convert
8433
+ * @returns Array of converted rule nodes
8434
+ * @throws If the rule is invalid or cannot be converted
8435
+ */
8436
+ static convertToAdg(rule) {
8437
+ // Clone the provided AST node to avoid side effects
8438
+ const ruleNode = cloneDeep(rule);
8439
+ // Convert cosmetic rule modifiers
8440
+ if (ruleNode.modifiers) {
8441
+ if (ruleNode.syntax === AdblockSyntax.Ubo) {
8442
+ // uBO doesn't support this rule:
8443
+ // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
8444
+ if (ruleNode.type === CosmeticRuleType.ScriptletInjectionRule) {
8445
+ throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
8446
+ }
8447
+ ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
8448
+ }
8449
+ else if (ruleNode.syntax === AdblockSyntax.Abp) {
8450
+ // TODO: Implement once ABP starts supporting cosmetic rule modifiers
8451
+ throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
8452
+ }
8453
+ }
8454
+ // Convert cosmetic rule based on its type
8455
+ switch (ruleNode.type) {
8456
+ case CosmeticRuleType.ElementHidingRule:
8457
+ return ElementHidingRuleConverter.convertToAdg(ruleNode);
8458
+ case CosmeticRuleType.ScriptletInjectionRule:
8459
+ return ScriptletRuleConverter.convertToAdg(ruleNode);
8460
+ case CosmeticRuleType.CssInjectionRule:
8461
+ return CssInjectionRuleConverter.convertToAdg(ruleNode);
8462
+ case CosmeticRuleType.HtmlFilteringRule:
8463
+ // Handle special case: uBO response header filtering rule
8464
+ if (ruleNode.body.body.type === CssTreeNodeType.Function
8465
+ && ruleNode.body.body.name === UBO_RESPONSEHEADER_MARKER) {
8466
+ return HeaderRemovalRuleConverter.convertToAdg(ruleNode);
8467
+ }
8468
+ return HtmlRuleConverter.convertToAdg(ruleNode);
8469
+ // Note: Currently, only ADG supports JS injection rules
8470
+ case CosmeticRuleType.JsInjectionRule:
8471
+ return [ruleNode];
8472
+ default:
8473
+ throw new RuleConversionError('Unsupported cosmetic rule type');
8474
+ }
8475
+ }
8476
+ }
8477
+
8478
+ /**
8479
+ * @file Network rule modifier list converter.
8480
+ */
8481
+ /**
8482
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#csp-modifier}
8483
+ */
8484
+ const CSP_MODIFIER = 'csp';
8485
+ const CSP_SEPARATOR = SEMICOLON + SPACE;
8486
+ /**
8487
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#csp-modifier}
8488
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy}
8489
+ */
8490
+ const COMMON_CSP_PARAMS = '\'self\' \'unsafe-eval\' http: https: data: blob: mediastream: filesystem:';
8491
+ /**
8492
+ * @see {@link https://help.adblockplus.org/hc/en-us/articles/360062733293#rewrite}
8493
+ */
8494
+ const ABP_REWRITE_MODIFIER = 'rewrite';
8495
+ /**
8496
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#redirect-modifier}
8497
+ */
8498
+ const REDIRECT_MODIFIER = 'redirect';
8499
+ /**
8500
+ * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier}
8501
+ */
8502
+ const REDIRECT_RULE_MODIFIER = 'redirect-rule';
8503
+ /**
8504
+ * Redirect-related modifiers.
8505
+ */
8506
+ const REDIRECT_MODIFIERS = new Set([
8507
+ ABP_REWRITE_MODIFIER,
8508
+ REDIRECT_MODIFIER,
8509
+ REDIRECT_RULE_MODIFIER,
8510
+ ]);
8511
+ /**
8512
+ * Conversion map for ADG network rule modifiers.
8513
+ */
8514
+ const ADG_CONVERSION_MAP = new Map([
8515
+ ['1p', [{ name: () => 'third-party', exception: (actual) => !actual }]],
8516
+ ['3p', [{ name: () => 'third-party' }]],
8517
+ ['css', [{ name: () => 'stylesheet' }]],
8518
+ ['doc', [{ name: () => 'document' }]],
8519
+ ['ehide', [{ name: () => 'elemhide' }]],
8520
+ ['empty', [{ name: () => 'redirect', value: () => 'nooptext' }]],
8521
+ ['first-party', [{ name: () => 'third-party', exception: (actual) => !actual }]],
8522
+ ['frame', [{ name: () => 'subdocument' }]],
8523
+ ['ghide', [{ name: () => 'generichide' }]],
8524
+ ['inline-font', [{ name: () => CSP_MODIFIER, value: () => `font-src ${COMMON_CSP_PARAMS}` }]],
8525
+ ['inline-script', [{ name: () => CSP_MODIFIER, value: () => `script-src ${COMMON_CSP_PARAMS}` }]],
8526
+ ['mp4', [{ name: () => 'redirect', value: () => 'noopmp4-1s' }, { name: () => 'media', value: () => undefined }]],
8527
+ ['queryprune', [{ name: () => 'removeparam' }]],
8528
+ ['shide', [{ name: () => 'specifichide' }]],
8529
+ ['xhr', [{ name: () => 'xmlhttprequest' }]],
8530
+ ]);
8531
+ /**
8532
+ * Helper class for converting network rule modifier lists.
8533
+ *
8534
+ * @todo Implement `convertToUbo` and `convertToAbp`
8535
+ */
8536
+ class NetworkRuleModifierListConverter extends ConverterBase {
8537
+ /**
8538
+ * Converts a network rule modifier list to AdGuard format, if possible.
8539
+ *
8540
+ * @param modifierList Network rule modifier list node to convert
8541
+ * @returns Converted modifier list node
8542
+ * @throws If the conversion is not possible
8543
+ */
8544
+ static convertToAdg(modifierList) {
8545
+ // Clone the provided AST node to avoid side effects
8546
+ const modifierListNode = cloneDeep(modifierList);
8547
+ const convertedModifierList = createModifierListNode();
8548
+ // We should merge $csp modifiers into one
8549
+ const cspValues = [];
8550
+ modifierListNode.children.forEach((modifierNode) => {
8551
+ // Handle regular modifiers conversion and $csp modifiers collection
8552
+ const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
8553
+ if (modifierConversions) {
8554
+ for (const modifierConversion of modifierConversions) {
8555
+ const name = modifierConversion.name(modifierNode.modifier.value);
8556
+ const exception = modifierConversion.exception
8557
+ // If the exception value is undefined in the original modifier, it
8558
+ // means that the modifier isn't negated
8559
+ ? modifierConversion.exception(modifierNode.exception || false)
8560
+ : modifierNode.exception;
8561
+ const value = modifierConversion.value
8562
+ ? modifierConversion.value(modifierNode.value?.value)
8563
+ : modifierNode.value?.value;
8564
+ if (name === CSP_MODIFIER && value) {
8565
+ // Special case: collect $csp values
8566
+ cspValues.push(value);
8567
+ }
8568
+ else {
8569
+ // Regular case: collect the converted modifiers, if the modifier list
8570
+ // not already contains the same modifier
8571
+ const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
8572
+ if (!existingModifier) {
8573
+ convertedModifierList.children.push(createModifierNode(name, value, exception));
8574
+ }
8575
+ }
8576
+ }
8577
+ return;
8578
+ }
8579
+ // Handle special case: resource redirection modifiers
8580
+ if (REDIRECT_MODIFIERS.has(modifierNode.modifier.value)) {
8581
+ // Redirect modifiers can't be negated
8582
+ if (modifierNode.exception === true) {
8583
+ throw new RuleConversionError(`Modifier '${modifierNode.modifier.value}' cannot be negated`);
8584
+ }
8585
+ // Convert the redirect resource name to ADG format
8586
+ const redirectResource = modifierNode.value?.value;
8587
+ if (!redirectResource) {
8588
+ throw new RuleConversionError(`No redirect resource specified for '${modifierNode.modifier.value}' modifier`);
8589
+ }
8590
+ // Leave $redirect and $redirect-rule modifiers as is, but convert $rewrite to $redirect
8591
+ const modifierName = modifierNode.modifier.value === ABP_REWRITE_MODIFIER
8592
+ ? REDIRECT_MODIFIER
8593
+ : modifierNode.modifier.value;
8594
+ // Try to convert the redirect resource name to ADG format
8595
+ // This function returns undefined if the resource name is unknown
8596
+ const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
8597
+ convertedModifierList.children.push(createModifierNode(modifierName,
8598
+ // If the redirect resource name is unknown, fall back to the original one
8599
+ // Later, the validator will throw an error if the resource name is invalid
8600
+ convertedRedirectResource || redirectResource, modifierNode.exception));
8601
+ return;
8602
+ }
8603
+ // In all other cases, just copy the modifier as is, if the modifier list
8604
+ // not already contains the same modifier
8605
+ const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === modifierNode.modifier.value
8606
+ && m.exception === modifierNode.exception
8607
+ && m.value?.value === modifierNode.value?.value);
8608
+ if (!existingModifier) {
8609
+ convertedModifierList.children.push(modifierNode);
8610
+ }
8611
+ });
8612
+ // Merge $csp modifiers into one, then add it to the converted modifier list
8613
+ if (cspValues.length > 0) {
8614
+ convertedModifierList.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
8615
+ }
8616
+ return convertedModifierList;
8617
+ }
8618
+ }
8619
+
8620
+ /**
8621
+ * @file Network rule converter
8622
+ */
8623
+ /**
8624
+ * Network rule converter class (also known as "basic rule converter")
8625
+ *
8626
+ * @todo Implement `convertToUbo` and `convertToAbp`
8627
+ */
8628
+ class NetworkRuleConverter extends RuleConverterBase {
8629
+ /**
8630
+ * Converts a network rule to AdGuard format, if possible.
8631
+ *
8632
+ * @param rule Rule node to convert
8633
+ * @returns Array of converted rule nodes
8634
+ * @throws If the rule is invalid or cannot be converted
8635
+ */
8636
+ static convertToAdg(rule) {
8637
+ // Clone the provided AST node to avoid side effects
8638
+ const ruleNode = cloneDeep(rule);
8639
+ // Convert modifiers
8640
+ if (ruleNode.modifiers) {
8641
+ Object.assign(ruleNode.modifiers, NetworkRuleModifierListConverter.convertToAdg(ruleNode.modifiers));
8642
+ }
8643
+ return [ruleNode];
8644
+ }
8645
+ }
8646
+
8647
+ /**
8648
+ * @file Adblock rule converter
8649
+ *
8650
+ * This file is the entry point for all rule converters
8651
+ * which automatically detects the rule type and calls
8652
+ * the corresponding "sub-converter".
8653
+ */
8654
+ /**
8655
+ * Adblock filtering rule converter class
8656
+ *
8657
+ * @todo Implement `convertToUbo` and `convertToAbp`
8658
+ */
8659
+ class RuleConverter extends RuleConverterBase {
8660
+ /**
8661
+ * Converts an adblock filtering rule to AdGuard format, if possible.
8662
+ *
8663
+ * @param rule Rule node to convert
8664
+ * @returns Array of converted rule nodes
8665
+ * @throws If the rule is invalid or cannot be converted
8666
+ */
8667
+ static convertToAdg(rule) {
8668
+ // Clone the provided AST node to avoid side effects
8669
+ const ruleNode = cloneDeep(rule);
8670
+ // Delegate conversion to the corresponding sub-converter
8671
+ // based on the rule category
8672
+ switch (ruleNode.category) {
8673
+ case RuleCategory.Comment:
8674
+ return CommentRuleConverter.convertToAdg(ruleNode);
8675
+ case RuleCategory.Cosmetic:
8676
+ return CosmeticRuleConverter.convertToAdg(ruleNode);
8677
+ case RuleCategory.Network:
8678
+ return NetworkRuleConverter.convertToAdg(ruleNode);
8679
+ default:
8680
+ throw new RuleConversionError(`Unknown rule category: ${ruleNode.category}`);
8681
+ }
8682
+ }
8683
+ }
8684
+
8685
+ /**
8686
+ * @file Utility functions for working with filter list nodes
8687
+ */
8688
+ /**
8689
+ * Creates a filter list node
8690
+ *
8691
+ * @param rules Rules to put in the list (optional, defaults to an empty list)
8692
+ * @returns Filter list node
8693
+ */
8694
+ function createFilterListNode(rules = []) {
8695
+ const result = {
8696
+ type: 'FilterList',
8697
+ children: [],
8698
+ };
8699
+ // We need to clone the rules to avoid side effects
8700
+ if (rules.length > 0) {
8701
+ result.children = cloneDeep(rules);
8702
+ }
8703
+ return result;
8704
+ }
8705
+
8706
+ /**
8707
+ * @file Adblock filter list converter
8708
+ */
8709
+ /**
8710
+ * Adblock filter list converter class
8711
+ *
8712
+ * This class just provides an extra layer on top of the {@link RuleConverter}
8713
+ * and can be used to convert entire filter lists.
8714
+ *
8715
+ * @todo Implement `convertToUbo` and `convertToAbp`
8716
+ * @todo Implement tolerant mode, which will allow to convert a filter list
8717
+ * even if some of its rules are invalid
8718
+ */
8719
+ class FilterListConverter extends ConverterBase {
8720
+ /**
8721
+ * Converts an adblock filter list to AdGuard format, if possible.
8722
+ *
8723
+ * @param filterListNode Filter list node to convert
8724
+ * @returns Converted filter list node
8725
+ * @throws If the filter list is invalid or cannot be converted
8726
+ */
8727
+ static convertToAdg(filterListNode) {
8728
+ const result = createFilterListNode();
8729
+ // Iterate over the filtering rules and convert them one by one,
8730
+ // then add them to the result (one conversion may result in multiple rules)
8731
+ for (const ruleNode of filterListNode.children) {
8732
+ const convertedRules = RuleConverter.convertToAdg(ruleNode);
8733
+ result.children.push(...convertedRules);
8734
+ }
8735
+ return result;
8736
+ }
8737
+ }
8738
+
4839
8739
  /**
4840
8740
  * @file Utility functions for domain and hostname validation.
4841
8741
  */
@@ -4949,7 +8849,7 @@ class LogicalExpressionUtils {
4949
8849
  }
4950
8850
  }
4951
8851
 
4952
- const version$1 = "1.0.1";
8852
+ const version$1 = "1.1.0";
4953
8853
 
4954
8854
  /**
4955
8855
  * @file AGTree version
@@ -4960,4 +8860,4 @@ const version$1 = "1.0.1";
4960
8860
  // with wrong relative path to `package.json`. So we need this little "hack"
4961
8861
  const version = version$1;
4962
8862
 
4963
- export { AdblockSyntaxError, AgentCommentRuleParser, AgentParser, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleType, DomainListParser, DomainUtils, FilterListParser, HintCommentRuleParser, HintParser, LogicalExpressionParser, LogicalExpressionUtils, MetadataCommentRuleParser, ModifierListParser, ModifierParser, NetworkRuleParser, ParameterListParser, PreProcessorCommentRuleParser, RuleCategory, RuleParser, version };
8863
+ 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 };