@adguard/agtree 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -5
- package/README.md +74 -283
- package/dist/agtree.cjs +4273 -291
- package/dist/agtree.d.ts +1273 -397
- package/dist/agtree.esm.js +4143 -217
- package/dist/agtree.iife.min.js +9 -3
- package/dist/agtree.umd.min.js +9 -3
- package/dist/build.txt +1 -1
- package/package.json +36 -23
package/dist/agtree.esm.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* AGTree v1.
|
|
2
|
+
* AGTree v1.1.1 (build date: Fri, 11 Aug 2023 13:11:01 GMT)
|
|
3
3
|
* (c) 2023 AdGuard Software Ltd.
|
|
4
4
|
* Released under the MIT license
|
|
5
|
-
* https://github.com/AdguardTeam/
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1115
|
+
// ##+
|
|
1116
|
+
return createResult(i, CosmeticRuleSeparator.UboScriptletInjection);
|
|
946
1117
|
}
|
|
947
1118
|
if (rule[i + 2] === '^') {
|
|
948
|
-
|
|
1119
|
+
// ##^
|
|
1120
|
+
return createResult(i, CosmeticRuleSeparator.UboHtmlFiltering);
|
|
949
1121
|
}
|
|
950
1122
|
if (rule[i - 1] !== SPACE) {
|
|
951
|
-
|
|
1123
|
+
// ##
|
|
1124
|
+
return createResult(i, CosmeticRuleSeparator.ElementHiding);
|
|
952
1125
|
}
|
|
953
1126
|
}
|
|
954
1127
|
if (rule[i + 1] === '?' && rule[i + 2] === '#') {
|
|
955
|
-
|
|
1128
|
+
// #?#
|
|
1129
|
+
return createResult(i, CosmeticRuleSeparator.ExtendedElementHiding);
|
|
956
1130
|
}
|
|
957
1131
|
if (rule[i + 1] === '%' && rule[i + 2] === '#') {
|
|
958
|
-
|
|
1132
|
+
// #%#
|
|
1133
|
+
return createResult(i, CosmeticRuleSeparator.AdgJsInjection);
|
|
959
1134
|
}
|
|
960
1135
|
if (rule[i + 1] === '$') {
|
|
961
1136
|
if (rule[i + 2] === '#') {
|
|
962
|
-
|
|
1137
|
+
// #$#
|
|
1138
|
+
return createResult(i, CosmeticRuleSeparator.AdgCssInjection);
|
|
963
1139
|
}
|
|
964
1140
|
if (rule[i + 2] === '?' && rule[i + 3] === '#') {
|
|
965
|
-
|
|
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
|
-
|
|
1149
|
+
// #@#+
|
|
1150
|
+
return createResult(i, CosmeticRuleSeparator.UboScriptletInjectionException);
|
|
973
1151
|
}
|
|
974
1152
|
if (rule[i + 3] === '^') {
|
|
975
|
-
|
|
1153
|
+
// #@#^
|
|
1154
|
+
return createResult(i, CosmeticRuleSeparator.UboHtmlFilteringException);
|
|
976
1155
|
}
|
|
977
1156
|
if (rule[i - 1] !== SPACE) {
|
|
978
|
-
|
|
1157
|
+
// #@#
|
|
1158
|
+
return createResult(i, CosmeticRuleSeparator.ElementHidingException);
|
|
979
1159
|
}
|
|
980
1160
|
}
|
|
981
1161
|
if (rule[i + 2] === '?' && rule[i + 3] === '#') {
|
|
982
|
-
|
|
1162
|
+
// #@?#
|
|
1163
|
+
return createResult(i, CosmeticRuleSeparator.ExtendedElementHidingException);
|
|
983
1164
|
}
|
|
984
1165
|
if (rule[i + 2] === '%' && rule[i + 3] === '#') {
|
|
985
|
-
|
|
1166
|
+
// #@%#
|
|
1167
|
+
return createResult(i, CosmeticRuleSeparator.AdgJsInjectionException);
|
|
986
1168
|
}
|
|
987
1169
|
if (rule[i + 2] === '$') {
|
|
988
1170
|
if (rule[i + 3] === '#') {
|
|
989
|
-
|
|
1171
|
+
// #@$#
|
|
1172
|
+
return createResult(i, CosmeticRuleSeparator.AdgCssInjectionException);
|
|
990
1173
|
}
|
|
991
1174
|
if (rule[i + 3] === '?' && rule[i + 4] === '#') {
|
|
992
|
-
|
|
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
|
-
|
|
1183
|
+
// $$
|
|
1184
|
+
return createResult(i, CosmeticRuleSeparator.AdgHtmlFiltering);
|
|
1000
1185
|
}
|
|
1001
1186
|
if (rule[i + 1] === '@' && rule[i + 2] === '$') {
|
|
1002
|
-
|
|
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 =
|
|
2563
|
-
|
|
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
|
|
2571
|
-
|
|
2572
|
-
const modifierEnd =
|
|
2573
|
-
?
|
|
2574
|
-
: StringUtils.skipWSBack(raw,
|
|
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 =
|
|
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
|
-
*
|
|
3098
|
+
* Checks if the CSSTree node is an ExtendedCSS node.
|
|
2871
3099
|
*
|
|
2872
|
-
* @param
|
|
2873
|
-
* @
|
|
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
|
|
2876
|
-
|
|
2877
|
-
|
|
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
|
-
*
|
|
3110
|
+
* Walks through the CSSTree node and returns all ExtendedCSS nodes.
|
|
2900
3111
|
*
|
|
2901
|
-
* @param
|
|
2902
|
-
* @
|
|
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
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
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
|
|
3135
|
+
return nodes;
|
|
2920
3136
|
}
|
|
2921
3137
|
/**
|
|
2922
|
-
*
|
|
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.
|
|
3821
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
*
|
|
5213
|
+
* Generates the rule pattern from the AST.
|
|
4393
5214
|
*
|
|
4394
5215
|
* @param ast Cosmetic rule AST
|
|
4395
|
-
* @returns Raw
|
|
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
|
|
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
|
-
|
|
4409
|
-
|
|
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
|
|
5250
|
+
result = ElementHidingBodyParser.generate(ast.body);
|
|
4414
5251
|
break;
|
|
4415
5252
|
case CosmeticRuleType.CssInjectionRule:
|
|
4416
|
-
result
|
|
5253
|
+
result = CssInjectionBodyParser.generate(ast.body, ast.syntax);
|
|
4417
5254
|
break;
|
|
4418
5255
|
case CosmeticRuleType.HtmlFilteringRule:
|
|
4419
|
-
result
|
|
5256
|
+
result = HtmlFilteringBodyParser.generate(ast.body, ast.syntax);
|
|
4420
5257
|
break;
|
|
4421
5258
|
case CosmeticRuleType.JsInjectionRule:
|
|
4422
5259
|
// Native JS code
|
|
4423
|
-
result
|
|
5260
|
+
result = ast.body.value;
|
|
4424
5261
|
break;
|
|
4425
5262
|
case CosmeticRuleType.ScriptletInjectionRule:
|
|
4426
|
-
result
|
|
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
|
-
*
|
|
5271
|
+
* Converts a cosmetic rule AST into a string.
|
|
4447
5272
|
*
|
|
4448
|
-
* @param
|
|
4449
|
-
* @
|
|
4450
|
-
|
|
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,3063 @@ 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_optional:true,
|
|
5822
|
+
value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" },
|
|
5823
|
+
adg_ext_any:{ name:"cookie",
|
|
5824
|
+
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.",
|
|
5825
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#cookie-modifier",
|
|
5826
|
+
assignable:true,
|
|
5827
|
+
negatable:false,
|
|
5828
|
+
value_optional:true,
|
|
5829
|
+
value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" } };
|
|
5830
|
+
|
|
5831
|
+
var data$N = { adg_os_any:{ name:"csp",
|
|
5832
|
+
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.",
|
|
5833
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
|
|
5834
|
+
conflicts:[ "domain",
|
|
5835
|
+
"important",
|
|
5836
|
+
"subdocument",
|
|
5837
|
+
"badfilter" ],
|
|
5838
|
+
inverse_conflicts:true,
|
|
5839
|
+
assignable:true,
|
|
5840
|
+
negatable:false,
|
|
5841
|
+
value_optional:true,
|
|
5842
|
+
value_format:"/[^,$]+/" },
|
|
5843
|
+
adg_ext_any:{ name:"csp",
|
|
5844
|
+
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.",
|
|
5845
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
|
|
5846
|
+
conflicts:[ "domain",
|
|
5847
|
+
"important",
|
|
5848
|
+
"subdocument",
|
|
5849
|
+
"badfilter" ],
|
|
5850
|
+
inverse_conflicts:true,
|
|
5851
|
+
assignable:true,
|
|
5852
|
+
negatable:false,
|
|
5853
|
+
value_optional:true,
|
|
5854
|
+
value_format:"/[^,$]+/" },
|
|
5855
|
+
abp_ext_any:{ name:"csp",
|
|
5856
|
+
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.",
|
|
5857
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#content-security-policies",
|
|
5858
|
+
conflicts:[ "domain",
|
|
5859
|
+
"subdocument" ],
|
|
5860
|
+
inverse_conflicts:true,
|
|
5861
|
+
assignable:true,
|
|
5862
|
+
negatable:false,
|
|
5863
|
+
value_optional:true,
|
|
5864
|
+
value_format:"/[^,$]+/" },
|
|
5865
|
+
ubo_ext_any:{ name:"csp",
|
|
5866
|
+
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.",
|
|
5867
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#csp",
|
|
5868
|
+
conflicts:[ "1p",
|
|
5869
|
+
"3p",
|
|
5870
|
+
"domain",
|
|
5871
|
+
"badfilter" ],
|
|
5872
|
+
inverse_conflicts:true,
|
|
5873
|
+
assignable:true,
|
|
5874
|
+
negatable:false,
|
|
5875
|
+
value_optional:true,
|
|
5876
|
+
value_format:"/[^,$]+/" } };
|
|
5877
|
+
|
|
5878
|
+
var data$M = { adg_os_any:{ name:"denyallow",
|
|
5879
|
+
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.",
|
|
5880
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
|
|
5881
|
+
conflicts:[ "to" ],
|
|
5882
|
+
assignable:true,
|
|
5883
|
+
negatable:false,
|
|
5884
|
+
value_format:"pipe_separated_domains" },
|
|
5885
|
+
adg_ext_any:{ name:"denyallow",
|
|
5886
|
+
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.",
|
|
5887
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
|
|
5888
|
+
conflicts:[ "to" ],
|
|
5889
|
+
assignable:true,
|
|
5890
|
+
negatable:false,
|
|
5891
|
+
value_format:"pipe_separated_domains" },
|
|
5892
|
+
adg_cb_ios:{ name:"denyallow",
|
|
5893
|
+
description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
|
|
5894
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
|
|
5895
|
+
conflicts:[ "to" ],
|
|
5896
|
+
assignable:true,
|
|
5897
|
+
negatable:false,
|
|
5898
|
+
value_format:"pipe_separated_domains" },
|
|
5899
|
+
adg_cb_safari:{ name:"denyallow",
|
|
5900
|
+
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.",
|
|
5901
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
|
|
5902
|
+
conflicts:[ "to" ],
|
|
5903
|
+
assignable:true,
|
|
5904
|
+
negatable:false,
|
|
5905
|
+
value_format:"pipe_separated_domains" },
|
|
5906
|
+
ubo_ext_any:{ name:"denyallow",
|
|
5907
|
+
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.",
|
|
5908
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#denyallow",
|
|
5909
|
+
conflicts:[ "to" ],
|
|
5910
|
+
assignable:true,
|
|
5911
|
+
negatable:false,
|
|
5912
|
+
value_format:"pipe_separated_domains" } };
|
|
5913
|
+
|
|
5914
|
+
var data$L = { adg_os_any:{ name:"document",
|
|
5915
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5916
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
|
|
5917
|
+
negatable:false },
|
|
5918
|
+
adg_ext_any:{ name:"document",
|
|
5919
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5920
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
|
|
5921
|
+
negatable:false },
|
|
5922
|
+
adg_cb_ios:{ name:"document",
|
|
5923
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5924
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
|
|
5925
|
+
negatable:false },
|
|
5926
|
+
adg_cb_safari:{ name:"document",
|
|
5927
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5928
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
|
|
5929
|
+
negatable:false },
|
|
5930
|
+
abp_ext_any:{ name:"document",
|
|
5931
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5932
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#allowlist",
|
|
5933
|
+
negatable:false },
|
|
5934
|
+
ubo_ext_any:{ name:"document",
|
|
5935
|
+
aliases:[ "doc" ],
|
|
5936
|
+
description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
|
|
5937
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#document",
|
|
5938
|
+
negatable:false } };
|
|
5939
|
+
|
|
5940
|
+
var data$K = { adg_any:{ name:"domain",
|
|
5941
|
+
aliases:[ "from" ],
|
|
5942
|
+
description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
|
|
5943
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#domain-modifier",
|
|
5944
|
+
assignable:true,
|
|
5945
|
+
negatable:false,
|
|
5946
|
+
value_format:"pipe_separated_domains" },
|
|
5947
|
+
abp_any:{ name:"domain",
|
|
5948
|
+
description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
|
|
5949
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#domain-restrictions",
|
|
5950
|
+
assignable:true,
|
|
5951
|
+
negatable:false,
|
|
5952
|
+
value_format:"pipe_separated_domains" },
|
|
5953
|
+
ubo_any:{ name:"domain",
|
|
5954
|
+
aliases:[ "from" ],
|
|
5955
|
+
description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
|
|
5956
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#from",
|
|
5957
|
+
assignable:true,
|
|
5958
|
+
negatable:false,
|
|
5959
|
+
value_format:"pipe_separated_domains" } };
|
|
5960
|
+
|
|
5961
|
+
var data$J = { adg_any:{ name:"elemhide",
|
|
5962
|
+
aliases:[ "ehide" ],
|
|
5963
|
+
description:"Disables any cosmetic rules on the pages matching the rule.",
|
|
5964
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#elemhide-modifier",
|
|
5965
|
+
negatable:false,
|
|
5966
|
+
exception_only:true },
|
|
5967
|
+
abp_any:{ name:"elemhide",
|
|
5968
|
+
aliases:[ "ehide" ],
|
|
5969
|
+
description:"Disables any cosmetic rules on the pages matching the rule.",
|
|
5970
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
5971
|
+
negatable:false,
|
|
5972
|
+
exception_only:true },
|
|
5973
|
+
ubo_any:{ name:"elemhide",
|
|
5974
|
+
aliases:[ "ehide" ],
|
|
5975
|
+
negatable:false,
|
|
5976
|
+
exception_only:true,
|
|
5977
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#elemhide-1",
|
|
5978
|
+
description:"Disables any cosmetic rules on the pages matching the rule." } };
|
|
5979
|
+
|
|
5980
|
+
var data$I = { adg_os_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
|
+
adg_ext_any:{ name:"empty",
|
|
5987
|
+
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.",
|
|
5988
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#empty-modifier",
|
|
5989
|
+
deprecated:true,
|
|
5990
|
+
deprecation_message:"Rules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
|
|
5991
|
+
negatable:false },
|
|
5992
|
+
ubo_ext_any:{ name:"empty",
|
|
5993
|
+
description:"This modifier is deprecated in favor of the $redirect modifier.\nRules with `$empty` are supported and being converted into `$redirect=nooptext`.",
|
|
5994
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#empty",
|
|
5995
|
+
negatable:false } };
|
|
5996
|
+
|
|
5997
|
+
var data$H = { adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#third-party-modifier",
|
|
6002
|
+
negatable:false },
|
|
6003
|
+
ubo_any:{ name:"first-party",
|
|
6004
|
+
aliases:[ "1p",
|
|
6005
|
+
"~third-party" ],
|
|
6006
|
+
description:"A restriction of first-party requests. Equal to `~third-party`.",
|
|
6007
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#1p",
|
|
6008
|
+
negatable:false } };
|
|
6009
|
+
|
|
6010
|
+
var data$G = { adg_os_any:{ name:"extension",
|
|
6011
|
+
description:"Disables all userscripts on the pages matching this rule.",
|
|
6012
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#extension-modifier",
|
|
6013
|
+
conflicts:[ "domain",
|
|
6014
|
+
"specifichide",
|
|
6015
|
+
"generichide",
|
|
6016
|
+
"elemhide",
|
|
6017
|
+
"genericblock",
|
|
6018
|
+
"urlblock",
|
|
6019
|
+
"jsinject",
|
|
6020
|
+
"content",
|
|
6021
|
+
"xmlhttprequest",
|
|
6022
|
+
"badfilter" ],
|
|
6023
|
+
inverse_conflicts:true,
|
|
6024
|
+
exception_only:true } };
|
|
6025
|
+
|
|
6026
|
+
var data$F = { adg_any:{ name:"font",
|
|
6027
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#font-modifier",
|
|
6028
|
+
description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
|
|
6029
|
+
abp_any:{ name:"font",
|
|
6030
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
|
|
6031
|
+
description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
|
|
6032
|
+
ubo_any:{ name:"font",
|
|
6033
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
|
|
6034
|
+
description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." } };
|
|
6035
|
+
|
|
6036
|
+
var data$E = { adg_os_any:{ name:"genericblock",
|
|
6037
|
+
description:"Disables generic basic rules on pages that correspond to exception rule.",
|
|
6038
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
|
|
6039
|
+
conflicts:[ "domain",
|
|
6040
|
+
"specifichide",
|
|
6041
|
+
"generichide",
|
|
6042
|
+
"elemhide",
|
|
6043
|
+
"extension",
|
|
6044
|
+
"jsinject",
|
|
6045
|
+
"content",
|
|
6046
|
+
"badfilter" ],
|
|
6047
|
+
inverse_conflicts:true,
|
|
6048
|
+
negatable:false,
|
|
6049
|
+
exception_only:true },
|
|
6050
|
+
adg_ext_any:{ name:"genericblock",
|
|
6051
|
+
description:"Disables generic basic rules on pages that correspond to exception rule.",
|
|
6052
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
|
|
6053
|
+
conflicts:[ "domain",
|
|
6054
|
+
"specifichide",
|
|
6055
|
+
"generichide",
|
|
6056
|
+
"elemhide",
|
|
6057
|
+
"jsinject",
|
|
6058
|
+
"badfilter" ],
|
|
6059
|
+
inverse_conflicts:true,
|
|
6060
|
+
negatable:false,
|
|
6061
|
+
exception_only:true },
|
|
6062
|
+
adg_cb_ios:{ name:"genericblock",
|
|
6063
|
+
description:"Disables generic basic rules on pages that correspond to exception rule.",
|
|
6064
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
|
|
6065
|
+
conflicts:[ "domain",
|
|
6066
|
+
"specifichide",
|
|
6067
|
+
"generichide",
|
|
6068
|
+
"elemhide",
|
|
6069
|
+
"jsinject",
|
|
6070
|
+
"badfilter" ],
|
|
6071
|
+
inverse_conflicts:true,
|
|
6072
|
+
negatable:false,
|
|
6073
|
+
exception_only:true },
|
|
6074
|
+
adg_cb_safari:{ name:"genericblock",
|
|
6075
|
+
description:"Disables generic basic rules on pages that correspond to exception rule.",
|
|
6076
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
|
|
6077
|
+
conflicts:[ "domain",
|
|
6078
|
+
"specifichide",
|
|
6079
|
+
"generichide",
|
|
6080
|
+
"elemhide",
|
|
6081
|
+
"jsinject",
|
|
6082
|
+
"badfilter" ],
|
|
6083
|
+
inverse_conflicts:true,
|
|
6084
|
+
negatable:false,
|
|
6085
|
+
exception_only:true },
|
|
6086
|
+
abp_ext_any:{ name:"genericblock",
|
|
6087
|
+
description:"Disables generic basic rules on pages that correspond to exception rule.",
|
|
6088
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
6089
|
+
negatable:false,
|
|
6090
|
+
exception_only:true } };
|
|
6091
|
+
|
|
6092
|
+
var data$D = { adg_any:{ name:"generichide",
|
|
6093
|
+
aliases:[ "ghide" ],
|
|
6094
|
+
description:"Disables all generic cosmetic rules.",
|
|
6095
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#generichide-modifier",
|
|
6096
|
+
conflicts:[ "domain",
|
|
6097
|
+
"genericblock",
|
|
6098
|
+
"urlblock",
|
|
6099
|
+
"extension",
|
|
6100
|
+
"jsinject",
|
|
6101
|
+
"content",
|
|
6102
|
+
"xmlhttprequest",
|
|
6103
|
+
"badfilter" ],
|
|
6104
|
+
inverse_conflicts:true,
|
|
6105
|
+
negatable:false,
|
|
6106
|
+
exception_only:true },
|
|
6107
|
+
ubo_any:{ name:"generichide",
|
|
6108
|
+
aliases:[ "ghide" ],
|
|
6109
|
+
description:"Disables all generic cosmetic rules.",
|
|
6110
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#generichide",
|
|
6111
|
+
conflicts:[ "domain",
|
|
6112
|
+
"badfilter" ],
|
|
6113
|
+
inverse_conflicts:true,
|
|
6114
|
+
negatable:false,
|
|
6115
|
+
exception_only:true },
|
|
6116
|
+
abp_any:{ name:"generichide",
|
|
6117
|
+
description:"Disables all generic cosmetic rules.",
|
|
6118
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
6119
|
+
conflicts:[ "domain" ],
|
|
6120
|
+
inverse_conflicts:true,
|
|
6121
|
+
negatable:false,
|
|
6122
|
+
exception_only:true } };
|
|
6123
|
+
|
|
6124
|
+
var data$C = { adg_os_any:{ name:"header",
|
|
6125
|
+
description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
|
|
6126
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
|
|
6127
|
+
assignable:true,
|
|
6128
|
+
value_format:"/^[A-z0-9-]+(:.+|)$/" },
|
|
6129
|
+
adg_ext_any:{ name:"header",
|
|
6130
|
+
description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
|
|
6131
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
|
|
6132
|
+
assignable:true,
|
|
6133
|
+
value_format:"/^[A-z0-9-]+(:.+|)$/" },
|
|
6134
|
+
ubo_ext_any:{ name:"header",
|
|
6135
|
+
description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
|
|
6136
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#header",
|
|
6137
|
+
assignable:true,
|
|
6138
|
+
value_format:"/^[A-z0-9-]+(:.+|)$/" } };
|
|
6139
|
+
|
|
6140
|
+
var data$B = { adg_os_any:{ name:"hls",
|
|
6141
|
+
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).",
|
|
6142
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#hls-modifier",
|
|
6143
|
+
version_added:"CoreLibs 1.10",
|
|
6144
|
+
conflicts:[ "domain",
|
|
6145
|
+
"third-party",
|
|
6146
|
+
"app",
|
|
6147
|
+
"important",
|
|
6148
|
+
"match-case",
|
|
6149
|
+
"xmlhttprequest" ],
|
|
6150
|
+
inverse_conflicts:true,
|
|
6151
|
+
assignable:true,
|
|
6152
|
+
negatable:false,
|
|
6153
|
+
value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
|
|
6154
|
+
|
|
6155
|
+
var data$A = { adg_any:{ name:"image",
|
|
6156
|
+
description:"The rule corresponds to images requests.",
|
|
6157
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#image-modifier" },
|
|
6158
|
+
abp_any:{ name:"image",
|
|
6159
|
+
description:"The rule corresponds to images requests.",
|
|
6160
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" },
|
|
6161
|
+
ubo_any:{ name:"image",
|
|
6162
|
+
description:"The rule corresponds to images requests.",
|
|
6163
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
|
|
6164
|
+
|
|
6165
|
+
var data$z = { adg_any:{ name:"important",
|
|
6166
|
+
description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
|
|
6167
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#important-modifier",
|
|
6168
|
+
negatable:false },
|
|
6169
|
+
ubo_any:{ name:"important",
|
|
6170
|
+
description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
|
|
6171
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#important",
|
|
6172
|
+
negatable:false } };
|
|
6173
|
+
|
|
6174
|
+
var data$y = { adg_os_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://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
|
|
6177
|
+
adg_ext_any:{ name:"inline-font",
|
|
6178
|
+
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```",
|
|
6179
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
|
|
6180
|
+
ubo_ext_any:{ name:"inline-font",
|
|
6181
|
+
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```",
|
|
6182
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-font" } };
|
|
6183
|
+
|
|
6184
|
+
var data$x = { adg_os_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://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
|
|
6187
|
+
adg_ext_any:{ name:"inline-script",
|
|
6188
|
+
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```",
|
|
6189
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
|
|
6190
|
+
ubo_ext_any:{ name:"inline-script",
|
|
6191
|
+
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```",
|
|
6192
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-script" } };
|
|
6193
|
+
|
|
6194
|
+
var data$w = { adg_os_any:{ name:"jsinject",
|
|
6195
|
+
description:"Forbids adding of javascript code to the page.",
|
|
6196
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
|
|
6197
|
+
conflicts:[ "domain",
|
|
6198
|
+
"specifichide",
|
|
6199
|
+
"generichide",
|
|
6200
|
+
"elemhide",
|
|
6201
|
+
"genericblock",
|
|
6202
|
+
"urlblock",
|
|
6203
|
+
"extension",
|
|
6204
|
+
"content",
|
|
6205
|
+
"xmlhttprequest",
|
|
6206
|
+
"badfilter" ],
|
|
6207
|
+
inverse_conflicts:true,
|
|
6208
|
+
negatable:false,
|
|
6209
|
+
exception_only:true },
|
|
6210
|
+
adg_ext_any:{ name:"jsinject",
|
|
6211
|
+
description:"Forbids adding of javascript code to the page.",
|
|
6212
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
|
|
6213
|
+
conflicts:[ "domain",
|
|
6214
|
+
"specifichide",
|
|
6215
|
+
"generichide",
|
|
6216
|
+
"elemhide",
|
|
6217
|
+
"genericblock",
|
|
6218
|
+
"urlblock",
|
|
6219
|
+
"xmlhttprequest",
|
|
6220
|
+
"badfilter" ],
|
|
6221
|
+
inverse_conflicts:true,
|
|
6222
|
+
negatable:false,
|
|
6223
|
+
exception_only:true },
|
|
6224
|
+
adg_cb_ios:{ name:"jsinject",
|
|
6225
|
+
description:"Forbids adding of javascript code to the page.",
|
|
6226
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
|
|
6227
|
+
conflicts:[ "domain",
|
|
6228
|
+
"specifichide",
|
|
6229
|
+
"generichide",
|
|
6230
|
+
"elemhide",
|
|
6231
|
+
"genericblock",
|
|
6232
|
+
"urlblock",
|
|
6233
|
+
"xmlhttprequest",
|
|
6234
|
+
"badfilter" ],
|
|
6235
|
+
inverse_conflicts:true,
|
|
6236
|
+
negatable:false,
|
|
6237
|
+
exception_only:true },
|
|
6238
|
+
adg_cb_safari:{ name:"jsinject",
|
|
6239
|
+
description:"Forbids adding of javascript code to the page.",
|
|
6240
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
|
|
6241
|
+
conflicts:[ "domain",
|
|
6242
|
+
"specifichide",
|
|
6243
|
+
"generichide",
|
|
6244
|
+
"elemhide",
|
|
6245
|
+
"genericblock",
|
|
6246
|
+
"urlblock",
|
|
6247
|
+
"xmlhttprequest",
|
|
6248
|
+
"badfilter" ],
|
|
6249
|
+
inverse_conflicts:true,
|
|
6250
|
+
negatable:false,
|
|
6251
|
+
exception_only:true } };
|
|
6252
|
+
|
|
6253
|
+
var data$v = { adg_os_any:{ name:"jsonprune",
|
|
6254
|
+
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.",
|
|
6255
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
|
|
6256
|
+
assignable:true,
|
|
6257
|
+
negatable:false,
|
|
6258
|
+
value_format:"/^\\\\\\$\\.(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
|
|
6259
|
+
|
|
6260
|
+
var data$u = { adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#match-case-modifier" },
|
|
6263
|
+
abp_any:{ name:"match-case",
|
|
6264
|
+
description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
|
|
6265
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6266
|
+
ubo_any:{ name:"match-case",
|
|
6267
|
+
description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
|
|
6268
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#match-case" } };
|
|
6269
|
+
|
|
6270
|
+
var data$t = { adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#media-modifier" },
|
|
6273
|
+
abp_any:{ name:"media",
|
|
6274
|
+
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.",
|
|
6275
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" },
|
|
6276
|
+
ubo_any:{ name:"media",
|
|
6277
|
+
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.",
|
|
6278
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
|
|
6279
|
+
|
|
6280
|
+
var data$s = { adg_os_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
|
+
adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
|
|
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
|
+
ubo_ext_any:{ name:"method",
|
|
6293
|
+
description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
|
|
6294
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#method",
|
|
6295
|
+
negatable:false,
|
|
6296
|
+
assignable:true,
|
|
6297
|
+
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|(\\|?|~?))+(?<!\\|)$" } };
|
|
6298
|
+
|
|
6299
|
+
var data$r = { adg_os_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
|
+
adg_ext_any:{ name:"mp4",
|
|
6306
|
+
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.",
|
|
6307
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#mp4-modifier",
|
|
6308
|
+
deprecated:true,
|
|
6309
|
+
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.",
|
|
6310
|
+
negatable:false },
|
|
6311
|
+
ubo_ext_any:{ name:"mp4",
|
|
6312
|
+
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`.",
|
|
6313
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#mp4",
|
|
6314
|
+
negatable:false } };
|
|
6315
|
+
|
|
6316
|
+
var data$q = { adg_os_any:{ name:"network",
|
|
6317
|
+
description:"This is basically a Firewall-kind of rules allowing to fully block\nor unblock access to a specified remote address.",
|
|
6318
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#network-modifier",
|
|
6319
|
+
conflicts:[ "app",
|
|
6320
|
+
"important" ],
|
|
6321
|
+
inverse_conflicts:true,
|
|
6322
|
+
negatable:false } };
|
|
6323
|
+
|
|
6324
|
+
var data$p = { adg_os_any:{ name:"_",
|
|
6325
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
|
|
6326
|
+
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.",
|
|
6327
|
+
negatable:false },
|
|
6328
|
+
adg_ext_any:{ name:"_",
|
|
6329
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
|
|
6330
|
+
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.",
|
|
6331
|
+
negatable:false },
|
|
6332
|
+
adg_cb_ios:{ name:"_",
|
|
6333
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
|
|
6334
|
+
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.",
|
|
6335
|
+
negatable:false },
|
|
6336
|
+
adg_cb_safari:{ name:"_",
|
|
6337
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
|
|
6338
|
+
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.",
|
|
6339
|
+
negatable:false },
|
|
6340
|
+
ubo_ext_any:{ name:"_",
|
|
6341
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#_-aka-noop",
|
|
6342
|
+
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.",
|
|
6343
|
+
negatable:false } };
|
|
6344
|
+
|
|
6345
|
+
var data$o = { adg_any:{ name:"object-subrequest",
|
|
6346
|
+
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).",
|
|
6347
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-subrequest-modifier",
|
|
6348
|
+
removed:true,
|
|
6349
|
+
removal_message:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid." } };
|
|
6350
|
+
|
|
6351
|
+
var data$n = { adg_any:{ name:"object",
|
|
6352
|
+
description:"The rule corresponds to browser plugins resources, e.g. Java or Flash",
|
|
6353
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-modifier" },
|
|
6354
|
+
abp_any:{ name:"object",
|
|
6355
|
+
description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
|
|
6356
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6357
|
+
ubo_any:{ name:"object",
|
|
6358
|
+
description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
|
|
6359
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
|
|
6360
|
+
|
|
6361
|
+
var data$m = { adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#other-modifier" },
|
|
6364
|
+
abp_any:{ name:"other",
|
|
6365
|
+
description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
|
|
6366
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6367
|
+
ubo_any:{ name:"other",
|
|
6368
|
+
description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
|
|
6369
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
|
|
6370
|
+
|
|
6371
|
+
var data$l = { adg_os_any:{ name:"permissions",
|
|
6372
|
+
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.",
|
|
6373
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier",
|
|
6374
|
+
version_added:"CoreLibs 1.11",
|
|
6375
|
+
conflicts:[ "domain",
|
|
6376
|
+
"important",
|
|
6377
|
+
"subdocument" ],
|
|
6378
|
+
inverse_conflicts:true,
|
|
6379
|
+
assignable:true,
|
|
6380
|
+
negatable:false,
|
|
6381
|
+
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 )=\\(\\)(\\\\, )?)+(?<!,)$" } };
|
|
6382
|
+
|
|
6383
|
+
var data$k = { adg_any:{ name:"ping",
|
|
6384
|
+
description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
|
|
6385
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#ping-modifier" },
|
|
6386
|
+
abp_any:{ name:"ping",
|
|
6387
|
+
description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
|
|
6388
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6389
|
+
ubo_any:{ name:"ping",
|
|
6390
|
+
description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
|
|
6391
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
|
|
6392
|
+
|
|
6393
|
+
var data$j = { ubo_ext_any:{ name:"popunder",
|
|
6394
|
+
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.",
|
|
6395
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#popunder",
|
|
6396
|
+
negatable:false,
|
|
6397
|
+
block_only:true } };
|
|
6398
|
+
|
|
6399
|
+
var data$i = { adg_any:{ name:"popup",
|
|
6400
|
+
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.",
|
|
6401
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#popup-modifier",
|
|
6402
|
+
negatable:false },
|
|
6403
|
+
abp_any:{ name:"popup",
|
|
6404
|
+
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.",
|
|
6405
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
6406
|
+
negatable:false },
|
|
6407
|
+
ubo_any:{ name:"popup",
|
|
6408
|
+
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.",
|
|
6409
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
6410
|
+
negatable:false } };
|
|
6411
|
+
|
|
6412
|
+
var data$h = { adg_os_any:{ name:"redirect-rule",
|
|
6413
|
+
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.",
|
|
6414
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
|
|
6415
|
+
conflicts:[ "domain",
|
|
6416
|
+
"to",
|
|
6417
|
+
"third-party",
|
|
6418
|
+
"popup",
|
|
6419
|
+
"match-case",
|
|
6420
|
+
"header",
|
|
6421
|
+
"first-party",
|
|
6422
|
+
"document",
|
|
6423
|
+
"image",
|
|
6424
|
+
"stylesheet",
|
|
6425
|
+
"script",
|
|
6426
|
+
"object",
|
|
6427
|
+
"font",
|
|
6428
|
+
"media",
|
|
6429
|
+
"subdocument",
|
|
6430
|
+
"ping",
|
|
6431
|
+
"xmlhttprequest",
|
|
6432
|
+
"websocket",
|
|
6433
|
+
"other",
|
|
6434
|
+
"webrtc",
|
|
6435
|
+
"important",
|
|
6436
|
+
"badfilter",
|
|
6437
|
+
"app" ],
|
|
6438
|
+
inverse_conflicts:true,
|
|
6439
|
+
assignable:true,
|
|
6440
|
+
negatable:false,
|
|
6441
|
+
value_optional:true,
|
|
6442
|
+
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 )?$" },
|
|
6443
|
+
adg_ext_any:{ name:"redirect-rule",
|
|
6444
|
+
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.",
|
|
6445
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
|
|
6446
|
+
conflicts:[ "domain",
|
|
6447
|
+
"to",
|
|
6448
|
+
"third-party",
|
|
6449
|
+
"popup",
|
|
6450
|
+
"match-case",
|
|
6451
|
+
"header",
|
|
6452
|
+
"first-party",
|
|
6453
|
+
"document",
|
|
6454
|
+
"image",
|
|
6455
|
+
"stylesheet",
|
|
6456
|
+
"script",
|
|
6457
|
+
"object",
|
|
6458
|
+
"font",
|
|
6459
|
+
"media",
|
|
6460
|
+
"subdocument",
|
|
6461
|
+
"ping",
|
|
6462
|
+
"xmlhttprequest",
|
|
6463
|
+
"websocket",
|
|
6464
|
+
"other",
|
|
6465
|
+
"webrtc",
|
|
6466
|
+
"important",
|
|
6467
|
+
"badfilter" ],
|
|
6468
|
+
inverse_conflicts:true,
|
|
6469
|
+
assignable:true,
|
|
6470
|
+
negatable:false,
|
|
6471
|
+
value_optional:true,
|
|
6472
|
+
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 )?$" },
|
|
6473
|
+
ubo_ext_any:{ name:"redirect-rule",
|
|
6474
|
+
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.",
|
|
6475
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect-rule",
|
|
6476
|
+
conflicts:[ "domain",
|
|
6477
|
+
"to",
|
|
6478
|
+
"third-party",
|
|
6479
|
+
"popup",
|
|
6480
|
+
"match-case",
|
|
6481
|
+
"header",
|
|
6482
|
+
"first-party",
|
|
6483
|
+
"document",
|
|
6484
|
+
"image",
|
|
6485
|
+
"stylesheet",
|
|
6486
|
+
"script",
|
|
6487
|
+
"object",
|
|
6488
|
+
"font",
|
|
6489
|
+
"media",
|
|
6490
|
+
"subdocument",
|
|
6491
|
+
"ping",
|
|
6492
|
+
"xmlhttprequest",
|
|
6493
|
+
"websocket",
|
|
6494
|
+
"other",
|
|
6495
|
+
"webrtc",
|
|
6496
|
+
"important",
|
|
6497
|
+
"badfilter" ],
|
|
6498
|
+
inverse_conflicts:true,
|
|
6499
|
+
assignable:true,
|
|
6500
|
+
negatable:false,
|
|
6501
|
+
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]+)?$" } };
|
|
6502
|
+
|
|
6503
|
+
var data$g = { adg_os_any:{ name:"redirect",
|
|
6504
|
+
description:"Used to redirect web requests to a local \"resource\".",
|
|
6505
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
|
|
6506
|
+
assignable:true,
|
|
6507
|
+
negatable:false,
|
|
6508
|
+
value_optional:true,
|
|
6509
|
+
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 )?$" },
|
|
6510
|
+
adg_ext_any:{ name:"redirect",
|
|
6511
|
+
description:"Used to redirect web requests to a local \"resource\".",
|
|
6512
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
|
|
6513
|
+
assignable:true,
|
|
6514
|
+
negatable:false,
|
|
6515
|
+
value_optional:true,
|
|
6516
|
+
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 )?$" },
|
|
6517
|
+
ubo_ext_any:{ name:"redirect",
|
|
6518
|
+
description:"Used to redirect web requests to a local \"resource\".",
|
|
6519
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect",
|
|
6520
|
+
assignable:true,
|
|
6521
|
+
negatable:false,
|
|
6522
|
+
value_optional:true,
|
|
6523
|
+
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]+)?$" } };
|
|
6524
|
+
|
|
6525
|
+
var data$f = { adg_os_any:{ name:"removeheader",
|
|
6526
|
+
description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
|
|
6527
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
|
|
6528
|
+
conflicts:[ "domain",
|
|
6529
|
+
"third-party",
|
|
6530
|
+
"first-party",
|
|
6531
|
+
"app",
|
|
6532
|
+
"important",
|
|
6533
|
+
"match-case",
|
|
6534
|
+
"document",
|
|
6535
|
+
"image",
|
|
6536
|
+
"stylesheet",
|
|
6537
|
+
"script",
|
|
6538
|
+
"object",
|
|
6539
|
+
"font",
|
|
6540
|
+
"media",
|
|
6541
|
+
"subdocument",
|
|
6542
|
+
"ping",
|
|
6543
|
+
"xmlhttpreqeust",
|
|
6544
|
+
"websocket",
|
|
6545
|
+
"other",
|
|
6546
|
+
"webrtc" ],
|
|
6547
|
+
inverse_conflicts:true,
|
|
6548
|
+
assignable:true,
|
|
6549
|
+
negatable:false,
|
|
6550
|
+
value_optional:true,
|
|
6551
|
+
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 $" },
|
|
6552
|
+
adg_ext_any:{ name:"removeheader",
|
|
6553
|
+
description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
|
|
6554
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
|
|
6555
|
+
conflicts:[ "domain",
|
|
6556
|
+
"third-party",
|
|
6557
|
+
"first-party",
|
|
6558
|
+
"app",
|
|
6559
|
+
"important",
|
|
6560
|
+
"match-case",
|
|
6561
|
+
"document",
|
|
6562
|
+
"image",
|
|
6563
|
+
"stylesheet",
|
|
6564
|
+
"script",
|
|
6565
|
+
"object",
|
|
6566
|
+
"font",
|
|
6567
|
+
"media",
|
|
6568
|
+
"subdocument",
|
|
6569
|
+
"ping",
|
|
6570
|
+
"xmlhttpreqeust",
|
|
6571
|
+
"websocket",
|
|
6572
|
+
"other",
|
|
6573
|
+
"webrtc" ],
|
|
6574
|
+
inverse_conflicts:true,
|
|
6575
|
+
assignable:true,
|
|
6576
|
+
negatable:false,
|
|
6577
|
+
value_optional:true,
|
|
6578
|
+
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 $" } };
|
|
6579
|
+
|
|
6580
|
+
var data$e = { adg_os_any:{ name:"removeparam",
|
|
6581
|
+
description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
|
|
6582
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
|
|
6583
|
+
assignable:true,
|
|
6584
|
+
negatable:false,
|
|
6585
|
+
value_optional:true,
|
|
6586
|
+
value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
|
|
6587
|
+
adg_ext_any:{ name:"removeparam",
|
|
6588
|
+
description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
|
|
6589
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeparam-modifier",
|
|
6590
|
+
assignable:true,
|
|
6591
|
+
negatable:false,
|
|
6592
|
+
value_optional:true,
|
|
6593
|
+
value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" },
|
|
6594
|
+
ubo_ext_any:{ name:"removeparam",
|
|
6595
|
+
description:"Rules with the `$removeparam` modifier are intended to strip query parameters from requests' URLs.",
|
|
6596
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#removeparam",
|
|
6597
|
+
assignable:true,
|
|
6598
|
+
negatable:false,
|
|
6599
|
+
value_format:"/^(?!.*([^\\\\](,|\\$|\\/))).*$/" } };
|
|
6600
|
+
|
|
6601
|
+
var data$d = { adg_os_any:{ name:"replace",
|
|
6602
|
+
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.",
|
|
6603
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
|
|
6604
|
+
conflicts:[ "app",
|
|
6605
|
+
"domain",
|
|
6606
|
+
"document",
|
|
6607
|
+
"subdocument",
|
|
6608
|
+
"script",
|
|
6609
|
+
"stylesheet",
|
|
6610
|
+
"other",
|
|
6611
|
+
"xmlhttprequest",
|
|
6612
|
+
"first-party",
|
|
6613
|
+
"third-party",
|
|
6614
|
+
"important",
|
|
6615
|
+
"badfilter" ],
|
|
6616
|
+
inverse_conflicts:true,
|
|
6617
|
+
assignable:true,
|
|
6618
|
+
negatable:false,
|
|
6619
|
+
value_format:"/^\\/.+\\/.*\\/$/" },
|
|
6620
|
+
adg_ext_firefox:{ name:"replace",
|
|
6621
|
+
description:"This modifier completely changes the rule behavior.\nIf it is applied, the rule will not block the request. The response is going to be modified instead.",
|
|
6622
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
|
|
6623
|
+
conflicts:[ "domain",
|
|
6624
|
+
"document",
|
|
6625
|
+
"subdocument",
|
|
6626
|
+
"script",
|
|
6627
|
+
"stylesheet",
|
|
6628
|
+
"other",
|
|
6629
|
+
"xmlhttprequest",
|
|
6630
|
+
"first-party",
|
|
6631
|
+
"third-party",
|
|
6632
|
+
"important",
|
|
6633
|
+
"badfilter" ],
|
|
6634
|
+
inverse_conflicts:true,
|
|
6635
|
+
assignable:true,
|
|
6636
|
+
negatable:false,
|
|
6637
|
+
value_format:"/^\\/.+\\/.*\\/$/" } };
|
|
6638
|
+
|
|
6639
|
+
var data$c = { adg_any:{ name:"script",
|
|
6640
|
+
description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
|
|
6641
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#script-modifier" },
|
|
6642
|
+
abp_any:{ name:"script",
|
|
6643
|
+
description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
|
|
6644
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6645
|
+
ubo_any:{ name:"script",
|
|
6646
|
+
description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
|
|
6647
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
|
|
6648
|
+
|
|
6649
|
+
var data$b = { adg_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://adguard.app/kb/general/ad-filtering/create-own-filters/#specifichide-modifier",
|
|
6653
|
+
conflicts:[ "domain",
|
|
6654
|
+
"genericblock",
|
|
6655
|
+
"urlblock",
|
|
6656
|
+
"extension",
|
|
6657
|
+
"jsinject",
|
|
6658
|
+
"content",
|
|
6659
|
+
"xmlhttprequest",
|
|
6660
|
+
"badfilter" ],
|
|
6661
|
+
inverse_conflicts:true,
|
|
6662
|
+
negatable:false,
|
|
6663
|
+
exception_only:true },
|
|
6664
|
+
ubo_any:{ name:"specifichide",
|
|
6665
|
+
aliases:[ "shide" ],
|
|
6666
|
+
description:"Disables all specific element hiding and CSS rules, but not general ones.\nHas an opposite effect to `$generichide`.",
|
|
6667
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#specifichide",
|
|
6668
|
+
conflicts:[ "domain",
|
|
6669
|
+
"badfilter" ],
|
|
6670
|
+
inverse_conflicts:true,
|
|
6671
|
+
negatable:false,
|
|
6672
|
+
exception_only:true } };
|
|
6673
|
+
|
|
6674
|
+
var data$a = { adg_os_any:{ name:"stealth",
|
|
6675
|
+
description:"Disables the Stealth Mode module for all corresponding pages and requests.",
|
|
6676
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
|
|
6677
|
+
assignable:true,
|
|
6678
|
+
negatable:false,
|
|
6679
|
+
exception_only:true,
|
|
6680
|
+
value_optional:true,
|
|
6681
|
+
value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n 3p-cache|\n 3p-auth|\n webrtc|\n push|\n location|\n flash|\n java|\n referrer|\n useragent|\n ip|\n xclientdata|\n dpi|\n \\|?\n )\\b)+(?<!\\|)$" },
|
|
6682
|
+
adg_ext_chrome:{ name:"stealth",
|
|
6683
|
+
description:"Disables the Stealth Mode module for all corresponding pages and requests.",
|
|
6684
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
|
|
6685
|
+
assignable:true,
|
|
6686
|
+
negatable:false,
|
|
6687
|
+
exception_only:true,
|
|
6688
|
+
value_optional:true,
|
|
6689
|
+
value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n xclientdata\n |\\|?\n )\\b)+(?<!\\|)$" },
|
|
6690
|
+
adg_ext_firefox:{ name:"stealth",
|
|
6691
|
+
description:"Disables the Stealth Mode module for all corresponding pages and requests.",
|
|
6692
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
|
|
6693
|
+
assignable:true,
|
|
6694
|
+
negatable:false,
|
|
6695
|
+
exception_only:true,
|
|
6696
|
+
value_optional:true,
|
|
6697
|
+
value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
|
|
6698
|
+
adg_ext_opera:{ name:"stealth",
|
|
6699
|
+
description:"Disables the Stealth Mode module for all corresponding pages and requests.",
|
|
6700
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
|
|
6701
|
+
assignable:true,
|
|
6702
|
+
negatable:false,
|
|
6703
|
+
exception_only:true,
|
|
6704
|
+
value_optional:true,
|
|
6705
|
+
value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" },
|
|
6706
|
+
adg_ext_edge:{ name:"stealth",
|
|
6707
|
+
description:"Disables the Stealth Mode module for all corresponding pages and requests.",
|
|
6708
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stealth-modifier",
|
|
6709
|
+
assignable:true,
|
|
6710
|
+
negatable:false,
|
|
6711
|
+
exception_only:true,
|
|
6712
|
+
value_optional:true,
|
|
6713
|
+
value_format:"(?x)\n ^(?!\\|)\\b(?:(\n searchqueries|\n donottrack|\n 3p-cookie|\n 1p-cookie|\n webrtc|\n referrer|\n |\\|?\n )\\b)+(?<!\\|)$" } };
|
|
6714
|
+
|
|
6715
|
+
var data$9 = { ubo_any:{ name:"strict1p",
|
|
6716
|
+
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.",
|
|
6717
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#strict1p" } };
|
|
6718
|
+
|
|
6719
|
+
var data$8 = { ubo_any:{ name:"strict3p",
|
|
6720
|
+
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.",
|
|
6721
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#strict3p" } };
|
|
6722
|
+
|
|
6723
|
+
var data$7 = { adg_any:{ name:"stylesheet",
|
|
6724
|
+
description:"The rule corresponds to CSS files requests.",
|
|
6725
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#stylesheet-modifier" },
|
|
6726
|
+
abp_any:{ name:"stylesheet",
|
|
6727
|
+
description:"The rule corresponds to CSS files requests.",
|
|
6728
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6729
|
+
ubo_any:{ name:"stylesheet",
|
|
6730
|
+
aliases:[ "css" ],
|
|
6731
|
+
description:"The rule corresponds to CSS files requests.",
|
|
6732
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#css" } };
|
|
6733
|
+
|
|
6734
|
+
var data$6 = { adg_any:{ name:"subdocument",
|
|
6735
|
+
description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
|
|
6736
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#subdocument-modifier" },
|
|
6737
|
+
abp_any:{ name:"subdocument",
|
|
6738
|
+
description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
|
|
6739
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6740
|
+
ubo_any:{ name:"subdocument",
|
|
6741
|
+
aliases:[ "frame" ],
|
|
6742
|
+
description:"The rule corresponds to requests for built-in pages — HTML tags frame and iframe.",
|
|
6743
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#frame" } };
|
|
6744
|
+
|
|
6745
|
+
var data$5 = { adg_any:{ name:"third-party",
|
|
6746
|
+
aliases:[ "3p" ],
|
|
6747
|
+
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.",
|
|
6748
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#third-party-modifier" },
|
|
6749
|
+
ubo_any:{ name:"3p",
|
|
6750
|
+
aliases:[ "third-party" ],
|
|
6751
|
+
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.",
|
|
6752
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#3p" },
|
|
6753
|
+
abp_any:{ name:"third-party",
|
|
6754
|
+
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.",
|
|
6755
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#party-requests" } };
|
|
6756
|
+
|
|
6757
|
+
var data$4 = { ubo_ext_any:{ name:"to",
|
|
6758
|
+
description:"The main motivation of this option is\nto give static network filtering engine an equivalent of DNR's `requestDomains` and `excludedRequestDomains`.",
|
|
6759
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#to",
|
|
6760
|
+
assignable:true,
|
|
6761
|
+
negatable:false,
|
|
6762
|
+
value_format:"pipe_separated_domains" } };
|
|
6763
|
+
|
|
6764
|
+
var data$3 = { adg_any:{ name:"urlblock",
|
|
6765
|
+
description:"Disables blocking of all requests sent from the pages matching the rule.",
|
|
6766
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#urlblock-modifier",
|
|
6767
|
+
conflicts:[ "domain",
|
|
6768
|
+
"specifichide",
|
|
6769
|
+
"generichide",
|
|
6770
|
+
"elemhide",
|
|
6771
|
+
"extension",
|
|
6772
|
+
"jsinject",
|
|
6773
|
+
"content",
|
|
6774
|
+
"badfilter" ],
|
|
6775
|
+
inverse_conflicts:true,
|
|
6776
|
+
negatable:false,
|
|
6777
|
+
exception_only:true } };
|
|
6778
|
+
|
|
6779
|
+
var data$2 = { adg_any:{ name:"webrtc",
|
|
6780
|
+
description:"The rule applies only to WebRTC connections.",
|
|
6781
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#webrtc-modifier",
|
|
6782
|
+
removed:true,
|
|
6783
|
+
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)." },
|
|
6784
|
+
ubo_any:{ name:"webrtc",
|
|
6785
|
+
description:"The rule applies only to WebRTC connections.",
|
|
6786
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax",
|
|
6787
|
+
removed:true,
|
|
6788
|
+
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-)." },
|
|
6789
|
+
abp_any:{ name:"webrtc",
|
|
6790
|
+
description:"The rule applies only to WebRTC connections.",
|
|
6791
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
|
|
6792
|
+
version_added:"1.13.3" } };
|
|
6793
|
+
|
|
6794
|
+
var data$1 = { adg_os_any:{ name:"websocket",
|
|
6795
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6796
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
|
|
6797
|
+
adg_ext_any:{ name:"websocket",
|
|
6798
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6799
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
|
|
6800
|
+
adg_cb_ios:{ name:"websocket",
|
|
6801
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6802
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
|
|
6803
|
+
adg_cb_safari:{ name:"websocket",
|
|
6804
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6805
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#websocket-modifier" },
|
|
6806
|
+
abp_ext_any:{ name:"websocket",
|
|
6807
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6808
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6809
|
+
ubo_ext_any:{ name:"websocket",
|
|
6810
|
+
description:"The rule applies only to WebSocket connections.",
|
|
6811
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
|
|
6812
|
+
|
|
6813
|
+
var data = { adg_any:{ name:"xmlhttprequest",
|
|
6814
|
+
aliases:[ "xhr" ],
|
|
6815
|
+
description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
|
|
6816
|
+
docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#xmlhttprequest-modifier" },
|
|
6817
|
+
abp_any:{ name:"xmlhttprequest",
|
|
6818
|
+
description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
|
|
6819
|
+
docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" },
|
|
6820
|
+
ubo_any:{ name:"xhr",
|
|
6821
|
+
aliases:[ "xmlhttprequest" ],
|
|
6822
|
+
description:"The rule applies only to ajax requests (requests sent via javascript object XMLHttpRequest).",
|
|
6823
|
+
docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#xhr" } };
|
|
6824
|
+
|
|
6825
|
+
/**
|
|
6826
|
+
* @file Raw compatibility tables data reexport from yaml files.
|
|
6827
|
+
*
|
|
6828
|
+
* '@ts-nocheck' is used here once instead of adding @ts-ignore for each import.
|
|
6829
|
+
*/
|
|
6830
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
6831
|
+
// @ts-nocheck
|
|
6832
|
+
// Please keep imports and exports in alphabetical order
|
|
6833
|
+
const rawModifiersData = {
|
|
6834
|
+
all: data$T,
|
|
6835
|
+
app: data$S,
|
|
6836
|
+
badfilter: data$R,
|
|
6837
|
+
cname: data$Q,
|
|
6838
|
+
content: data$P,
|
|
6839
|
+
cookie: data$O,
|
|
6840
|
+
csp: data$N,
|
|
6841
|
+
denyallow: data$M,
|
|
6842
|
+
document: data$L,
|
|
6843
|
+
domain: data$K,
|
|
6844
|
+
elemhide: data$J,
|
|
6845
|
+
empty: data$I,
|
|
6846
|
+
firstParty: data$H,
|
|
6847
|
+
extension: data$G,
|
|
6848
|
+
font: data$F,
|
|
6849
|
+
genericblock: data$E,
|
|
6850
|
+
generichide: data$D,
|
|
6851
|
+
header: data$C,
|
|
6852
|
+
hls: data$B,
|
|
6853
|
+
image: data$A,
|
|
6854
|
+
important: data$z,
|
|
6855
|
+
inlineFont: data$y,
|
|
6856
|
+
inlineScript: data$x,
|
|
6857
|
+
jsinject: data$w,
|
|
6858
|
+
jsonprune: data$v,
|
|
6859
|
+
matchCase: data$u,
|
|
6860
|
+
media: data$t,
|
|
6861
|
+
method: data$s,
|
|
6862
|
+
mp4: data$r,
|
|
6863
|
+
network: data$q,
|
|
6864
|
+
noop: data$p,
|
|
6865
|
+
objectSubrequest: data$o,
|
|
6866
|
+
object: data$n,
|
|
6867
|
+
other: data$m,
|
|
6868
|
+
permissions: data$l,
|
|
6869
|
+
ping: data$k,
|
|
6870
|
+
popunder: data$j,
|
|
6871
|
+
popup: data$i,
|
|
6872
|
+
redirectRule: data$h,
|
|
6873
|
+
redirect: data$g,
|
|
6874
|
+
removeheader: data$f,
|
|
6875
|
+
removeparam: data$e,
|
|
6876
|
+
replace: data$d,
|
|
6877
|
+
script: data$c,
|
|
6878
|
+
specifichide: data$b,
|
|
6879
|
+
stealth: data$a,
|
|
6880
|
+
strict1p: data$9,
|
|
6881
|
+
strict3p: data$8,
|
|
6882
|
+
stylesheet: data$7,
|
|
6883
|
+
subdocument: data$6,
|
|
6884
|
+
thirdParty: data$5,
|
|
6885
|
+
to: data$4,
|
|
6886
|
+
urlblock: data$3,
|
|
6887
|
+
webrtc: data$2,
|
|
6888
|
+
websocket: data$1,
|
|
6889
|
+
xmlhttprequest: data,
|
|
6890
|
+
};
|
|
6891
|
+
|
|
6892
|
+
/**
|
|
6893
|
+
* @file Compatibility tables types.
|
|
6894
|
+
*/
|
|
6895
|
+
/**
|
|
6896
|
+
* List of properties names for modifier data.
|
|
6897
|
+
*
|
|
6898
|
+
* @see {@link https://github.com/AdguardTeam/tsurlfilter/blob/master/packages/agtree/src/compatibility-tables/modifiers/README.md#file-structure}
|
|
6899
|
+
*/
|
|
6900
|
+
var SpecificKey;
|
|
6901
|
+
(function (SpecificKey) {
|
|
6902
|
+
SpecificKey["Name"] = "name";
|
|
6903
|
+
SpecificKey["Aliases"] = "aliases";
|
|
6904
|
+
SpecificKey["Description"] = "description";
|
|
6905
|
+
SpecificKey["Docs"] = "docs";
|
|
6906
|
+
SpecificKey["Deprecated"] = "deprecated";
|
|
6907
|
+
SpecificKey["DeprecationMessage"] = "deprecation_message";
|
|
6908
|
+
SpecificKey["Removed"] = "removed";
|
|
6909
|
+
SpecificKey["RemovalMessage"] = "removal_message";
|
|
6910
|
+
SpecificKey["Conflicts"] = "conflicts";
|
|
6911
|
+
SpecificKey["InverseConflicts"] = "inverse_conflicts";
|
|
6912
|
+
SpecificKey["Assignable"] = "assignable";
|
|
6913
|
+
SpecificKey["Negatable"] = "negatable";
|
|
6914
|
+
SpecificKey["BlockOnly"] = "block_only";
|
|
6915
|
+
SpecificKey["ExceptionOnly"] = "exception_only";
|
|
6916
|
+
// TODO: consider removing this field
|
|
6917
|
+
// and handle whether the value is optional by `value_format`. AG-24028
|
|
6918
|
+
SpecificKey["ValueOptional"] = "value_optional";
|
|
6919
|
+
SpecificKey["ValueFormat"] = "value_format";
|
|
6920
|
+
// TODO: following fields should be handled later
|
|
6921
|
+
// VersionAdded = 'version_added',
|
|
6922
|
+
// VersionRemoved = 'version_removed',
|
|
6923
|
+
})(SpecificKey || (SpecificKey = {}));
|
|
6924
|
+
|
|
6925
|
+
/**
|
|
6926
|
+
* Prepares specific platform modifier data from raw modifiers data —
|
|
6927
|
+
* sets [default values] to properties that are not defined in raw data.
|
|
6928
|
+
*
|
|
6929
|
+
* [default values]: ./modifiers/README.md "Check File structure table for default values."
|
|
6930
|
+
*
|
|
6931
|
+
* @param blockerId Key in ModifierData, i.e. 'adg_os_any', 'ubo_ext_any', etc.
|
|
6932
|
+
* @param rawModifierData Specific platform modifier data from raw modifiers data.
|
|
6933
|
+
*
|
|
6934
|
+
* @returns Prepared specific platform modifier data where properties cannot be undefined.
|
|
6935
|
+
*/
|
|
6936
|
+
const prepareBlockerData = (blockerId, rawModifierData) => {
|
|
6937
|
+
const rawData = rawModifierData[blockerId];
|
|
6938
|
+
const blockerData = {
|
|
6939
|
+
[SpecificKey.Name]: rawData[SpecificKey.Name],
|
|
6940
|
+
[SpecificKey.Aliases]: rawData[SpecificKey.Aliases] || null,
|
|
6941
|
+
[SpecificKey.Description]: rawData[SpecificKey.Description] || null,
|
|
6942
|
+
[SpecificKey.Docs]: rawData[SpecificKey.Docs] || null,
|
|
6943
|
+
[SpecificKey.Deprecated]: rawData[SpecificKey.Deprecated] || false,
|
|
6944
|
+
[SpecificKey.DeprecationMessage]: rawData[SpecificKey.DeprecationMessage] || null,
|
|
6945
|
+
[SpecificKey.Removed]: rawData[SpecificKey.Removed] || false,
|
|
6946
|
+
[SpecificKey.RemovalMessage]: rawData[SpecificKey.RemovalMessage] || null,
|
|
6947
|
+
[SpecificKey.Conflicts]: rawData[SpecificKey.Conflicts] || null,
|
|
6948
|
+
[SpecificKey.InverseConflicts]: rawData[SpecificKey.InverseConflicts] || false,
|
|
6949
|
+
[SpecificKey.Assignable]: rawData[SpecificKey.Assignable] || false,
|
|
6950
|
+
// 'negatable' should be checked whether it is undefined or not
|
|
6951
|
+
// because if it is 'false', default value 'true' will override it
|
|
6952
|
+
[SpecificKey.Negatable]: isUndefined(rawData[SpecificKey.Negatable])
|
|
6953
|
+
? true
|
|
6954
|
+
: rawData[SpecificKey.Negatable],
|
|
6955
|
+
[SpecificKey.BlockOnly]: rawData[SpecificKey.BlockOnly] || false,
|
|
6956
|
+
[SpecificKey.ExceptionOnly]: rawData[SpecificKey.ExceptionOnly] || false,
|
|
6957
|
+
[SpecificKey.ValueOptional]: rawData[SpecificKey.ValueOptional] || false,
|
|
6958
|
+
[SpecificKey.ValueFormat]: rawData[SpecificKey.ValueFormat] || null,
|
|
6959
|
+
};
|
|
6960
|
+
return blockerData;
|
|
6961
|
+
};
|
|
6962
|
+
/**
|
|
6963
|
+
* Prepares raw modifiers data into a data map with default values for properties
|
|
6964
|
+
* that are not defined in raw data.
|
|
6965
|
+
*
|
|
6966
|
+
* @returns Map of parsed and prepared modifiers data.
|
|
6967
|
+
*/
|
|
6968
|
+
const getModifiersData = () => {
|
|
6969
|
+
const dataMap = new Map();
|
|
6970
|
+
Object.keys(rawModifiersData).forEach((modifierId) => {
|
|
6971
|
+
const rawModifierData = rawModifiersData[modifierId];
|
|
6972
|
+
const modifierData = {};
|
|
6973
|
+
Object.keys(rawModifierData).forEach((blockerId) => {
|
|
6974
|
+
modifierData[blockerId] = prepareBlockerData(blockerId, rawModifierData);
|
|
6975
|
+
});
|
|
6976
|
+
dataMap.set(modifierId, modifierData);
|
|
6977
|
+
});
|
|
6978
|
+
return dataMap;
|
|
6979
|
+
};
|
|
6980
|
+
|
|
6981
|
+
const INVALID_ERROR_PREFIX = {
|
|
6982
|
+
NOT_EXISTENT: 'Not existent modifier',
|
|
6983
|
+
NOT_SUPPORTED: 'The adblocker does not support the modifier',
|
|
6984
|
+
REMOVED: 'Removed and no longer supported modifier',
|
|
6985
|
+
BLOCK_ONLY: 'Only blocking rules may contain the modifier',
|
|
6986
|
+
EXCEPTION_ONLY: 'Only exception rules may contain the modifier',
|
|
6987
|
+
NOT_NEGATABLE: 'Not negatable modifier',
|
|
6988
|
+
VALUE_REQUIRED: 'Value is required for the modifier',
|
|
6989
|
+
VALUE_FORBIDDEN: 'Value is not allowed for the modifier',
|
|
6990
|
+
INVALID_NOOP: 'Invalid noop modifier',
|
|
6991
|
+
};
|
|
6992
|
+
|
|
6993
|
+
/**
|
|
6994
|
+
* @file Validator for modifiers.
|
|
6995
|
+
*/
|
|
6996
|
+
const BLOCKER_PREFIX = {
|
|
6997
|
+
[AdblockSyntax.Adg]: 'adg_',
|
|
6998
|
+
[AdblockSyntax.Ubo]: 'ubo_',
|
|
6999
|
+
[AdblockSyntax.Abp]: 'abp_',
|
|
7000
|
+
};
|
|
7001
|
+
/**
|
|
7002
|
+
* Collects names and aliases for all supported modifiers.
|
|
7003
|
+
* Deprecated and removed modifiers are included because they are known and existent
|
|
7004
|
+
* and they should be validated properly.
|
|
7005
|
+
*
|
|
7006
|
+
* @param dataMap Parsed all modifiers data.
|
|
7007
|
+
*
|
|
7008
|
+
* @returns Set of all modifier names (and their aliases).
|
|
7009
|
+
*/
|
|
7010
|
+
const getAllModifierNames = (dataMap) => {
|
|
7011
|
+
const names = new Set();
|
|
7012
|
+
dataMap.forEach((modifierData) => {
|
|
7013
|
+
Object.keys(modifierData).forEach((blockerId) => {
|
|
7014
|
+
const blockerData = modifierData[blockerId];
|
|
7015
|
+
names.add(blockerData.name);
|
|
7016
|
+
if (!blockerData.aliases) {
|
|
7017
|
+
return;
|
|
7018
|
+
}
|
|
7019
|
+
blockerData.aliases.forEach((alias) => names.add(alias));
|
|
7020
|
+
});
|
|
7021
|
+
});
|
|
7022
|
+
return names;
|
|
7023
|
+
};
|
|
7024
|
+
/**
|
|
7025
|
+
* Returns modifier data for given modifier name and adblocker.
|
|
7026
|
+
*
|
|
7027
|
+
* @param modifiersData Parsed all modifiers data map.
|
|
7028
|
+
* @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
|
|
7029
|
+
* @param modifierName Modifier name.
|
|
7030
|
+
*
|
|
7031
|
+
* @returns Modifier data or `null` if not found.
|
|
7032
|
+
*/
|
|
7033
|
+
const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
|
|
7034
|
+
let specificBlockerData = null;
|
|
7035
|
+
modifiersData.forEach((modifierData) => {
|
|
7036
|
+
Object.keys(modifierData).forEach((blockerId) => {
|
|
7037
|
+
const blockerData = modifierData[blockerId];
|
|
7038
|
+
if (blockerData.name === modifierName
|
|
7039
|
+
|| (blockerData.aliases && blockerData.aliases.includes(modifierName))) {
|
|
7040
|
+
// modifier is found by name or alias
|
|
7041
|
+
// so its support by specific adblocker should be checked
|
|
7042
|
+
if (blockerId.startsWith(blockerPrefix)) {
|
|
7043
|
+
// so maybe other data objects should be checked as well (not sure)
|
|
7044
|
+
specificBlockerData = blockerData;
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
});
|
|
7048
|
+
});
|
|
7049
|
+
return specificBlockerData;
|
|
7050
|
+
};
|
|
7051
|
+
/**
|
|
7052
|
+
* Returns invalid validation result with given error message.
|
|
7053
|
+
*
|
|
7054
|
+
* @param error Error message.
|
|
7055
|
+
*
|
|
7056
|
+
* @returns Validation result `{ ok: false, error }`.
|
|
7057
|
+
*/
|
|
7058
|
+
const getInvalidValidationResult = (error) => {
|
|
7059
|
+
return {
|
|
7060
|
+
ok: false,
|
|
7061
|
+
error,
|
|
7062
|
+
};
|
|
7063
|
+
};
|
|
7064
|
+
/**
|
|
7065
|
+
* Fully checks whether the given `modifier` valid for given blocker `syntax`:
|
|
7066
|
+
* is it supported by the blocker, deprecated, assignable, negatable, etc.
|
|
7067
|
+
*
|
|
7068
|
+
* @param modifiersData Parsed all modifiers data map.
|
|
7069
|
+
* @param syntax Adblock syntax to check the modifier for.
|
|
7070
|
+
* 'Common' is not supported, it should be specific — 'AdGuard', 'uBlockOrigin', or 'AdblockPlus'.
|
|
7071
|
+
* @param modifier Parsed modifier AST node.
|
|
7072
|
+
* @param isException Whether the modifier is used in exception rule.
|
|
7073
|
+
* Needed to check whether the modifier is allowed only in blocking or exception rules.
|
|
7074
|
+
*
|
|
7075
|
+
* @returns Result of modifier validation.
|
|
7076
|
+
*/
|
|
7077
|
+
const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException) => {
|
|
7078
|
+
if (syntax === AdblockSyntax.Common) {
|
|
7079
|
+
throw new Error(`Syntax should be specific, '${AdblockSyntax.Common}' is not supported`);
|
|
7080
|
+
}
|
|
7081
|
+
const modifierName = modifier.modifier.value;
|
|
7082
|
+
const blockerPrefix = BLOCKER_PREFIX[syntax];
|
|
7083
|
+
if (!blockerPrefix) {
|
|
7084
|
+
throw new Error(`Unknown syntax: ${syntax}`);
|
|
7085
|
+
}
|
|
7086
|
+
// needed for validation of negation, assignment, etc.
|
|
7087
|
+
const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifierName);
|
|
7088
|
+
// if no specific blocker data is found
|
|
7089
|
+
if (!specificBlockerData) {
|
|
7090
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_SUPPORTED}: '${modifierName}'`);
|
|
7091
|
+
}
|
|
7092
|
+
// e.g. 'object-subrequest'
|
|
7093
|
+
if (specificBlockerData.removed) {
|
|
7094
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.REMOVED}: '${modifierName}'`);
|
|
7095
|
+
}
|
|
7096
|
+
if (specificBlockerData.deprecated) {
|
|
7097
|
+
if (!specificBlockerData.deprecation_message) {
|
|
7098
|
+
throw new Error('Deprecation notice is required for deprecated modifier');
|
|
7099
|
+
}
|
|
7100
|
+
return {
|
|
7101
|
+
ok: true,
|
|
7102
|
+
warn: specificBlockerData.deprecation_message,
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
if (specificBlockerData.block_only && isException) {
|
|
7106
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.BLOCK_ONLY}: '${modifierName}'`);
|
|
7107
|
+
}
|
|
7108
|
+
if (specificBlockerData.exception_only && !isException) {
|
|
7109
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.EXCEPTION_ONLY}: '${modifierName}'`);
|
|
7110
|
+
}
|
|
7111
|
+
// e.g. '~domain=example.com'
|
|
7112
|
+
if (!specificBlockerData.negatable && modifier.exception) {
|
|
7113
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_NEGATABLE}: '${modifierName}'`);
|
|
7114
|
+
}
|
|
7115
|
+
// e.g. 'domain'
|
|
7116
|
+
if (specificBlockerData.assignable) {
|
|
7117
|
+
/**
|
|
7118
|
+
* Some assignable modifiers can be used without a value,
|
|
7119
|
+
* e.g. '@@||example.com^$cookie'.
|
|
7120
|
+
*/
|
|
7121
|
+
if (!modifier.value
|
|
7122
|
+
// value should be specified if it is not optional
|
|
7123
|
+
&& !specificBlockerData.value_optional) {
|
|
7124
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_REQUIRED}: '${modifierName}'`);
|
|
7125
|
+
}
|
|
7126
|
+
/**
|
|
7127
|
+
* TODO: consider to return `{ ok: true, warn: 'Modifier value may be specified' }` (???)
|
|
7128
|
+
* for $stealth modifier without a value
|
|
7129
|
+
* but only after the extension will support value for $stealth:
|
|
7130
|
+
* https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2107
|
|
7131
|
+
*/
|
|
7132
|
+
}
|
|
7133
|
+
else if (modifier?.value) {
|
|
7134
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.VALUE_FORBIDDEN}: '${modifierName}'`);
|
|
7135
|
+
}
|
|
7136
|
+
return { ok: true };
|
|
7137
|
+
};
|
|
7138
|
+
/**
|
|
7139
|
+
* Returns documentation URL for given modifier and adblocker.
|
|
7140
|
+
*
|
|
7141
|
+
* @param modifiersData Parsed all modifiers data map.
|
|
7142
|
+
* @param blockerPrefix Prefix of the adblocker, e.g. 'adg_', 'ubo_', or 'abp_'.
|
|
7143
|
+
* @param modifier Parsed modifier AST node.
|
|
7144
|
+
*
|
|
7145
|
+
* @returns Documentation URL or `null` if not found.
|
|
7146
|
+
*/
|
|
7147
|
+
const getBlockerDocumentationLink = (modifiersData, blockerPrefix, modifier) => {
|
|
7148
|
+
const specificBlockerData = getSpecificBlockerData(modifiersData, blockerPrefix, modifier.modifier.value);
|
|
7149
|
+
return specificBlockerData?.docs || null;
|
|
7150
|
+
};
|
|
7151
|
+
/**
|
|
7152
|
+
* Validates the noop modifier (i.e. only underscores).
|
|
7153
|
+
*
|
|
7154
|
+
* @param value Value of the modifier.
|
|
7155
|
+
*
|
|
7156
|
+
* @returns True if the modifier is valid, false otherwise.
|
|
7157
|
+
*/
|
|
7158
|
+
const isValidNoopModifier = (value) => {
|
|
7159
|
+
return value.split('').every((char) => char === UNDERSCORE);
|
|
7160
|
+
};
|
|
7161
|
+
/**
|
|
7162
|
+
* Modifier validator class.
|
|
7163
|
+
*/
|
|
7164
|
+
class ModifierValidator {
|
|
7165
|
+
/**
|
|
7166
|
+
* Map of all modifiers data parsed from yaml files.
|
|
7167
|
+
*/
|
|
7168
|
+
modifiersData;
|
|
7169
|
+
/**
|
|
7170
|
+
* List of all modifier names for any adblocker.
|
|
7171
|
+
*
|
|
7172
|
+
* Please note that **deprecated** modifiers are **included** as well.
|
|
7173
|
+
*/
|
|
7174
|
+
allModifierNames;
|
|
7175
|
+
constructor() {
|
|
7176
|
+
// data map based on yaml files
|
|
7177
|
+
this.modifiersData = getModifiersData();
|
|
7178
|
+
this.allModifierNames = getAllModifierNames(this.modifiersData);
|
|
7179
|
+
}
|
|
7180
|
+
/**
|
|
7181
|
+
* Simply checks whether the modifier exists in any adblocker.
|
|
7182
|
+
*
|
|
7183
|
+
* **Deprecated** and **removed** modifiers are considered as **existent**.
|
|
7184
|
+
*
|
|
7185
|
+
* @param modifier Already parsed modifier AST node.
|
|
7186
|
+
*
|
|
7187
|
+
* @returns True if modifier exists, false otherwise.
|
|
7188
|
+
*/
|
|
7189
|
+
exists = (modifier) => {
|
|
7190
|
+
return this.allModifierNames.has(modifier.modifier.value);
|
|
7191
|
+
};
|
|
7192
|
+
/**
|
|
7193
|
+
* Checks whether the given `modifier` is valid for specified `syntax`.
|
|
7194
|
+
*
|
|
7195
|
+
* For `Common` syntax it simply checks whether the modifier exists.
|
|
7196
|
+
* For specific syntax the validation is more complex —
|
|
7197
|
+
* deprecated, assignable, negatable and other requirements are checked.
|
|
7198
|
+
*
|
|
7199
|
+
* @param syntax Adblock syntax to check the modifier for.
|
|
7200
|
+
* @param rawModifier Modifier AST node.
|
|
7201
|
+
* @param isException Whether the modifier is used in exception rule, default to false.
|
|
7202
|
+
* Needed to check whether the modifier is allowed only in blocking or exception rules.
|
|
7203
|
+
*
|
|
7204
|
+
* @returns Result of modifier validation.
|
|
7205
|
+
*/
|
|
7206
|
+
validate = (syntax, rawModifier, isException = false) => {
|
|
7207
|
+
const modifier = cloneDeep(rawModifier);
|
|
7208
|
+
// special case: handle noop modifier which may be used as multiple underscores (not just one)
|
|
7209
|
+
// https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
|
|
7210
|
+
if (modifier.modifier.value.startsWith(UNDERSCORE)) {
|
|
7211
|
+
// check whether the modifier value contains something else besides underscores
|
|
7212
|
+
if (!isValidNoopModifier(modifier.modifier.value)) {
|
|
7213
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.INVALID_NOOP}: '${modifier.modifier.value}'`);
|
|
7214
|
+
}
|
|
7215
|
+
// otherwise, replace the modifier value with single underscore.
|
|
7216
|
+
// it is needed to check whether the modifier is supported by specific adblocker due to the syntax
|
|
7217
|
+
modifier.modifier.value = UNDERSCORE;
|
|
7218
|
+
}
|
|
7219
|
+
if (!this.exists(modifier)) {
|
|
7220
|
+
return getInvalidValidationResult(`${INVALID_ERROR_PREFIX.NOT_EXISTENT}: '${modifier.modifier.value}'`);
|
|
7221
|
+
}
|
|
7222
|
+
// for 'Common' syntax we cannot check something more
|
|
7223
|
+
if (syntax === AdblockSyntax.Common) {
|
|
7224
|
+
return { ok: true };
|
|
7225
|
+
}
|
|
7226
|
+
return validateForSpecificSyntax(this.modifiersData, syntax, modifier, isException);
|
|
7227
|
+
};
|
|
7228
|
+
/**
|
|
7229
|
+
* Returns AdGuard documentation URL for given modifier.
|
|
7230
|
+
*
|
|
7231
|
+
* @param modifier Parsed modifier AST node.
|
|
7232
|
+
*
|
|
7233
|
+
* @returns AdGuard documentation URL or `null` if not found.
|
|
7234
|
+
*/
|
|
7235
|
+
getAdgDocumentationLink = (modifier) => {
|
|
7236
|
+
if (!this.exists(modifier)) {
|
|
7237
|
+
return null;
|
|
7238
|
+
}
|
|
7239
|
+
return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Adg], modifier);
|
|
7240
|
+
};
|
|
7241
|
+
/**
|
|
7242
|
+
* Returns Ublock Origin documentation URL for given modifier.
|
|
7243
|
+
*
|
|
7244
|
+
* @param modifier Parsed modifier AST node.
|
|
7245
|
+
*
|
|
7246
|
+
* @returns Ublock Origin documentation URL or `null` if not found.
|
|
7247
|
+
*/
|
|
7248
|
+
getUboDocumentationLink = (modifier) => {
|
|
7249
|
+
if (!this.exists(modifier)) {
|
|
7250
|
+
return null;
|
|
7251
|
+
}
|
|
7252
|
+
return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Ubo], modifier);
|
|
7253
|
+
};
|
|
7254
|
+
/**
|
|
7255
|
+
* Returns AdBlock Plus documentation URL for given modifier.
|
|
7256
|
+
*
|
|
7257
|
+
* @param modifier Parsed modifier AST node.
|
|
7258
|
+
*
|
|
7259
|
+
* @returns AdBlock Plus documentation URL or `null` if not found.
|
|
7260
|
+
*/
|
|
7261
|
+
getAbpDocumentationLink = (modifier) => {
|
|
7262
|
+
if (!this.exists(modifier)) {
|
|
7263
|
+
return null;
|
|
7264
|
+
}
|
|
7265
|
+
return getBlockerDocumentationLink(this.modifiersData, BLOCKER_PREFIX[AdblockSyntax.Abp], modifier);
|
|
7266
|
+
};
|
|
7267
|
+
}
|
|
7268
|
+
const modifierValidator = new ModifierValidator();
|
|
7269
|
+
|
|
7270
|
+
/**
|
|
7271
|
+
* @file Base class for converters
|
|
7272
|
+
*
|
|
7273
|
+
* TS doesn't support abstract static methods, so we should use
|
|
7274
|
+
* a workaround and extend this class instead of implementing it
|
|
7275
|
+
*/
|
|
7276
|
+
/* eslint-disable jsdoc/require-returns-check */
|
|
7277
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
7278
|
+
/**
|
|
7279
|
+
* Basic class for rule converters
|
|
7280
|
+
*/
|
|
7281
|
+
class ConverterBase {
|
|
7282
|
+
/**
|
|
7283
|
+
* Converts some data to AdGuard format
|
|
7284
|
+
*
|
|
7285
|
+
* @param data Data to convert
|
|
7286
|
+
* @returns Converted data
|
|
7287
|
+
* @throws If the data is invalid or incompatible
|
|
7288
|
+
*/
|
|
7289
|
+
static convertToAdg(data) {
|
|
7290
|
+
throw new NotImplementedError();
|
|
7291
|
+
}
|
|
7292
|
+
/**
|
|
7293
|
+
* Converts some data to Adblock Plus format
|
|
7294
|
+
*
|
|
7295
|
+
* @param data Data to convert
|
|
7296
|
+
* @returns Converted data
|
|
7297
|
+
* @throws If the data is invalid or incompatible
|
|
7298
|
+
*/
|
|
7299
|
+
static convertToAbp(data) {
|
|
7300
|
+
throw new NotImplementedError();
|
|
7301
|
+
}
|
|
7302
|
+
/**
|
|
7303
|
+
* Converts some data to uBlock Origin format
|
|
7304
|
+
*
|
|
7305
|
+
* @param data Data to convert
|
|
7306
|
+
* @returns Converted data
|
|
7307
|
+
* @throws If the data is invalid or incompatible
|
|
7308
|
+
*/
|
|
7309
|
+
static convertToUbo(data) {
|
|
7310
|
+
throw new NotImplementedError();
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
/**
|
|
7315
|
+
* @file Base class for rule converters
|
|
7316
|
+
*
|
|
7317
|
+
* TS doesn't support abstract static methods, so we should use
|
|
7318
|
+
* a workaround and extend this class instead of implementing it
|
|
7319
|
+
*/
|
|
7320
|
+
/* eslint-disable jsdoc/require-returns-check */
|
|
7321
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
7322
|
+
/**
|
|
7323
|
+
* Basic class for rule converters
|
|
7324
|
+
*/
|
|
7325
|
+
class RuleConverterBase extends ConverterBase {
|
|
7326
|
+
/**
|
|
7327
|
+
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
7328
|
+
*
|
|
7329
|
+
* @param rule Rule node to convert
|
|
7330
|
+
* @returns Array of converted rule nodes
|
|
7331
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7332
|
+
*/
|
|
7333
|
+
static convertToAdg(rule) {
|
|
7334
|
+
throw new NotImplementedError();
|
|
7335
|
+
}
|
|
7336
|
+
/**
|
|
7337
|
+
* Converts an adblock filtering rule to Adblock Plus format, if possible.
|
|
7338
|
+
*
|
|
7339
|
+
* @param rule Rule node to convert
|
|
7340
|
+
* @returns Array of converted rule nodes
|
|
7341
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7342
|
+
*/
|
|
7343
|
+
static convertToAbp(rule) {
|
|
7344
|
+
throw new NotImplementedError();
|
|
7345
|
+
}
|
|
7346
|
+
/**
|
|
7347
|
+
* Converts an adblock filtering rule to uBlock Origin format, if possible.
|
|
7348
|
+
*
|
|
7349
|
+
* @param rule Rule node to convert
|
|
7350
|
+
* @returns Array of converted rule nodes
|
|
7351
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7352
|
+
*/
|
|
7353
|
+
static convertToUbo(rule) {
|
|
7354
|
+
throw new NotImplementedError();
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
|
|
7358
|
+
/**
|
|
7359
|
+
* @file Comment rule converter
|
|
7360
|
+
*/
|
|
7361
|
+
/**
|
|
7362
|
+
* Comment rule converter class
|
|
7363
|
+
*
|
|
7364
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
7365
|
+
*/
|
|
7366
|
+
class CommentRuleConverter extends RuleConverterBase {
|
|
7367
|
+
/**
|
|
7368
|
+
* Converts a comment rule to AdGuard format, if possible.
|
|
7369
|
+
*
|
|
7370
|
+
* @param rule Rule node to convert
|
|
7371
|
+
* @returns Array of converted rule nodes
|
|
7372
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7373
|
+
*/
|
|
7374
|
+
static convertToAdg(rule) {
|
|
7375
|
+
// Clone the provided AST node to avoid side effects
|
|
7376
|
+
const ruleNode = cloneDeep(rule);
|
|
7377
|
+
// TODO: Add support for other comment types, if needed
|
|
7378
|
+
// Main task is # -> ! conversion
|
|
7379
|
+
switch (ruleNode.type) {
|
|
7380
|
+
case CommentRuleType.CommentRule:
|
|
7381
|
+
// 'Comment' uBO style comments
|
|
7382
|
+
if (ruleNode.type === CommentRuleType.CommentRule
|
|
7383
|
+
&& ruleNode.marker.value === CommentMarker.Hashmark) {
|
|
7384
|
+
ruleNode.marker.value = CommentMarker.Regular;
|
|
7385
|
+
// Add the hashmark to the beginning of the comment
|
|
7386
|
+
ruleNode.text.value = `${SPACE}${CommentMarker.Hashmark}${ruleNode.text.value}`;
|
|
7387
|
+
}
|
|
7388
|
+
return [ruleNode];
|
|
7389
|
+
// Leave any other comment rule as is
|
|
7390
|
+
default:
|
|
7391
|
+
return [ruleNode];
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
|
|
7396
|
+
/**
|
|
7397
|
+
* @file Regular expression utilities
|
|
7398
|
+
*/
|
|
7399
|
+
// Special RegExp constants
|
|
7400
|
+
const REGEX_START = CARET; // '^'
|
|
7401
|
+
const REGEX_END = DOLLAR_SIGN; // '$'
|
|
7402
|
+
const REGEX_ANY_CHARACTERS = DOT + ASTERISK; // '.*'
|
|
7403
|
+
// Special adblock pattern symbols and their RegExp equivalents
|
|
7404
|
+
const ADBLOCK_URL_START = PIPE + PIPE; // '||'
|
|
7405
|
+
const ADBLOCK_URL_START_REGEX = '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?';
|
|
7406
|
+
const ADBLOCK_URL_SEPARATOR = CARET; // '^'
|
|
7407
|
+
const ADBLOCK_URL_SEPARATOR_REGEX = '([^ a-zA-Z0-9.%_-]|$)';
|
|
7408
|
+
const ADBLOCK_WILDCARD = ASTERISK; // '*'
|
|
7409
|
+
const ADBLOCK_WILDCARD_REGEX = REGEX_ANY_CHARACTERS;
|
|
7410
|
+
// Negation wrapper for RegExp patterns
|
|
7411
|
+
const REGEX_NEGATION_PREFIX = '^((?!';
|
|
7412
|
+
const REGEX_NEGATION_SUFFIX = ').)*$';
|
|
7413
|
+
/**
|
|
7414
|
+
* Special RegExp symbols
|
|
7415
|
+
*
|
|
7416
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-escape
|
|
7417
|
+
*/
|
|
7418
|
+
const SPECIAL_REGEX_SYMBOLS = new Set([
|
|
7419
|
+
ASTERISK,
|
|
7420
|
+
CARET,
|
|
7421
|
+
CLOSE_CURLY_BRACKET,
|
|
7422
|
+
CLOSE_PARENTHESIS,
|
|
7423
|
+
CLOSE_SQUARE_BRACKET,
|
|
7424
|
+
DOLLAR_SIGN,
|
|
7425
|
+
DOT,
|
|
7426
|
+
ESCAPE_CHARACTER,
|
|
7427
|
+
OPEN_CURLY_BRACKET,
|
|
7428
|
+
OPEN_PARENTHESIS,
|
|
7429
|
+
OPEN_SQUARE_BRACKET,
|
|
7430
|
+
PIPE,
|
|
7431
|
+
PLUS,
|
|
7432
|
+
QUESTION_MARK,
|
|
7433
|
+
SLASH,
|
|
7434
|
+
]);
|
|
7435
|
+
/**
|
|
7436
|
+
* Utility functions for working with RegExp patterns
|
|
7437
|
+
*/
|
|
7438
|
+
class RegExpUtils {
|
|
7439
|
+
/**
|
|
7440
|
+
* Checks whether a string is a RegExp pattern.
|
|
7441
|
+
* Flags are not supported.
|
|
7442
|
+
*
|
|
7443
|
+
* @param pattern - Pattern to check
|
|
7444
|
+
* @returns `true` if the string is a RegExp pattern, `false` otherwise
|
|
7445
|
+
*/
|
|
7446
|
+
static isRegexPattern(pattern) {
|
|
7447
|
+
const trimmedPattern = pattern.trim();
|
|
7448
|
+
// Avoid false positives
|
|
7449
|
+
if (trimmedPattern.length > REGEX_MARKER.length * 2 && trimmedPattern.startsWith(REGEX_MARKER)) {
|
|
7450
|
+
const last = StringUtils.findNextUnescapedCharacter(trimmedPattern, REGEX_MARKER, REGEX_MARKER.length);
|
|
7451
|
+
return last === trimmedPattern.length - 1;
|
|
7452
|
+
}
|
|
7453
|
+
return false;
|
|
7454
|
+
}
|
|
7455
|
+
/**
|
|
7456
|
+
* Negates a RegExp pattern. Technically, this method wraps the pattern in `^((?!` and `).)*$`.
|
|
7457
|
+
*
|
|
7458
|
+
* RegExp modifiers are not supported.
|
|
7459
|
+
*
|
|
7460
|
+
* @param pattern Pattern to negate (can be wrapped in slashes or not)
|
|
7461
|
+
* @returns Negated RegExp pattern
|
|
7462
|
+
*/
|
|
7463
|
+
static negateRegexPattern(pattern) {
|
|
7464
|
+
let result = pattern.trim();
|
|
7465
|
+
let slashes = false;
|
|
7466
|
+
// Remove the leading and trailing slashes (/)
|
|
7467
|
+
if (RegExpUtils.isRegexPattern(result)) {
|
|
7468
|
+
result = result.substring(REGEX_MARKER.length, result.length - REGEX_MARKER.length);
|
|
7469
|
+
slashes = true;
|
|
7470
|
+
}
|
|
7471
|
+
// Only negate the pattern if it's not already negated
|
|
7472
|
+
if (!(result.startsWith(REGEX_NEGATION_PREFIX) && result.endsWith(REGEX_NEGATION_SUFFIX))) {
|
|
7473
|
+
// Remove leading caret (^)
|
|
7474
|
+
if (result.startsWith(REGEX_START)) {
|
|
7475
|
+
result = result.substring(REGEX_START.length);
|
|
7476
|
+
}
|
|
7477
|
+
// Remove trailing dollar sign ($)
|
|
7478
|
+
if (result.endsWith(REGEX_END)) {
|
|
7479
|
+
result = result.substring(0, result.length - REGEX_END.length);
|
|
7480
|
+
}
|
|
7481
|
+
// Wrap the pattern in the negation
|
|
7482
|
+
result = `${REGEX_NEGATION_PREFIX}${result}${REGEX_NEGATION_SUFFIX}`;
|
|
7483
|
+
}
|
|
7484
|
+
// Add the leading and trailing slashes back if they were there
|
|
7485
|
+
if (slashes) {
|
|
7486
|
+
result = `${REGEX_MARKER}${result}${REGEX_MARKER}`;
|
|
7487
|
+
}
|
|
7488
|
+
return result;
|
|
7489
|
+
}
|
|
7490
|
+
/**
|
|
7491
|
+
* Converts a basic adblock rule pattern to a RegExp pattern. Based on
|
|
7492
|
+
* https://github.com/AdguardTeam/tsurlfilter/blob/9b26e0b4a0e30b87690bc60f7cf377d112c3085c/packages/tsurlfilter/src/rules/simple-regex.ts#L219
|
|
7493
|
+
*
|
|
7494
|
+
* @param pattern Pattern to convert
|
|
7495
|
+
* @returns RegExp equivalent of the pattern
|
|
7496
|
+
* @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#basic-rules}
|
|
7497
|
+
*/
|
|
7498
|
+
static patternToRegexp(pattern) {
|
|
7499
|
+
const trimmed = pattern.trim();
|
|
7500
|
+
// Return regex for any character sequence if the pattern is just |, ||, * or empty
|
|
7501
|
+
if (trimmed === ADBLOCK_URL_START
|
|
7502
|
+
|| trimmed === PIPE
|
|
7503
|
+
|| trimmed === ADBLOCK_WILDCARD
|
|
7504
|
+
|| trimmed === EMPTY) {
|
|
7505
|
+
return REGEX_ANY_CHARACTERS;
|
|
7506
|
+
}
|
|
7507
|
+
// If the pattern is already a RegExp, just return it, but remove the leading and trailing slashes
|
|
7508
|
+
if (RegExpUtils.isRegexPattern(pattern)) {
|
|
7509
|
+
return pattern.substring(REGEX_MARKER.length, pattern.length - REGEX_MARKER.length);
|
|
7510
|
+
}
|
|
7511
|
+
let result = EMPTY;
|
|
7512
|
+
let offset = 0;
|
|
7513
|
+
let len = trimmed.length;
|
|
7514
|
+
// Handle leading pipes
|
|
7515
|
+
if (trimmed[0] === PIPE) {
|
|
7516
|
+
if (trimmed[1] === PIPE) {
|
|
7517
|
+
// Replace adblock url start (||) with its RegExp equivalent
|
|
7518
|
+
result += ADBLOCK_URL_START_REGEX;
|
|
7519
|
+
offset = ADBLOCK_URL_START.length;
|
|
7520
|
+
}
|
|
7521
|
+
else {
|
|
7522
|
+
// Replace single pipe (|) with the RegExp start symbol (^)
|
|
7523
|
+
result += REGEX_START;
|
|
7524
|
+
offset = REGEX_START.length;
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
// Handle trailing pipes
|
|
7528
|
+
let trailingPipe = false;
|
|
7529
|
+
if (trimmed.endsWith(PIPE)) {
|
|
7530
|
+
trailingPipe = true;
|
|
7531
|
+
len -= PIPE.length;
|
|
7532
|
+
}
|
|
7533
|
+
// Handle the rest of the pattern, if any
|
|
7534
|
+
for (; offset < len; offset += 1) {
|
|
7535
|
+
if (trimmed[offset] === ADBLOCK_WILDCARD) {
|
|
7536
|
+
// Replace adblock wildcard (*) with its RegExp equivalent
|
|
7537
|
+
result += ADBLOCK_WILDCARD_REGEX;
|
|
7538
|
+
}
|
|
7539
|
+
else if (trimmed[offset] === ADBLOCK_URL_SEPARATOR) {
|
|
7540
|
+
// Replace adblock url separator (^) with its RegExp equivalent
|
|
7541
|
+
result += ADBLOCK_URL_SEPARATOR_REGEX;
|
|
7542
|
+
}
|
|
7543
|
+
else if (SPECIAL_REGEX_SYMBOLS.has(trimmed[offset])) {
|
|
7544
|
+
// Escape special RegExp symbols (we handled pipe (|) and asterisk (*) already)
|
|
7545
|
+
result += ESCAPE_CHARACTER + trimmed[offset];
|
|
7546
|
+
}
|
|
7547
|
+
else {
|
|
7548
|
+
// Just add any other character
|
|
7549
|
+
result += trimmed[offset];
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
// Handle trailing pipes
|
|
7553
|
+
if (trailingPipe) {
|
|
7554
|
+
// Replace trailing pipe (|) with the RegExp end symbol ($)
|
|
7555
|
+
result += REGEX_END;
|
|
7556
|
+
}
|
|
7557
|
+
return result;
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
/**
|
|
7562
|
+
* @file HTML filtering rule converter
|
|
7563
|
+
*/
|
|
7564
|
+
/**
|
|
7565
|
+
* From the AdGuard docs:
|
|
7566
|
+
* Specifies the maximum length for content of HTML element. If this parameter is
|
|
7567
|
+
* set and the content length exceeds the value, a rule does not apply to the element.
|
|
7568
|
+
* If this parameter is not specified, the max-length is considered to be 8192 (8 KB).
|
|
7569
|
+
* When converting from other formats, we set the max-length to 262144 (256 KB).
|
|
7570
|
+
*
|
|
7571
|
+
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
|
|
7572
|
+
*/
|
|
7573
|
+
const ADGUARD_HTML_DEFAULT_MAX_LENGTH = 8192;
|
|
7574
|
+
const ADGUARD_HTML_CONVERSION_MAX_LENGTH = ADGUARD_HTML_DEFAULT_MAX_LENGTH * 32;
|
|
7575
|
+
const NOT_SPECIFIED = -1;
|
|
7576
|
+
const CONTAINS$1 = 'contains';
|
|
7577
|
+
const HAS_TEXT$1 = 'has-text';
|
|
7578
|
+
const MAX_LENGTH = 'max-length';
|
|
7579
|
+
const MIN_LENGTH = 'min-length';
|
|
7580
|
+
const MIN_TEXT_LENGTH = 'min-text-length';
|
|
7581
|
+
const TAG_CONTENT = 'tag-content';
|
|
7582
|
+
const WILDCARD$1 = 'wildcard';
|
|
7583
|
+
/**
|
|
7584
|
+
* HTML filtering rule converter class
|
|
7585
|
+
*
|
|
7586
|
+
* @todo Implement `convertToUbo` (ABP currently doesn't support HTML filtering rules)
|
|
7587
|
+
*/
|
|
7588
|
+
class HtmlRuleConverter extends RuleConverterBase {
|
|
7589
|
+
/**
|
|
7590
|
+
* Converts a HTML rule to AdGuard syntax, if possible. Also can be used to convert
|
|
7591
|
+
* AdGuard rules to AdGuard syntax to validate them.
|
|
7592
|
+
*
|
|
7593
|
+
* _Note:_ uBlock Origin supports multiple selectors within a single rule, but AdGuard doesn't,
|
|
7594
|
+
* so the following rule
|
|
7595
|
+
* ```
|
|
7596
|
+
* example.com##^div[attr1="value1"][attr2="value2"], script:has-text(value)
|
|
7597
|
+
* ```
|
|
7598
|
+
* will be converted to multiple AdGuard rules:
|
|
7599
|
+
* ```
|
|
7600
|
+
* example.com$$div[attr1="value1"][attr2="value2"][max-length="262144"]
|
|
7601
|
+
* example.com$$script[tag-content="value"][max-length="262144"]
|
|
7602
|
+
* ```
|
|
7603
|
+
*
|
|
7604
|
+
* @param rule Rule node to convert
|
|
7605
|
+
* @returns Array of converted rule nodes
|
|
7606
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7607
|
+
*/
|
|
7608
|
+
static convertToAdg(rule) {
|
|
7609
|
+
// Clone the provided AST node to avoid side effects
|
|
7610
|
+
const ruleNode = cloneDeep(rule);
|
|
7611
|
+
// Prepare the conversion result
|
|
7612
|
+
const conversionResult = [];
|
|
7613
|
+
// Iterate over selector list
|
|
7614
|
+
for (const selector of ruleNode.body.body.children) {
|
|
7615
|
+
// Check selector, just in case
|
|
7616
|
+
if (selector.type !== CssTreeNodeType.Selector) {
|
|
7617
|
+
throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
|
|
7618
|
+
}
|
|
7619
|
+
// At least one child is required, and first child may be a tag selector
|
|
7620
|
+
if (selector.children.length === 0) {
|
|
7621
|
+
throw new RuleConversionError('Invalid selector, no children are present');
|
|
7622
|
+
}
|
|
7623
|
+
// Prepare bounds
|
|
7624
|
+
let minLength = NOT_SPECIFIED;
|
|
7625
|
+
let maxLength = NOT_SPECIFIED;
|
|
7626
|
+
// Prepare the converted selector
|
|
7627
|
+
const convertedSelector = {
|
|
7628
|
+
type: CssTreeNodeType.Selector,
|
|
7629
|
+
children: [],
|
|
7630
|
+
};
|
|
7631
|
+
for (let i = 0; i < selector.children.length; i += 1) {
|
|
7632
|
+
// Current node within the current selector
|
|
7633
|
+
const node = selector.children[i];
|
|
7634
|
+
switch (node.type) {
|
|
7635
|
+
case CssTreeNodeType.TypeSelector:
|
|
7636
|
+
// First child in the selector may be a tag selector
|
|
7637
|
+
if (i !== 0) {
|
|
7638
|
+
throw new RuleConversionError('Tag selector should be the first child, if present');
|
|
7639
|
+
}
|
|
7640
|
+
// Simply store the tag selector
|
|
7641
|
+
convertedSelector.children.push(cloneDeep(node));
|
|
7642
|
+
break;
|
|
7643
|
+
case CssTreeNodeType.AttributeSelector:
|
|
7644
|
+
// Check if the attribute selector is a special AdGuard attribute
|
|
7645
|
+
switch (node.name.name) {
|
|
7646
|
+
case MIN_LENGTH:
|
|
7647
|
+
minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
7648
|
+
break;
|
|
7649
|
+
case MAX_LENGTH:
|
|
7650
|
+
maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
|
|
7651
|
+
break;
|
|
7652
|
+
case TAG_CONTENT:
|
|
7653
|
+
case WILDCARD$1:
|
|
7654
|
+
CssTree.assertAttributeSelectorHasStringValue(node);
|
|
7655
|
+
convertedSelector.children.push(cloneDeep(node));
|
|
7656
|
+
break;
|
|
7657
|
+
default:
|
|
7658
|
+
convertedSelector.children.push(cloneDeep(node));
|
|
7659
|
+
}
|
|
7660
|
+
break;
|
|
7661
|
+
case CssTreeNodeType.PseudoClassSelector:
|
|
7662
|
+
CssTree.assertPseudoClassHasAnyArgument(node);
|
|
7663
|
+
// eslint-disable-next-line no-case-declarations
|
|
7664
|
+
const arg = node.children[0];
|
|
7665
|
+
if (arg.type !== CssTreeNodeType.String
|
|
7666
|
+
&& arg.type !== CssTreeNodeType.Raw
|
|
7667
|
+
&& arg.type !== CssTreeNodeType.Number) {
|
|
7668
|
+
throw new RuleConversionError(`Unsupported argument type '${arg.type}' for pseudo class '${node.name}'`);
|
|
7669
|
+
}
|
|
7670
|
+
// Process the pseudo class based on its name
|
|
7671
|
+
switch (node.name) {
|
|
7672
|
+
case HAS_TEXT$1:
|
|
7673
|
+
case CONTAINS$1:
|
|
7674
|
+
// Check if the argument is a RegExp
|
|
7675
|
+
if (RegExpUtils.isRegexPattern(arg.value)) {
|
|
7676
|
+
// TODO: Add some support for RegExp patterns later
|
|
7677
|
+
// Need to find a way to convert some RegExp patterns to glob patterns
|
|
7678
|
+
throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
|
|
7679
|
+
}
|
|
7680
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(TAG_CONTENT, arg.value));
|
|
7681
|
+
break;
|
|
7682
|
+
// https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
|
|
7683
|
+
case MIN_TEXT_LENGTH:
|
|
7684
|
+
minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
|
|
7685
|
+
break;
|
|
7686
|
+
default:
|
|
7687
|
+
throw new RuleConversionError(`Unsupported pseudo class '${node.name}'`);
|
|
7688
|
+
}
|
|
7689
|
+
break;
|
|
7690
|
+
default:
|
|
7691
|
+
throw new RuleConversionError(`Unsupported node type '${node.type}'`);
|
|
7692
|
+
}
|
|
7693
|
+
}
|
|
7694
|
+
if (minLength !== NOT_SPECIFIED) {
|
|
7695
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(MIN_LENGTH, String(minLength)));
|
|
7696
|
+
}
|
|
7697
|
+
convertedSelector.children.push(CssTree.createAttributeSelectorNode(MAX_LENGTH, String(maxLength === NOT_SPECIFIED
|
|
7698
|
+
? ADGUARD_HTML_CONVERSION_MAX_LENGTH
|
|
7699
|
+
: maxLength)));
|
|
7700
|
+
// Create the converted rule
|
|
7701
|
+
conversionResult.push({
|
|
7702
|
+
category: RuleCategory.Cosmetic,
|
|
7703
|
+
type: CosmeticRuleType.HtmlFilteringRule,
|
|
7704
|
+
syntax: AdblockSyntax.Adg,
|
|
7705
|
+
// Convert the separator based on the exception status
|
|
7706
|
+
separator: {
|
|
7707
|
+
type: 'Value',
|
|
7708
|
+
value: ruleNode.exception
|
|
7709
|
+
? CosmeticRuleSeparator.AdgHtmlFilteringException
|
|
7710
|
+
: CosmeticRuleSeparator.AdgHtmlFiltering,
|
|
7711
|
+
},
|
|
7712
|
+
// Create the body based on the converted selector
|
|
7713
|
+
body: {
|
|
7714
|
+
type: 'HtmlFilteringRuleBody',
|
|
7715
|
+
body: {
|
|
7716
|
+
type: CssTreeNodeType.SelectorList,
|
|
7717
|
+
children: [{
|
|
7718
|
+
type: CssTreeNodeType.Selector,
|
|
7719
|
+
children: [convertedSelector],
|
|
7720
|
+
}],
|
|
7721
|
+
},
|
|
7722
|
+
},
|
|
7723
|
+
exception: ruleNode.exception,
|
|
7724
|
+
domains: ruleNode.domains,
|
|
7725
|
+
});
|
|
7726
|
+
}
|
|
7727
|
+
return conversionResult;
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7731
|
+
/**
|
|
7732
|
+
* @file Utility functions for working with quotes
|
|
7733
|
+
*/
|
|
7734
|
+
/**
|
|
7735
|
+
* Possible quote types for scriptlet parameters
|
|
7736
|
+
*/
|
|
7737
|
+
var QuoteType;
|
|
7738
|
+
(function (QuoteType) {
|
|
7739
|
+
/**
|
|
7740
|
+
* No quotes at all
|
|
7741
|
+
*/
|
|
7742
|
+
QuoteType["None"] = "none";
|
|
7743
|
+
/**
|
|
7744
|
+
* Single quotes (`'`)
|
|
7745
|
+
*/
|
|
7746
|
+
QuoteType["Single"] = "single";
|
|
7747
|
+
/**
|
|
7748
|
+
* Double quotes (`"`)
|
|
7749
|
+
*/
|
|
7750
|
+
QuoteType["Double"] = "double";
|
|
7751
|
+
})(QuoteType || (QuoteType = {}));
|
|
7752
|
+
/**
|
|
7753
|
+
* Utility functions for working with quotes
|
|
7754
|
+
*/
|
|
7755
|
+
class QuoteUtils {
|
|
7756
|
+
/**
|
|
7757
|
+
* Escape all unescaped occurrences of the character
|
|
7758
|
+
*
|
|
7759
|
+
* @param string String to escape
|
|
7760
|
+
* @param char Character to escape
|
|
7761
|
+
* @returns Escaped string
|
|
7762
|
+
*/
|
|
7763
|
+
static escapeUnescapedOccurrences(string, char) {
|
|
7764
|
+
let result = EMPTY;
|
|
7765
|
+
for (let i = 0; i < string.length; i += 1) {
|
|
7766
|
+
if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
|
|
7767
|
+
result += ESCAPE_CHARACTER;
|
|
7768
|
+
}
|
|
7769
|
+
result += string[i];
|
|
7770
|
+
}
|
|
7771
|
+
return result;
|
|
7772
|
+
}
|
|
7773
|
+
/**
|
|
7774
|
+
* Unescape all single escaped occurrences of the character
|
|
7775
|
+
*
|
|
7776
|
+
* @param string String to unescape
|
|
7777
|
+
* @param char Character to unescape
|
|
7778
|
+
* @returns Unescaped string
|
|
7779
|
+
*/
|
|
7780
|
+
static unescapeSingleEscapedOccurrences(string, char) {
|
|
7781
|
+
let result = EMPTY;
|
|
7782
|
+
for (let i = 0; i < string.length; i += 1) {
|
|
7783
|
+
if (string[i] === char
|
|
7784
|
+
&& string[i - 1] === ESCAPE_CHARACTER
|
|
7785
|
+
&& (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
|
|
7786
|
+
result = result.slice(0, -1);
|
|
7787
|
+
}
|
|
7788
|
+
result += string[i];
|
|
7789
|
+
}
|
|
7790
|
+
return result;
|
|
7791
|
+
}
|
|
7792
|
+
/**
|
|
7793
|
+
* Get quote type of the string
|
|
7794
|
+
*
|
|
7795
|
+
* @param string String to check
|
|
7796
|
+
* @returns Quote type of the string
|
|
7797
|
+
*/
|
|
7798
|
+
static getStringQuoteType(string) {
|
|
7799
|
+
// Don't check 1-character strings to avoid false positives
|
|
7800
|
+
if (string.length > 1) {
|
|
7801
|
+
if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
|
|
7802
|
+
return QuoteType.Single;
|
|
7803
|
+
}
|
|
7804
|
+
if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
|
|
7805
|
+
return QuoteType.Double;
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7808
|
+
return QuoteType.None;
|
|
7809
|
+
}
|
|
7810
|
+
/**
|
|
7811
|
+
* Set quote type of the string
|
|
7812
|
+
*
|
|
7813
|
+
* @param string String to set quote type of
|
|
7814
|
+
* @param quoteType Quote type to set
|
|
7815
|
+
* @returns String with the specified quote type
|
|
7816
|
+
*/
|
|
7817
|
+
static setStringQuoteType(string, quoteType) {
|
|
7818
|
+
const actualQuoteType = QuoteUtils.getStringQuoteType(string);
|
|
7819
|
+
switch (quoteType) {
|
|
7820
|
+
case QuoteType.None:
|
|
7821
|
+
if (actualQuoteType === QuoteType.Single) {
|
|
7822
|
+
return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
|
|
7823
|
+
}
|
|
7824
|
+
if (actualQuoteType === QuoteType.Double) {
|
|
7825
|
+
return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
|
|
7826
|
+
}
|
|
7827
|
+
return string;
|
|
7828
|
+
case QuoteType.Single:
|
|
7829
|
+
if (actualQuoteType === QuoteType.None) {
|
|
7830
|
+
return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
|
|
7831
|
+
}
|
|
7832
|
+
if (actualQuoteType === QuoteType.Double) {
|
|
7833
|
+
return SINGLE_QUOTE
|
|
7834
|
+
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
|
|
7835
|
+
}
|
|
7836
|
+
return string;
|
|
7837
|
+
case QuoteType.Double:
|
|
7838
|
+
if (actualQuoteType === QuoteType.None) {
|
|
7839
|
+
return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
|
|
7840
|
+
}
|
|
7841
|
+
if (actualQuoteType !== QuoteType.Double) {
|
|
7842
|
+
// eslint-disable-next-line max-len
|
|
7843
|
+
return DOUBLE_QUOTE
|
|
7844
|
+
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
|
|
7845
|
+
}
|
|
7846
|
+
return string;
|
|
7847
|
+
default:
|
|
7848
|
+
return string;
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* Removes bounding quotes from a string, if any
|
|
7853
|
+
*
|
|
7854
|
+
* @param string Input string
|
|
7855
|
+
* @returns String without quotes
|
|
7856
|
+
*/
|
|
7857
|
+
static removeQuotes(string) {
|
|
7858
|
+
if (
|
|
7859
|
+
// We should check for string length to avoid false positives
|
|
7860
|
+
string.length > 1
|
|
7861
|
+
&& (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE)
|
|
7862
|
+
&& string[0] === string[string.length - 1]) {
|
|
7863
|
+
return string.slice(1, -1);
|
|
7864
|
+
}
|
|
7865
|
+
return string;
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
|
|
7869
|
+
/**
|
|
7870
|
+
* @file Utility functions for working with scriptlet nodes
|
|
7871
|
+
*/
|
|
7872
|
+
/**
|
|
7873
|
+
* Get name of the scriptlet from the scriptlet node
|
|
7874
|
+
*
|
|
7875
|
+
* @param scriptletNode Scriptlet node to get name of
|
|
7876
|
+
* @returns Name of the scriptlet
|
|
7877
|
+
* @throws If the scriptlet is empty
|
|
7878
|
+
*/
|
|
7879
|
+
function getScriptletName(scriptletNode) {
|
|
7880
|
+
if (scriptletNode.children.length === 0) {
|
|
7881
|
+
throw new Error('Empty scriptlet');
|
|
7882
|
+
}
|
|
7883
|
+
return scriptletNode.children[0].value;
|
|
7884
|
+
}
|
|
7885
|
+
/**
|
|
7886
|
+
* Set name of the scriptlet
|
|
7887
|
+
*
|
|
7888
|
+
* @param scriptletNode Scriptlet node to set name of
|
|
7889
|
+
* @param name Name to set
|
|
7890
|
+
* @returns Scriptlet node with the specified name
|
|
7891
|
+
* @throws If the scriptlet is empty
|
|
7892
|
+
*/
|
|
7893
|
+
function setScriptletName(scriptletNode, name) {
|
|
7894
|
+
if (scriptletNode.children.length === 0) {
|
|
7895
|
+
throw new Error('Empty scriptlet');
|
|
7896
|
+
}
|
|
7897
|
+
const scriptletNodeClone = cloneDeep(scriptletNode);
|
|
7898
|
+
scriptletNodeClone.children[0].value = name;
|
|
7899
|
+
return scriptletNodeClone;
|
|
7900
|
+
}
|
|
7901
|
+
/**
|
|
7902
|
+
* Set quote type of the scriptlet parameters
|
|
7903
|
+
*
|
|
7904
|
+
* @param scriptletNode Scriptlet node to set quote type of
|
|
7905
|
+
* @param quoteType Preferred quote type
|
|
7906
|
+
* @returns Scriptlet node with the specified quote type
|
|
7907
|
+
*/
|
|
7908
|
+
function setScriptletQuoteType(scriptletNode, quoteType) {
|
|
7909
|
+
if (scriptletNode.children.length === 0) {
|
|
7910
|
+
throw new Error('Empty scriptlet');
|
|
7911
|
+
}
|
|
7912
|
+
const scriptletNodeClone = cloneDeep(scriptletNode);
|
|
7913
|
+
for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
|
|
7914
|
+
scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
|
|
7915
|
+
}
|
|
7916
|
+
return scriptletNodeClone;
|
|
7917
|
+
}
|
|
7918
|
+
|
|
7919
|
+
/**
|
|
7920
|
+
* @file Scriptlet conversions from ABP and uBO to ADG
|
|
7921
|
+
*/
|
|
7922
|
+
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
7923
|
+
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
7924
|
+
/**
|
|
7925
|
+
* Helper class for converting scriptlets from ABP and uBO to ADG
|
|
7926
|
+
*/
|
|
7927
|
+
class AdgScriptletConverter {
|
|
7928
|
+
/**
|
|
7929
|
+
* Helper function to convert scriptlets to ADG. We implement the core
|
|
7930
|
+
* logic here to avoid code duplication.
|
|
7931
|
+
*
|
|
7932
|
+
* @param scriptletNode Scriptlet parameter list node to convert
|
|
7933
|
+
* @param prefix Prefix to add to the scriptlet name
|
|
7934
|
+
* @returns Converted scriptlet parameter list node
|
|
7935
|
+
*/
|
|
7936
|
+
static convertToAdg(scriptletNode, prefix) {
|
|
7937
|
+
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
7938
|
+
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), QuoteType.None);
|
|
7939
|
+
// Clone the node to avoid any side effects
|
|
7940
|
+
let result = cloneDeep(scriptletNode);
|
|
7941
|
+
// Only add prefix if it's not already there
|
|
7942
|
+
if (!scriptletName.startsWith(prefix)) {
|
|
7943
|
+
result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
|
|
7944
|
+
}
|
|
7945
|
+
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
7946
|
+
result = setScriptletQuoteType(result, QuoteType.Single);
|
|
7947
|
+
return result;
|
|
7948
|
+
}
|
|
7949
|
+
/**
|
|
7950
|
+
* Converts an ABP snippet node to ADG scriptlet node, if possible.
|
|
7951
|
+
*
|
|
7952
|
+
* @param scriptletNode Scriptlet node to convert
|
|
7953
|
+
* @returns Converted scriptlet node
|
|
7954
|
+
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
7955
|
+
* @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
|
|
7956
|
+
*/
|
|
7957
|
+
static convertFromAbp = (scriptletNode) => {
|
|
7958
|
+
return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
|
|
7959
|
+
};
|
|
7960
|
+
/**
|
|
7961
|
+
* Convert a uBO scriptlet node to ADG scriptlet node, if possible.
|
|
7962
|
+
*
|
|
7963
|
+
* @param scriptletNode Scriptlet node to convert
|
|
7964
|
+
* @returns Converted scriptlet node
|
|
7965
|
+
* @throws If the scriptlet isn't supported by ADG or is invalid
|
|
7966
|
+
* @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
|
|
7967
|
+
*/
|
|
7968
|
+
static convertFromUbo = (scriptletNode) => {
|
|
7969
|
+
return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
|
|
7970
|
+
};
|
|
7971
|
+
}
|
|
7972
|
+
|
|
7973
|
+
/**
|
|
7974
|
+
* @file Scriptlet injection rule converter
|
|
7975
|
+
*/
|
|
7976
|
+
/**
|
|
7977
|
+
* Scriptlet injection rule converter class
|
|
7978
|
+
*
|
|
7979
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
7980
|
+
*/
|
|
7981
|
+
class ScriptletRuleConverter extends RuleConverterBase {
|
|
7982
|
+
/**
|
|
7983
|
+
* Converts a scriptlet injection rule to AdGuard format, if possible.
|
|
7984
|
+
*
|
|
7985
|
+
* @param rule Rule node to convert
|
|
7986
|
+
* @returns Array of converted rule nodes
|
|
7987
|
+
* @throws If the rule is invalid or cannot be converted
|
|
7988
|
+
*/
|
|
7989
|
+
static convertToAdg(rule) {
|
|
7990
|
+
// Clone the provided AST node to avoid side effects
|
|
7991
|
+
const ruleNode = cloneDeep(rule);
|
|
7992
|
+
const convertedScriptlets = [];
|
|
7993
|
+
for (const scriptlet of ruleNode.body.children) {
|
|
7994
|
+
if (ruleNode.syntax === AdblockSyntax.Abp) {
|
|
7995
|
+
convertedScriptlets.push(AdgScriptletConverter.convertFromAbp(scriptlet));
|
|
7996
|
+
}
|
|
7997
|
+
else if (ruleNode.syntax === AdblockSyntax.Ubo) {
|
|
7998
|
+
convertedScriptlets.push(AdgScriptletConverter.convertFromUbo(scriptlet));
|
|
7999
|
+
}
|
|
8000
|
+
else if (ruleNode.syntax === AdblockSyntax.Adg) {
|
|
8001
|
+
convertedScriptlets.push(scriptlet);
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
ruleNode.separator.value = ruleNode.exception
|
|
8005
|
+
? CosmeticRuleSeparator.AdgJsInjectionException
|
|
8006
|
+
: CosmeticRuleSeparator.AdgJsInjection;
|
|
8007
|
+
// ADG doesn't support multiple scriptlets in one rule, so we should split them
|
|
8008
|
+
return convertedScriptlets.map((scriptlet) => {
|
|
8009
|
+
return {
|
|
8010
|
+
...ruleNode,
|
|
8011
|
+
syntax: AdblockSyntax.Adg,
|
|
8012
|
+
body: {
|
|
8013
|
+
...ruleNode.body,
|
|
8014
|
+
children: [scriptlet],
|
|
8015
|
+
},
|
|
8016
|
+
};
|
|
8017
|
+
});
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
/**
|
|
8022
|
+
* @file Cosmetic rule modifier converter from uBO to ADG
|
|
8023
|
+
*/
|
|
8024
|
+
const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
|
|
8025
|
+
const ADG_PATH_MODIFIER = 'path';
|
|
8026
|
+
/**
|
|
8027
|
+
* Special characters in modifier regexps that should be escaped
|
|
8028
|
+
*/
|
|
8029
|
+
const SPECIAL_MODIFIER_REGEX_CHARS = new Set([
|
|
8030
|
+
OPEN_SQUARE_BRACKET,
|
|
8031
|
+
CLOSE_SQUARE_BRACKET,
|
|
8032
|
+
COMMA,
|
|
8033
|
+
ESCAPE_CHARACTER,
|
|
8034
|
+
]);
|
|
8035
|
+
/**
|
|
8036
|
+
* Helper class for converting cosmetic rule modifiers from uBO to ADG
|
|
8037
|
+
*/
|
|
8038
|
+
class AdgCosmeticRuleModifierConverter {
|
|
8039
|
+
/**
|
|
8040
|
+
* Converts a uBO cosmetic rule modifier list to ADG, if possible.
|
|
8041
|
+
*
|
|
8042
|
+
* @param modifierList Cosmetic rule modifier list node to convert
|
|
8043
|
+
* @returns Converted cosmetic rule modifier list node
|
|
8044
|
+
* @throws If the modifier list cannot be converted
|
|
8045
|
+
* @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
|
|
8046
|
+
*/
|
|
8047
|
+
static convertFromUbo = (modifierList) => {
|
|
8048
|
+
const convertedModifierList = createModifierListNode();
|
|
8049
|
+
for (const modifier of modifierList.children) {
|
|
8050
|
+
let modifierValue;
|
|
8051
|
+
switch (modifier.modifier.value) {
|
|
8052
|
+
case UBO_MATCHES_PATH_OPERATOR:
|
|
8053
|
+
// :matches-path() should have a value
|
|
8054
|
+
if (!modifier.value) {
|
|
8055
|
+
throw new RuleConversionError('Missing value for :matches-path(...)');
|
|
8056
|
+
}
|
|
8057
|
+
modifierValue = RegExpUtils.isRegexPattern(modifier.value.value)
|
|
8058
|
+
? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
|
|
8059
|
+
: modifier.value.value;
|
|
8060
|
+
// Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
|
|
8061
|
+
convertedModifierList.children.push(createModifierNode(ADG_PATH_MODIFIER,
|
|
8062
|
+
// We should negate the regexp if the modifier is an exception
|
|
8063
|
+
modifier.exception
|
|
8064
|
+
// eslint-disable-next-line max-len
|
|
8065
|
+
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
|
|
8066
|
+
: modifierValue));
|
|
8067
|
+
break;
|
|
8068
|
+
default:
|
|
8069
|
+
// Leave the modifier as-is
|
|
8070
|
+
convertedModifierList.children.push(modifier);
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
return convertedModifierList;
|
|
8074
|
+
};
|
|
8075
|
+
}
|
|
8076
|
+
|
|
8077
|
+
// Constants for pseudo-classes (please keep them sorted alphabetically)
|
|
8078
|
+
const ABP_CONTAINS = '-abp-contains';
|
|
8079
|
+
const ABP_HAS = '-abp-has';
|
|
8080
|
+
const CONTAINS = 'contains';
|
|
8081
|
+
const HAS = 'has';
|
|
8082
|
+
const HAS_TEXT = 'has-text';
|
|
8083
|
+
const MATCHES_CSS = 'matches-css';
|
|
8084
|
+
const MATCHES_CSS_AFTER = 'matches-css-after';
|
|
8085
|
+
const MATCHES_CSS_BEFORE = 'matches-css-before';
|
|
8086
|
+
const NOT = 'not';
|
|
8087
|
+
// Constants for pseudo-elements (please keep them sorted alphabetically)
|
|
8088
|
+
const AFTER = 'after';
|
|
8089
|
+
const BEFORE = 'before';
|
|
8090
|
+
/**
|
|
8091
|
+
* Converts some pseudo-classes to pseudo-elements. For example:
|
|
8092
|
+
* - `:before` → `::before`
|
|
8093
|
+
*
|
|
8094
|
+
* @param selectorList Selector list to convert
|
|
8095
|
+
* @returns Converted selector list
|
|
8096
|
+
*/
|
|
8097
|
+
function convertToPseudoElements(selectorList) {
|
|
8098
|
+
// Prepare conversion result
|
|
8099
|
+
const selectorListClone = cloneDeep(selectorList);
|
|
8100
|
+
walk(selectorListClone, {
|
|
8101
|
+
leave: (node) => {
|
|
8102
|
+
if (node.type === CssTreeNodeType.PseudoClassSelector) {
|
|
8103
|
+
// :after → ::after
|
|
8104
|
+
// :before → ::before
|
|
8105
|
+
if (node.name === AFTER || node.name === BEFORE) {
|
|
8106
|
+
Object.assign(node, {
|
|
8107
|
+
...node,
|
|
8108
|
+
type: CssTreeNodeType.PseudoElementSelector,
|
|
8109
|
+
});
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8112
|
+
},
|
|
8113
|
+
});
|
|
8114
|
+
return selectorListClone;
|
|
8115
|
+
}
|
|
8116
|
+
/**
|
|
8117
|
+
* Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
|
|
8118
|
+
* pseudo-classes to the new 'matches-css' pseudo-class:
|
|
8119
|
+
* - `:matches-css-before(...)` → `:matches-css(before, ...)`
|
|
8120
|
+
* - `:matches-css-after(...)` → `:matches-css(after, ...)`
|
|
8121
|
+
*
|
|
8122
|
+
* @param node Node to convert
|
|
8123
|
+
* @throws If the node is invalid
|
|
8124
|
+
*/
|
|
8125
|
+
function convertLegacyMatchesCss(node) {
|
|
8126
|
+
const nodeClone = cloneDeep(node);
|
|
8127
|
+
if (nodeClone.type === CssTreeNodeType.PseudoClassSelector
|
|
8128
|
+
&& [MATCHES_CSS_BEFORE, MATCHES_CSS_AFTER].includes(nodeClone.name)) {
|
|
8129
|
+
if (!nodeClone.children || nodeClone.children.size < 1) {
|
|
8130
|
+
throw new Error(`Invalid ${nodeClone.name} pseudo-class: missing argument`);
|
|
8131
|
+
}
|
|
8132
|
+
// Remove the 'matches-css-' prefix to get the direction
|
|
8133
|
+
const direction = nodeClone.name.substring(MATCHES_CSS.length + 1);
|
|
8134
|
+
// Rename the pseudo-class
|
|
8135
|
+
nodeClone.name = MATCHES_CSS;
|
|
8136
|
+
// Add the direction to the first raw argument
|
|
8137
|
+
const arg = nodeClone.children.first;
|
|
8138
|
+
// Check argument
|
|
8139
|
+
if (!arg) {
|
|
8140
|
+
throw new Error(`Invalid ${nodeClone.name} pseudo-class: argument shouldn't be null`);
|
|
8141
|
+
}
|
|
8142
|
+
if (arg.type !== CssTreeNodeType.Raw) {
|
|
8143
|
+
throw new Error(`Invalid ${nodeClone.name} pseudo-class: unexpected argument type`);
|
|
8144
|
+
}
|
|
8145
|
+
// Add the direction as the first argument
|
|
8146
|
+
arg.value = `${direction},${arg.value}`;
|
|
8147
|
+
// Replace the original node with the converted one
|
|
8148
|
+
Object.assign(node, nodeClone);
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8151
|
+
/**
|
|
8152
|
+
* Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
|
|
8153
|
+
* For example:
|
|
8154
|
+
* - `[-ext-has=...]` → `:has(...)`
|
|
8155
|
+
* - `[-ext-contains=...]` → `:contains(...)`
|
|
8156
|
+
* - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
|
|
8157
|
+
*
|
|
8158
|
+
* @param selectorList Selector list AST to convert
|
|
8159
|
+
* @returns Converted selector list
|
|
8160
|
+
*/
|
|
8161
|
+
function convertFromLegacyExtendedCss(selectorList) {
|
|
8162
|
+
// Prepare conversion result
|
|
8163
|
+
const selectorListClone = cloneDeep(selectorList);
|
|
8164
|
+
walk(selectorListClone, {
|
|
8165
|
+
leave: (node) => {
|
|
8166
|
+
// :matches-css-before(arg) → :matches-css(before,arg)
|
|
8167
|
+
// :matches-css-after(arg) → :matches-css(after,arg)
|
|
8168
|
+
convertLegacyMatchesCss(node);
|
|
8169
|
+
// [-ext-name=...] → :name(...)
|
|
8170
|
+
// [-ext-name='...'] → :name(...)
|
|
8171
|
+
// [-ext-name="..."] → :name(...)
|
|
8172
|
+
if (node.type === CssTreeNodeType.AttributeSelector
|
|
8173
|
+
&& node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)
|
|
8174
|
+
&& node.matcher === EQUALS) {
|
|
8175
|
+
// Node value should be exist
|
|
8176
|
+
if (!node.value) {
|
|
8177
|
+
throw new Error(`Invalid ${node.name} attribute selector: missing value`);
|
|
8178
|
+
}
|
|
8179
|
+
// Remove the '-ext-' prefix to get the pseudo-class name
|
|
8180
|
+
const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
|
|
8181
|
+
// Prepare the children list for the pseudo-class node
|
|
8182
|
+
const children = new List();
|
|
8183
|
+
// TODO: Change String node to Raw node to drop the quotes.
|
|
8184
|
+
// The structure of the node is the same, just the type
|
|
8185
|
+
// is different and generate() will generate the quotes
|
|
8186
|
+
// for String node. See:
|
|
8187
|
+
// - https://github.com/csstree/csstree/blob/master/docs/ast.md#string
|
|
8188
|
+
// - https://github.com/csstree/csstree/blob/master/docs/ast.md#raw
|
|
8189
|
+
// if (node.value.type === "String") {
|
|
8190
|
+
// node.value.type = "Raw";
|
|
8191
|
+
// }
|
|
8192
|
+
// For example, if the input is [-ext-has="> .selector"], then
|
|
8193
|
+
// we need to parse "> .selector" as a selector instead of string
|
|
8194
|
+
// it as a raw value
|
|
8195
|
+
if ([HAS, NOT].includes(name)) {
|
|
8196
|
+
// Get the value of the attribute selector
|
|
8197
|
+
const { value } = node;
|
|
8198
|
+
// If the value is an identifier, then simply push it to the
|
|
8199
|
+
// children list, otherwise parse it as a selector list before
|
|
8200
|
+
if (value.type === CssTreeNodeType.Identifier) {
|
|
8201
|
+
children.push(value);
|
|
8202
|
+
}
|
|
8203
|
+
else if (value.type === CssTreeNodeType.String) {
|
|
8204
|
+
// Parse the value as a selector
|
|
8205
|
+
const parsedChildren = CssTree.parse(value.value, CssTreeParserContext.selectorList);
|
|
8206
|
+
// Don't forget convert the parsed AST again, because
|
|
8207
|
+
// it was a raw string before
|
|
8208
|
+
children.push(convertFromLegacyExtendedCss(parsedChildren));
|
|
8209
|
+
}
|
|
8210
|
+
}
|
|
8211
|
+
else {
|
|
8212
|
+
let value = EMPTY;
|
|
8213
|
+
if (node.value.type === CssTreeNodeType.String) {
|
|
8214
|
+
// If the value is a string, then use its value
|
|
8215
|
+
value = node.value.value;
|
|
8216
|
+
}
|
|
8217
|
+
else if (node.value.type === CssTreeNodeType.Identifier) {
|
|
8218
|
+
// If the value is an identifier, then use its name
|
|
8219
|
+
value = node.value.name;
|
|
8220
|
+
}
|
|
8221
|
+
// In other cases, convert value to raw
|
|
8222
|
+
children.push({
|
|
8223
|
+
type: CssTreeNodeType.Raw,
|
|
8224
|
+
value,
|
|
8225
|
+
});
|
|
8226
|
+
}
|
|
8227
|
+
// Create a pseudo-class node with the data from the attribute
|
|
8228
|
+
// selector
|
|
8229
|
+
const pseudoNode = {
|
|
8230
|
+
type: CssTreeNodeType.PseudoClassSelector,
|
|
8231
|
+
name,
|
|
8232
|
+
children,
|
|
8233
|
+
};
|
|
8234
|
+
// Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
|
|
8235
|
+
convertLegacyMatchesCss(pseudoNode);
|
|
8236
|
+
// Convert attribute selector to pseudo-class selector, but
|
|
8237
|
+
// keep the reference to the original node
|
|
8238
|
+
Object.assign(node, pseudoNode);
|
|
8239
|
+
}
|
|
8240
|
+
},
|
|
8241
|
+
});
|
|
8242
|
+
return selectorListClone;
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* CSS selector converter
|
|
8246
|
+
*
|
|
8247
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8248
|
+
*/
|
|
8249
|
+
class CssSelectorConverter extends ConverterBase {
|
|
8250
|
+
/**
|
|
8251
|
+
* Converts Extended CSS elements to AdGuard-compatible ones
|
|
8252
|
+
*
|
|
8253
|
+
* @param selectorList Selector list to convert
|
|
8254
|
+
* @returns Converted selector list
|
|
8255
|
+
* @throws If the rule is invalid or incompatible
|
|
8256
|
+
*/
|
|
8257
|
+
static convertToAdg(selectorList) {
|
|
8258
|
+
// First, convert
|
|
8259
|
+
// - legacy Extended CSS selectors to the modern Extended CSS syntax and
|
|
8260
|
+
// - some pseudo-classes to pseudo-elements
|
|
8261
|
+
const selectorListClone = convertToPseudoElements(convertFromLegacyExtendedCss(cloneDeep(selectorList)));
|
|
8262
|
+
// Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
|
|
8263
|
+
walk(selectorListClone, {
|
|
8264
|
+
leave: (node) => {
|
|
8265
|
+
if (node.type === CssTreeNodeType.PseudoClassSelector) {
|
|
8266
|
+
// :-abp-contains(...) → :contains(...)
|
|
8267
|
+
// :has-text(...) → :contains(...)
|
|
8268
|
+
if (node.name === ABP_CONTAINS || node.name === HAS_TEXT) {
|
|
8269
|
+
CssTree.renamePseudoClass(node, CONTAINS);
|
|
8270
|
+
}
|
|
8271
|
+
// :-abp-has(...) → :has(...)
|
|
8272
|
+
if (node.name === ABP_HAS) {
|
|
8273
|
+
CssTree.renamePseudoClass(node, HAS);
|
|
8274
|
+
}
|
|
8275
|
+
// TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
|
|
8276
|
+
}
|
|
8277
|
+
},
|
|
8278
|
+
});
|
|
8279
|
+
return selectorListClone;
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
|
|
8283
|
+
/**
|
|
8284
|
+
* @file CSS injection rule converter
|
|
8285
|
+
*/
|
|
8286
|
+
/**
|
|
8287
|
+
* CSS injection rule converter class
|
|
8288
|
+
*
|
|
8289
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8290
|
+
*/
|
|
8291
|
+
class CssInjectionRuleConverter extends RuleConverterBase {
|
|
8292
|
+
/**
|
|
8293
|
+
* Converts a CSS injection rule to AdGuard format, if possible.
|
|
8294
|
+
*
|
|
8295
|
+
* @param rule Rule node to convert
|
|
8296
|
+
* @returns Array of converted rule nodes
|
|
8297
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8298
|
+
*/
|
|
8299
|
+
static convertToAdg(rule) {
|
|
8300
|
+
// Clone the provided AST node to avoid side effects
|
|
8301
|
+
const ruleNode = cloneDeep(rule);
|
|
8302
|
+
// Change the separator if the rule contains ExtendedCSS selectors
|
|
8303
|
+
if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList) || ruleNode.body.remove) {
|
|
8304
|
+
ruleNode.separator.value = ruleNode.exception
|
|
8305
|
+
? CosmeticRuleSeparator.AdgExtendedCssInjectionException
|
|
8306
|
+
: CosmeticRuleSeparator.AdgExtendedCssInjection;
|
|
8307
|
+
}
|
|
8308
|
+
else {
|
|
8309
|
+
ruleNode.separator.value = ruleNode.exception
|
|
8310
|
+
? CosmeticRuleSeparator.AdgCssInjectionException
|
|
8311
|
+
: CosmeticRuleSeparator.AdgCssInjection;
|
|
8312
|
+
}
|
|
8313
|
+
// Convert CSS selector list
|
|
8314
|
+
Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
|
|
8315
|
+
ruleNode.syntax = AdblockSyntax.Adg;
|
|
8316
|
+
return [ruleNode];
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
/**
|
|
8321
|
+
* @file Element hiding rule converter
|
|
8322
|
+
*/
|
|
8323
|
+
/**
|
|
8324
|
+
* Element hiding rule converter class
|
|
8325
|
+
*
|
|
8326
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8327
|
+
*/
|
|
8328
|
+
class ElementHidingRuleConverter extends RuleConverterBase {
|
|
8329
|
+
/**
|
|
8330
|
+
* Converts an element hiding rule to AdGuard format, if possible.
|
|
8331
|
+
*
|
|
8332
|
+
* @param rule Rule node to convert
|
|
8333
|
+
* @returns Array of converted rule nodes
|
|
8334
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8335
|
+
*/
|
|
8336
|
+
static convertToAdg(rule) {
|
|
8337
|
+
// Clone the provided AST node to avoid side effects
|
|
8338
|
+
const ruleNode = cloneDeep(rule);
|
|
8339
|
+
// Change the separator if the rule contains ExtendedCSS selectors
|
|
8340
|
+
if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList)) {
|
|
8341
|
+
ruleNode.separator.value = ruleNode.exception
|
|
8342
|
+
? CosmeticRuleSeparator.ExtendedElementHidingException
|
|
8343
|
+
: CosmeticRuleSeparator.ExtendedElementHiding;
|
|
8344
|
+
}
|
|
8345
|
+
else {
|
|
8346
|
+
ruleNode.separator.value = ruleNode.exception
|
|
8347
|
+
? CosmeticRuleSeparator.ElementHidingException
|
|
8348
|
+
: CosmeticRuleSeparator.ElementHiding;
|
|
8349
|
+
}
|
|
8350
|
+
// Convert CSS selector list
|
|
8351
|
+
Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
|
|
8352
|
+
ruleNode.syntax = AdblockSyntax.Adg;
|
|
8353
|
+
return [ruleNode];
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
|
|
8357
|
+
/**
|
|
8358
|
+
* @file Utility functions for working with network rule nodes
|
|
8359
|
+
*/
|
|
8360
|
+
/**
|
|
8361
|
+
* Creates a network rule node
|
|
8362
|
+
*
|
|
8363
|
+
* @param pattern Rule pattern
|
|
8364
|
+
* @param modifiers Rule modifiers (optional, default: undefined)
|
|
8365
|
+
* @param exception Exception rule flag (optional, default: false)
|
|
8366
|
+
* @param syntax Adblock syntax (optional, default: Common)
|
|
8367
|
+
* @returns Network rule node
|
|
8368
|
+
*/
|
|
8369
|
+
function createNetworkRuleNode(pattern, modifiers = undefined, exception = false, syntax = AdblockSyntax.Common) {
|
|
8370
|
+
const result = {
|
|
8371
|
+
category: RuleCategory.Network,
|
|
8372
|
+
type: 'NetworkRule',
|
|
8373
|
+
syntax,
|
|
8374
|
+
exception,
|
|
8375
|
+
pattern: {
|
|
8376
|
+
type: 'Value',
|
|
8377
|
+
value: pattern,
|
|
8378
|
+
},
|
|
8379
|
+
};
|
|
8380
|
+
if (!isUndefined(modifiers)) {
|
|
8381
|
+
result.modifiers = cloneDeep(modifiers);
|
|
8382
|
+
}
|
|
8383
|
+
return result;
|
|
8384
|
+
}
|
|
8385
|
+
|
|
8386
|
+
/**
|
|
8387
|
+
* @file Converter for request header removal rules
|
|
8388
|
+
*/
|
|
8389
|
+
const UBO_RESPONSEHEADER_MARKER = 'responseheader';
|
|
8390
|
+
const ADG_REMOVEHEADER_MODIFIER = 'removeheader';
|
|
8391
|
+
/**
|
|
8392
|
+
* Converter for request header removal rules
|
|
8393
|
+
*
|
|
8394
|
+
* @todo Implement `convertToUbo` (ABP currently doesn't support header removal rules)
|
|
8395
|
+
*/
|
|
8396
|
+
class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
8397
|
+
/**
|
|
8398
|
+
* Converts a header removal rule to AdGuard syntax, if possible.
|
|
8399
|
+
*
|
|
8400
|
+
* @param rule Rule node to convert
|
|
8401
|
+
* @returns Array of converted rule nodes
|
|
8402
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8403
|
+
*/
|
|
8404
|
+
static convertToAdg(rule) {
|
|
8405
|
+
// Clone the provided AST node to avoid side effects
|
|
8406
|
+
const ruleNode = cloneDeep(rule);
|
|
8407
|
+
// TODO: Add support for ABP syntax once it starts supporting header removal rules
|
|
8408
|
+
// Check the input rule
|
|
8409
|
+
if (ruleNode.category !== RuleCategory.Cosmetic
|
|
8410
|
+
|| ruleNode.type !== CosmeticRuleType.HtmlFilteringRule
|
|
8411
|
+
|| ruleNode.body.body.type !== CssTreeNodeType.Function
|
|
8412
|
+
|| ruleNode.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
|
|
8413
|
+
throw new RuleConversionError('Not a response header rule');
|
|
8414
|
+
}
|
|
8415
|
+
// Prepare network rule pattern
|
|
8416
|
+
let pattern = EMPTY;
|
|
8417
|
+
if (ruleNode.domains.children.length === 1) {
|
|
8418
|
+
// If the rule has only one domain, we can use a simple network rule pattern:
|
|
8419
|
+
// ||single-domain-from-the-rule^
|
|
8420
|
+
pattern = [
|
|
8421
|
+
ADBLOCK_URL_START,
|
|
8422
|
+
ruleNode.domains.children[0].value,
|
|
8423
|
+
ADBLOCK_URL_SEPARATOR,
|
|
8424
|
+
].join(EMPTY);
|
|
8425
|
+
}
|
|
8426
|
+
else if (ruleNode.domains.children.length > 1) {
|
|
8427
|
+
// TODO: Add support for multiple domains, for example:
|
|
8428
|
+
// example.com,example.org,example.net##^responseheader(header-name)
|
|
8429
|
+
// We should consider allowing $domain with $removeheader modifier,
|
|
8430
|
+
// for example:
|
|
8431
|
+
// $removeheader=header-name,domain=example.com|example.org|example.net
|
|
8432
|
+
throw new RuleConversionError('Multiple domains are not supported yet');
|
|
8433
|
+
}
|
|
8434
|
+
// Prepare network rule modifiers
|
|
8435
|
+
const modifiers = createModifierListNode();
|
|
8436
|
+
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionValue(fromPlainObject(ruleNode.body.body))));
|
|
8437
|
+
// Construct the network rule
|
|
8438
|
+
return [
|
|
8439
|
+
createNetworkRuleNode(pattern, modifiers,
|
|
8440
|
+
// Copy the exception flag
|
|
8441
|
+
ruleNode.exception, AdblockSyntax.Adg),
|
|
8442
|
+
];
|
|
8443
|
+
}
|
|
8444
|
+
}
|
|
8445
|
+
|
|
8446
|
+
/**
|
|
8447
|
+
* @file Cosmetic rule converter
|
|
8448
|
+
*/
|
|
8449
|
+
/**
|
|
8450
|
+
* Cosmetic rule converter class (also known as "non-basic rule converter")
|
|
8451
|
+
*
|
|
8452
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8453
|
+
*/
|
|
8454
|
+
class CosmeticRuleConverter extends RuleConverterBase {
|
|
8455
|
+
/**
|
|
8456
|
+
* Converts a cosmetic rule to AdGuard syntax, if possible.
|
|
8457
|
+
*
|
|
8458
|
+
* @param rule Rule node to convert
|
|
8459
|
+
* @returns Array of converted rule nodes
|
|
8460
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8461
|
+
*/
|
|
8462
|
+
static convertToAdg(rule) {
|
|
8463
|
+
// Clone the provided AST node to avoid side effects
|
|
8464
|
+
const ruleNode = cloneDeep(rule);
|
|
8465
|
+
// Convert cosmetic rule modifiers
|
|
8466
|
+
if (ruleNode.modifiers) {
|
|
8467
|
+
if (ruleNode.syntax === AdblockSyntax.Ubo) {
|
|
8468
|
+
// uBO doesn't support this rule:
|
|
8469
|
+
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
8470
|
+
if (ruleNode.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
8471
|
+
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
8472
|
+
}
|
|
8473
|
+
ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
|
|
8474
|
+
}
|
|
8475
|
+
else if (ruleNode.syntax === AdblockSyntax.Abp) {
|
|
8476
|
+
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
8477
|
+
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
// Convert cosmetic rule based on its type
|
|
8481
|
+
switch (ruleNode.type) {
|
|
8482
|
+
case CosmeticRuleType.ElementHidingRule:
|
|
8483
|
+
return ElementHidingRuleConverter.convertToAdg(ruleNode);
|
|
8484
|
+
case CosmeticRuleType.ScriptletInjectionRule:
|
|
8485
|
+
return ScriptletRuleConverter.convertToAdg(ruleNode);
|
|
8486
|
+
case CosmeticRuleType.CssInjectionRule:
|
|
8487
|
+
return CssInjectionRuleConverter.convertToAdg(ruleNode);
|
|
8488
|
+
case CosmeticRuleType.HtmlFilteringRule:
|
|
8489
|
+
// Handle special case: uBO response header filtering rule
|
|
8490
|
+
if (ruleNode.body.body.type === CssTreeNodeType.Function
|
|
8491
|
+
&& ruleNode.body.body.name === UBO_RESPONSEHEADER_MARKER) {
|
|
8492
|
+
return HeaderRemovalRuleConverter.convertToAdg(ruleNode);
|
|
8493
|
+
}
|
|
8494
|
+
return HtmlRuleConverter.convertToAdg(ruleNode);
|
|
8495
|
+
// Note: Currently, only ADG supports JS injection rules
|
|
8496
|
+
case CosmeticRuleType.JsInjectionRule:
|
|
8497
|
+
return [ruleNode];
|
|
8498
|
+
default:
|
|
8499
|
+
throw new RuleConversionError('Unsupported cosmetic rule type');
|
|
8500
|
+
}
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
|
|
8504
|
+
/**
|
|
8505
|
+
* @file Network rule modifier list converter.
|
|
8506
|
+
*/
|
|
8507
|
+
/**
|
|
8508
|
+
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#csp-modifier}
|
|
8509
|
+
*/
|
|
8510
|
+
const CSP_MODIFIER = 'csp';
|
|
8511
|
+
const CSP_SEPARATOR = SEMICOLON + SPACE;
|
|
8512
|
+
/**
|
|
8513
|
+
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#csp-modifier}
|
|
8514
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy}
|
|
8515
|
+
*/
|
|
8516
|
+
const COMMON_CSP_PARAMS = '\'self\' \'unsafe-eval\' http: https: data: blob: mediastream: filesystem:';
|
|
8517
|
+
/**
|
|
8518
|
+
* @see {@link https://help.adblockplus.org/hc/en-us/articles/360062733293#rewrite}
|
|
8519
|
+
*/
|
|
8520
|
+
const ABP_REWRITE_MODIFIER = 'rewrite';
|
|
8521
|
+
/**
|
|
8522
|
+
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#redirect-modifier}
|
|
8523
|
+
*/
|
|
8524
|
+
const REDIRECT_MODIFIER = 'redirect';
|
|
8525
|
+
/**
|
|
8526
|
+
* @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier}
|
|
8527
|
+
*/
|
|
8528
|
+
const REDIRECT_RULE_MODIFIER = 'redirect-rule';
|
|
8529
|
+
/**
|
|
8530
|
+
* Redirect-related modifiers.
|
|
8531
|
+
*/
|
|
8532
|
+
const REDIRECT_MODIFIERS = new Set([
|
|
8533
|
+
ABP_REWRITE_MODIFIER,
|
|
8534
|
+
REDIRECT_MODIFIER,
|
|
8535
|
+
REDIRECT_RULE_MODIFIER,
|
|
8536
|
+
]);
|
|
8537
|
+
/**
|
|
8538
|
+
* Conversion map for ADG network rule modifiers.
|
|
8539
|
+
*/
|
|
8540
|
+
const ADG_CONVERSION_MAP = new Map([
|
|
8541
|
+
['1p', [{ name: () => 'third-party', exception: (actual) => !actual }]],
|
|
8542
|
+
['3p', [{ name: () => 'third-party' }]],
|
|
8543
|
+
['css', [{ name: () => 'stylesheet' }]],
|
|
8544
|
+
['doc', [{ name: () => 'document' }]],
|
|
8545
|
+
['ehide', [{ name: () => 'elemhide' }]],
|
|
8546
|
+
['empty', [{ name: () => 'redirect', value: () => 'nooptext' }]],
|
|
8547
|
+
['first-party', [{ name: () => 'third-party', exception: (actual) => !actual }]],
|
|
8548
|
+
['frame', [{ name: () => 'subdocument' }]],
|
|
8549
|
+
['ghide', [{ name: () => 'generichide' }]],
|
|
8550
|
+
['inline-font', [{ name: () => CSP_MODIFIER, value: () => `font-src ${COMMON_CSP_PARAMS}` }]],
|
|
8551
|
+
['inline-script', [{ name: () => CSP_MODIFIER, value: () => `script-src ${COMMON_CSP_PARAMS}` }]],
|
|
8552
|
+
['mp4', [{ name: () => 'redirect', value: () => 'noopmp4-1s' }, { name: () => 'media', value: () => undefined }]],
|
|
8553
|
+
['queryprune', [{ name: () => 'removeparam' }]],
|
|
8554
|
+
['shide', [{ name: () => 'specifichide' }]],
|
|
8555
|
+
['xhr', [{ name: () => 'xmlhttprequest' }]],
|
|
8556
|
+
]);
|
|
8557
|
+
/**
|
|
8558
|
+
* Helper class for converting network rule modifier lists.
|
|
8559
|
+
*
|
|
8560
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8561
|
+
*/
|
|
8562
|
+
class NetworkRuleModifierListConverter extends ConverterBase {
|
|
8563
|
+
/**
|
|
8564
|
+
* Converts a network rule modifier list to AdGuard format, if possible.
|
|
8565
|
+
*
|
|
8566
|
+
* @param modifierList Network rule modifier list node to convert
|
|
8567
|
+
* @returns Converted modifier list node
|
|
8568
|
+
* @throws If the conversion is not possible
|
|
8569
|
+
*/
|
|
8570
|
+
static convertToAdg(modifierList) {
|
|
8571
|
+
// Clone the provided AST node to avoid side effects
|
|
8572
|
+
const modifierListNode = cloneDeep(modifierList);
|
|
8573
|
+
const convertedModifierList = createModifierListNode();
|
|
8574
|
+
// We should merge $csp modifiers into one
|
|
8575
|
+
const cspValues = [];
|
|
8576
|
+
modifierListNode.children.forEach((modifierNode) => {
|
|
8577
|
+
// Handle regular modifiers conversion and $csp modifiers collection
|
|
8578
|
+
const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
|
|
8579
|
+
if (modifierConversions) {
|
|
8580
|
+
for (const modifierConversion of modifierConversions) {
|
|
8581
|
+
const name = modifierConversion.name(modifierNode.modifier.value);
|
|
8582
|
+
const exception = modifierConversion.exception
|
|
8583
|
+
// If the exception value is undefined in the original modifier, it
|
|
8584
|
+
// means that the modifier isn't negated
|
|
8585
|
+
? modifierConversion.exception(modifierNode.exception || false)
|
|
8586
|
+
: modifierNode.exception;
|
|
8587
|
+
const value = modifierConversion.value
|
|
8588
|
+
? modifierConversion.value(modifierNode.value?.value)
|
|
8589
|
+
: modifierNode.value?.value;
|
|
8590
|
+
if (name === CSP_MODIFIER && value) {
|
|
8591
|
+
// Special case: collect $csp values
|
|
8592
|
+
cspValues.push(value);
|
|
8593
|
+
}
|
|
8594
|
+
else {
|
|
8595
|
+
// Regular case: collect the converted modifiers, if the modifier list
|
|
8596
|
+
// not already contains the same modifier
|
|
8597
|
+
const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
|
|
8598
|
+
if (!existingModifier) {
|
|
8599
|
+
convertedModifierList.children.push(createModifierNode(name, value, exception));
|
|
8600
|
+
}
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
return;
|
|
8604
|
+
}
|
|
8605
|
+
// Handle special case: resource redirection modifiers
|
|
8606
|
+
if (REDIRECT_MODIFIERS.has(modifierNode.modifier.value)) {
|
|
8607
|
+
// Redirect modifiers can't be negated
|
|
8608
|
+
if (modifierNode.exception === true) {
|
|
8609
|
+
throw new RuleConversionError(`Modifier '${modifierNode.modifier.value}' cannot be negated`);
|
|
8610
|
+
}
|
|
8611
|
+
// Convert the redirect resource name to ADG format
|
|
8612
|
+
const redirectResource = modifierNode.value?.value;
|
|
8613
|
+
if (!redirectResource) {
|
|
8614
|
+
throw new RuleConversionError(`No redirect resource specified for '${modifierNode.modifier.value}' modifier`);
|
|
8615
|
+
}
|
|
8616
|
+
// Leave $redirect and $redirect-rule modifiers as is, but convert $rewrite to $redirect
|
|
8617
|
+
const modifierName = modifierNode.modifier.value === ABP_REWRITE_MODIFIER
|
|
8618
|
+
? REDIRECT_MODIFIER
|
|
8619
|
+
: modifierNode.modifier.value;
|
|
8620
|
+
// Try to convert the redirect resource name to ADG format
|
|
8621
|
+
// This function returns undefined if the resource name is unknown
|
|
8622
|
+
const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
|
|
8623
|
+
convertedModifierList.children.push(createModifierNode(modifierName,
|
|
8624
|
+
// If the redirect resource name is unknown, fall back to the original one
|
|
8625
|
+
// Later, the validator will throw an error if the resource name is invalid
|
|
8626
|
+
convertedRedirectResource || redirectResource, modifierNode.exception));
|
|
8627
|
+
return;
|
|
8628
|
+
}
|
|
8629
|
+
// In all other cases, just copy the modifier as is, if the modifier list
|
|
8630
|
+
// not already contains the same modifier
|
|
8631
|
+
const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === modifierNode.modifier.value
|
|
8632
|
+
&& m.exception === modifierNode.exception
|
|
8633
|
+
&& m.value?.value === modifierNode.value?.value);
|
|
8634
|
+
if (!existingModifier) {
|
|
8635
|
+
convertedModifierList.children.push(modifierNode);
|
|
8636
|
+
}
|
|
8637
|
+
});
|
|
8638
|
+
// Merge $csp modifiers into one, then add it to the converted modifier list
|
|
8639
|
+
if (cspValues.length > 0) {
|
|
8640
|
+
convertedModifierList.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
|
|
8641
|
+
}
|
|
8642
|
+
return convertedModifierList;
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8646
|
+
/**
|
|
8647
|
+
* @file Network rule converter
|
|
8648
|
+
*/
|
|
8649
|
+
/**
|
|
8650
|
+
* Network rule converter class (also known as "basic rule converter")
|
|
8651
|
+
*
|
|
8652
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8653
|
+
*/
|
|
8654
|
+
class NetworkRuleConverter extends RuleConverterBase {
|
|
8655
|
+
/**
|
|
8656
|
+
* Converts a network rule to AdGuard format, if possible.
|
|
8657
|
+
*
|
|
8658
|
+
* @param rule Rule node to convert
|
|
8659
|
+
* @returns Array of converted rule nodes
|
|
8660
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8661
|
+
*/
|
|
8662
|
+
static convertToAdg(rule) {
|
|
8663
|
+
// Clone the provided AST node to avoid side effects
|
|
8664
|
+
const ruleNode = cloneDeep(rule);
|
|
8665
|
+
// Convert modifiers
|
|
8666
|
+
if (ruleNode.modifiers) {
|
|
8667
|
+
Object.assign(ruleNode.modifiers, NetworkRuleModifierListConverter.convertToAdg(ruleNode.modifiers));
|
|
8668
|
+
}
|
|
8669
|
+
return [ruleNode];
|
|
8670
|
+
}
|
|
8671
|
+
}
|
|
8672
|
+
|
|
8673
|
+
/**
|
|
8674
|
+
* @file Adblock rule converter
|
|
8675
|
+
*
|
|
8676
|
+
* This file is the entry point for all rule converters
|
|
8677
|
+
* which automatically detects the rule type and calls
|
|
8678
|
+
* the corresponding "sub-converter".
|
|
8679
|
+
*/
|
|
8680
|
+
/**
|
|
8681
|
+
* Adblock filtering rule converter class
|
|
8682
|
+
*
|
|
8683
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8684
|
+
*/
|
|
8685
|
+
class RuleConverter extends RuleConverterBase {
|
|
8686
|
+
/**
|
|
8687
|
+
* Converts an adblock filtering rule to AdGuard format, if possible.
|
|
8688
|
+
*
|
|
8689
|
+
* @param rule Rule node to convert
|
|
8690
|
+
* @returns Array of converted rule nodes
|
|
8691
|
+
* @throws If the rule is invalid or cannot be converted
|
|
8692
|
+
*/
|
|
8693
|
+
static convertToAdg(rule) {
|
|
8694
|
+
// Clone the provided AST node to avoid side effects
|
|
8695
|
+
const ruleNode = cloneDeep(rule);
|
|
8696
|
+
// Delegate conversion to the corresponding sub-converter
|
|
8697
|
+
// based on the rule category
|
|
8698
|
+
switch (ruleNode.category) {
|
|
8699
|
+
case RuleCategory.Comment:
|
|
8700
|
+
return CommentRuleConverter.convertToAdg(ruleNode);
|
|
8701
|
+
case RuleCategory.Cosmetic:
|
|
8702
|
+
return CosmeticRuleConverter.convertToAdg(ruleNode);
|
|
8703
|
+
case RuleCategory.Network:
|
|
8704
|
+
return NetworkRuleConverter.convertToAdg(ruleNode);
|
|
8705
|
+
default:
|
|
8706
|
+
throw new RuleConversionError(`Unknown rule category: ${ruleNode.category}`);
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
}
|
|
8710
|
+
|
|
8711
|
+
/**
|
|
8712
|
+
* @file Utility functions for working with filter list nodes
|
|
8713
|
+
*/
|
|
8714
|
+
/**
|
|
8715
|
+
* Creates a filter list node
|
|
8716
|
+
*
|
|
8717
|
+
* @param rules Rules to put in the list (optional, defaults to an empty list)
|
|
8718
|
+
* @returns Filter list node
|
|
8719
|
+
*/
|
|
8720
|
+
function createFilterListNode(rules = []) {
|
|
8721
|
+
const result = {
|
|
8722
|
+
type: 'FilterList',
|
|
8723
|
+
children: [],
|
|
8724
|
+
};
|
|
8725
|
+
// We need to clone the rules to avoid side effects
|
|
8726
|
+
if (rules.length > 0) {
|
|
8727
|
+
result.children = cloneDeep(rules);
|
|
8728
|
+
}
|
|
8729
|
+
return result;
|
|
8730
|
+
}
|
|
8731
|
+
|
|
8732
|
+
/**
|
|
8733
|
+
* @file Adblock filter list converter
|
|
8734
|
+
*/
|
|
8735
|
+
/**
|
|
8736
|
+
* Adblock filter list converter class
|
|
8737
|
+
*
|
|
8738
|
+
* This class just provides an extra layer on top of the {@link RuleConverter}
|
|
8739
|
+
* and can be used to convert entire filter lists.
|
|
8740
|
+
*
|
|
8741
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
8742
|
+
* @todo Implement tolerant mode, which will allow to convert a filter list
|
|
8743
|
+
* even if some of its rules are invalid
|
|
8744
|
+
*/
|
|
8745
|
+
class FilterListConverter extends ConverterBase {
|
|
8746
|
+
/**
|
|
8747
|
+
* Converts an adblock filter list to AdGuard format, if possible.
|
|
8748
|
+
*
|
|
8749
|
+
* @param filterListNode Filter list node to convert
|
|
8750
|
+
* @returns Converted filter list node
|
|
8751
|
+
* @throws If the filter list is invalid or cannot be converted
|
|
8752
|
+
*/
|
|
8753
|
+
static convertToAdg(filterListNode) {
|
|
8754
|
+
const result = createFilterListNode();
|
|
8755
|
+
// Iterate over the filtering rules and convert them one by one,
|
|
8756
|
+
// then add them to the result (one conversion may result in multiple rules)
|
|
8757
|
+
for (const ruleNode of filterListNode.children) {
|
|
8758
|
+
const convertedRules = RuleConverter.convertToAdg(ruleNode);
|
|
8759
|
+
result.children.push(...convertedRules);
|
|
8760
|
+
}
|
|
8761
|
+
return result;
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
|
|
4839
8765
|
/**
|
|
4840
8766
|
* @file Utility functions for domain and hostname validation.
|
|
4841
8767
|
*/
|
|
@@ -4949,7 +8875,7 @@ class LogicalExpressionUtils {
|
|
|
4949
8875
|
}
|
|
4950
8876
|
}
|
|
4951
8877
|
|
|
4952
|
-
const version$1 = "1.
|
|
8878
|
+
const version$1 = "1.1.1";
|
|
4953
8879
|
|
|
4954
8880
|
/**
|
|
4955
8881
|
* @file AGTree version
|
|
@@ -4960,4 +8886,4 @@ const version$1 = "1.0.1";
|
|
|
4960
8886
|
// with wrong relative path to `package.json`. So we need this little "hack"
|
|
4961
8887
|
const version = version$1;
|
|
4962
8888
|
|
|
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 };
|
|
8889
|
+
export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, CLASSIC_DOMAIN_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DOMAIN_EXCEPTION_MARKER, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, HINT_MARKER, HintCommentRuleParser, HintParser, IF, INCLUDE, LogicalExpressionParser, LogicalExpressionUtils, METADATA_HEADERS, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MODIFIER_DOMAIN_SEPARATOR, MODIFIER_EXCEPTION_MARKER, MetadataCommentRuleParser, ModifierListParser, ModifierParser, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };
|