@adguard/agtree 2.0.0-alpha.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/agtree.cjs +1460 -1274
- package/dist/agtree.d.ts +29 -8
- package/dist/agtree.esm.js +1460 -1274
- package/dist/agtree.iife.min.js +5 -5
- package/dist/agtree.umd.min.js +5 -5
- package/dist/build.txt +1 -1
- package/package.json +2 -2
package/dist/agtree.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* AGTree v2.0.0
|
|
2
|
+
* AGTree v2.0.0 (build date: Thu, 15 Aug 2024 15:06:58 GMT)
|
|
3
3
|
* (c) 2024 Adguard Software Ltd.
|
|
4
4
|
* Released under the MIT license
|
|
5
5
|
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
|
|
@@ -743,6 +743,15 @@ class StringUtils {
|
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Possible operators in the logical expression.
|
|
748
|
+
*/
|
|
749
|
+
var OperatorValue;
|
|
750
|
+
(function (OperatorValue) {
|
|
751
|
+
OperatorValue["Not"] = "!";
|
|
752
|
+
OperatorValue["And"] = "&&";
|
|
753
|
+
OperatorValue["Or"] = "||";
|
|
754
|
+
})(OperatorValue || (OperatorValue = {}));
|
|
746
755
|
/**
|
|
747
756
|
* Represents the different comment markers that can be used in an adblock rule.
|
|
748
757
|
*
|
|
@@ -2938,15 +2947,6 @@ var ParenthesisNodeBinaryPropMap;
|
|
|
2938
2947
|
ParenthesisNodeBinaryPropMap[ParenthesisNodeBinaryPropMap["Start"] = 2] = "Start";
|
|
2939
2948
|
ParenthesisNodeBinaryPropMap[ParenthesisNodeBinaryPropMap["End"] = 3] = "End";
|
|
2940
2949
|
})(ParenthesisNodeBinaryPropMap || (ParenthesisNodeBinaryPropMap = {}));
|
|
2941
|
-
/**
|
|
2942
|
-
* Possible operators in the logical expression.
|
|
2943
|
-
*/
|
|
2944
|
-
var OperatorValue;
|
|
2945
|
-
(function (OperatorValue) {
|
|
2946
|
-
OperatorValue["Not"] = "!";
|
|
2947
|
-
OperatorValue["And"] = "&&";
|
|
2948
|
-
OperatorValue["Or"] = "||";
|
|
2949
|
-
})(OperatorValue || (OperatorValue = {}));
|
|
2950
2950
|
/**
|
|
2951
2951
|
* Possible token types in the logical expression.
|
|
2952
2952
|
*/
|
|
@@ -2992,7 +2992,9 @@ const getOperatorOrFail = binary => {
|
|
|
2992
2992
|
/**
|
|
2993
2993
|
* Serialization map for known variables.
|
|
2994
2994
|
*/
|
|
2995
|
-
const KNOWN_VARIABLES_MAP = new Map([['ext_abp', 0], ['ext_ublock', 1], ['ext_ubol', 2], ['ext_devbuild', 3], ['env_chromium', 4], ['env_edge', 5], ['env_firefox', 6], ['env_mobile', 7], ['env_safari', 8], ['env_mv3', 9], ['false', 10], ['cap_html_filtering', 11], ['cap_user_stylesheet', 12], ['adguard', 13], ['adguard_app_windows', 14], ['adguard_app_mac', 15], ['adguard_app_android', 16], ['adguard_app_ios', 17], ['adguard_ext_safari', 18], ['adguard_ext_chromium', 19], ['adguard_ext_firefox', 20], ['adguard_ext_edge', 21], ['adguard_ext_opera', 22], ['adguard_ext_android_cb', 23]
|
|
2995
|
+
const KNOWN_VARIABLES_MAP = new Map([['ext_abp', 0], ['ext_ublock', 1], ['ext_ubol', 2], ['ext_devbuild', 3], ['env_chromium', 4], ['env_edge', 5], ['env_firefox', 6], ['env_mobile', 7], ['env_safari', 8], ['env_mv3', 9], ['false', 10], ['cap_html_filtering', 11], ['cap_user_stylesheet', 12], ['adguard', 13], ['adguard_app_windows', 14], ['adguard_app_mac', 15], ['adguard_app_android', 16], ['adguard_app_ios', 17], ['adguard_ext_safari', 18], ['adguard_ext_chromium', 19], ['adguard_ext_firefox', 20], ['adguard_ext_edge', 21], ['adguard_ext_opera', 22], ['adguard_ext_android_cb', 23]
|
|
2996
|
+
// TODO: Add 'adguard_ext_chromium_mv3' to the list
|
|
2997
|
+
]);
|
|
2996
2998
|
/**
|
|
2997
2999
|
* Deserialization map for known variables.
|
|
2998
3000
|
*/
|
|
@@ -5696,7 +5698,7 @@ class CssTokenStream {
|
|
|
5696
5698
|
* @returns An array containing the number of tokens skipped and the number of tokens skipped without leading and
|
|
5697
5699
|
* trailing whitespace tokens.
|
|
5698
5700
|
*/
|
|
5699
|
-
|
|
5701
|
+
skipUntilExt(type, balance) {
|
|
5700
5702
|
let i = this.index;
|
|
5701
5703
|
let firstNonWsToken = -1; // -1 means no non-whitespace token found yet
|
|
5702
5704
|
let lastNonWsToken = -1; // -1 means no non-whitespace token found yet
|
|
@@ -5924,7 +5926,7 @@ class AdgCssInjectionParser extends ParserBase {
|
|
|
5924
5926
|
// └ this one
|
|
5925
5927
|
const {
|
|
5926
5928
|
skippedTrimmed: selectorTokensLength
|
|
5927
|
-
} = stream.
|
|
5929
|
+
} = stream.skipUntilExt(TokenType$1.OpenCurlyBracket, balanceShift + 1);
|
|
5928
5930
|
stream.expect(TokenType$1.OpenCurlyBracket);
|
|
5929
5931
|
// If the skipped tokens count is 0 without leading and trailing whitespace characters, then the selector list
|
|
5930
5932
|
// is empty
|
|
@@ -6164,6 +6166,7 @@ class AbpSnippetInjectionBodyParser extends ParserBase {
|
|
|
6164
6166
|
*
|
|
6165
6167
|
* @note Only 256 values can be represented this way.
|
|
6166
6168
|
*/
|
|
6169
|
+
// TODO: Update this map with the actual values
|
|
6167
6170
|
static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-inline-script', 0], ['abort-on-property-read', 1], ['abort-on-property-write', 2], ['json-prune', 3], ['log', 4], ['prevent-listener', 5], ['cookie-remover', 6], ['override-property-read', 7], ['abort-on-iframe-property-read', 8], ['abort-on-iframe-property-write', 9], ['freeze-element', 10], ['json-override', 11], ['simulate-mouse-event', 12], ['strip-fetch-query-parameter', 13], ['hide-if-contains', 14], ['hide-if-contains-image', 15], ['hide-if-contains-image-hash', 16], ['hide-if-contains-similar-text', 17], ['hide-if-contains-visible-text', 18], ['hide-if-contains-and-matches-style', 19], ['hide-if-graph-matches', 20], ['hide-if-has-and-matches-style', 21], ['hide-if-labelled-by', 22], ['hide-if-matches-xpath', 23], ['hide-if-matches-computed-xpath', 24], ['hide-if-shadow-contains', 25], ['debug', 26], ['trace', 27], ['race', 28]]);
|
|
6168
6171
|
/**
|
|
6169
6172
|
* Value map for binary deserialization. This helps to reduce the size of the serialized data,
|
|
@@ -6291,6 +6294,7 @@ class UboScriptletInjectionBodyParser extends ParserBase {
|
|
|
6291
6294
|
*
|
|
6292
6295
|
* @note Only 256 values can be represented this way.
|
|
6293
6296
|
*/
|
|
6297
|
+
// TODO: Update this map with the actual values
|
|
6294
6298
|
static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-script.js', 0], ['acs.js', 1], ['abort-current-inline-script.js', 2], ['acis.js', 3], ['abort-on-property-read.js', 4], ['aopr.js', 5], ['abort-on-property-write.js', 6], ['aopw.js', 7], ['abort-on-stack-trace.js', 8], ['aost.js', 9], ['adjust-setInterval.js', 10], ['nano-setInterval-booster.js', 11], ['nano-sib.js', 12], ['adjust-setTimeout.js', 13], ['nano-setTimeout-booster.js', 14], ['nano-stb.js', 15], ['close-window.js', 16], ['window-close-if.js', 17], ['disable-newtab-links.js', 18], ['evaldata-prune.js', 19], ['json-prune.js', 20], ['addEventListener-logger.js', 21], ['aell.js', 22], ['m3u-prune.js', 23], ['nowebrtc.js', 24], ['addEventListener-defuser.js', 25], ['aeld.js', 26], ['prevent-addEventListener.js', 27], ['adfly-defuser.js', 28], ['noeval-if.js', 29], ['prevent-eval-if.js', 30], ['no-fetch-if.js', 31], ['prevent-fetch.js', 32], ['no-xhr-if.js', 33], ['prevent-xhr.js', 34], ['prevent-refresh.js', 35], ['refresh-defuser.js', 36], ['no-requestAnimationFrame-if.js', 37], ['norafif.js', 38], ['prevent-requestAnimationFrame.js', 39], ['no-setInterval-if.js', 40], ['nosiif.js', 41], ['prevent-setInterval.js', 42], ['setInterval-defuser.js', 43], ['no-setTimeout-if.js', 44], ['nostif.js', 45], ['prevent-setTimeout.js', 46], ['setTimeout-defuser.js', 47], ['no-window-open-if.js', 48], ['nowoif.js', 49], ['prevent-window-open.js', 50], ['window.open-defuser.js', 51], ['remove-attr.js', 52], ['ra.js', 53], ['remove-class.js', 54], ['rc.js', 55], ['remove-cookie.js', 56], ['cookie-remover.js', 57], ['remove-node-text.js', 58], ['rmnt.js', 59], ['set-attr.js', 60], ['set-constant.js', 61], ['set.js', 62], ['set-cookie.js', 63], ['set-local-storage-item.js', 64], ['set-session-storage-item.js', 65], ['xml-prune.js', 66], ['webrtc-if.js', 67], ['overlay-buster.js', 68], ['alert-buster.js', 69], ['golem.de.js', 70], ['href-sanitizer.js', 71], ['call-nothrow.js', 72], ['window.name-defuser.js', 73], ['spoof-css.js', 74], ['trusted-set-constant.js', 75], ['trusted-set.js', 76], ['trusted-set-cookie.js', 77], ['trusted-set-local-storage-item.js', 78], ['trusted-replace-fetch-response.js', 79], ['json-prune-fetch-response.js', 80], ['json-prune-xhr-response.js', 81], ['trusted-replace-xhr-response.js', 82], ['multiup.js', 83], ['prevent-canvas.js', 84], ['set-cookie-reload.js', 85], ['trusted-set-cookie-reload.js', 86], ['trusted-click-element.js', 87], ['trusted-prune-inbound-object.js', 88], ['trusted-prune-outbound-object.js', 89], ['trusted-set-session-storage-item.js', 90], ['trusted-replace-node-text.js', 91], ['trusted-rpnt.js', 92], ['replace-node-text.js', 93], ['rpnt.js', 94]]);
|
|
6295
6299
|
/**
|
|
6296
6300
|
* Value map for binary deserialization. This helps to reduce the size of the serialized data,
|
|
@@ -6428,6 +6432,7 @@ class AdgScriptletInjectionBodyParser extends ParserBase {
|
|
|
6428
6432
|
*
|
|
6429
6433
|
* @note Only 256 values can be represented this way.
|
|
6430
6434
|
*/
|
|
6435
|
+
// TODO: Update this map with the actual values
|
|
6431
6436
|
static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-inline-script', 0], ['abort-on-property-read', 1], ['abort-on-property-write', 2], ['abort-on-stack-trace', 3], ['adjust-setInterval', 4], ['adjust-setTimeout', 5], ['close-window', 6], ['debug-current-inline-script', 7], ['debug-on-property-read', 8], ['debug-on-property-write', 9], ['dir-string', 10], ['disable-newtab-links', 11], ['evaldata-prune', 12], ['json-prune', 13], ['log', 14], ['log-addEventListener', 15], ['log-eval', 16], ['log-on-stack-trace', 17], ['m3u-prune', 18], ['noeval', 19], ['nowebrtc', 20], ['no-topics', 21], ['prevent-addEventListener', 22], ['prevent-adfly', 23], ['prevent-bab', 24], ['prevent-eval-if', 25], ['prevent-fab-3.2.0', 26], ['prevent-fetch', 27], ['prevent-xhr', 28], ['prevent-popads-net', 29], ['prevent-refresh', 30], ['prevent-requestAnimationFrame', 31], ['prevent-setInterval', 32], ['prevent-setTimeout', 33], ['prevent-window-open', 34], ['remove-attr', 35], ['remove-class', 36], ['remove-cookie', 37], ['remove-node-text', 38], ['set-attr', 39], ['set-constant', 40], ['set-cookie', 41], ['set-cookie-reload', 42], ['set-local-storage-item', 43], ['set-popads-dummy', 44], ['set-session-storage-item', 45], ['xml-prune', 46]]);
|
|
6432
6437
|
/**
|
|
6433
6438
|
* Value map for binary deserialization. This helps to reduce the size of the serialized data,
|
|
@@ -21507,6 +21512,30 @@ function getScriptletName(scriptletNode) {
|
|
|
21507
21512
|
}
|
|
21508
21513
|
return scriptletNode.children[0]?.value ?? EMPTY;
|
|
21509
21514
|
}
|
|
21515
|
+
/**
|
|
21516
|
+
* Transform the nth argument of the scriptlet node
|
|
21517
|
+
*
|
|
21518
|
+
* @param scriptletNode Scriptlet node to transform argument of
|
|
21519
|
+
* @param index Index of the argument to transform (index 0 is the scriptlet name)
|
|
21520
|
+
* @param transform Function to transform the argument
|
|
21521
|
+
*/
|
|
21522
|
+
function transformNthScriptletArgument(scriptletNode, index, transform) {
|
|
21523
|
+
const child = scriptletNode.children[index];
|
|
21524
|
+
if (!isUndefined(child) && !isNull(child)) {
|
|
21525
|
+
child.value = transform(child.value);
|
|
21526
|
+
}
|
|
21527
|
+
}
|
|
21528
|
+
/**
|
|
21529
|
+
* Transform all arguments of the scriptlet node
|
|
21530
|
+
*
|
|
21531
|
+
* @param scriptletNode Scriptlet node to transform arguments of
|
|
21532
|
+
* @param transform Function to transform the arguments
|
|
21533
|
+
*/
|
|
21534
|
+
function transformAllScriptletArguments(scriptletNode, transform) {
|
|
21535
|
+
for (let i = 0; i < scriptletNode.children.length; i += 1) {
|
|
21536
|
+
transformNthScriptletArgument(scriptletNode, i, transform);
|
|
21537
|
+
}
|
|
21538
|
+
}
|
|
21510
21539
|
/**
|
|
21511
21540
|
* Set name of the scriptlet.
|
|
21512
21541
|
* Modifies input `scriptletNode` if needed.
|
|
@@ -21515,10 +21544,7 @@ function getScriptletName(scriptletNode) {
|
|
|
21515
21544
|
* @param name Name to set
|
|
21516
21545
|
*/
|
|
21517
21546
|
function setScriptletName(scriptletNode, name) {
|
|
21518
|
-
|
|
21519
|
-
// eslint-disable-next-line no-param-reassign
|
|
21520
|
-
scriptletNode.children[0].value = name;
|
|
21521
|
-
}
|
|
21547
|
+
transformNthScriptletArgument(scriptletNode, 0, () => name);
|
|
21522
21548
|
}
|
|
21523
21549
|
/**
|
|
21524
21550
|
* Set quote type of the scriptlet parameters
|
|
@@ -21527,1422 +21553,1559 @@ function setScriptletName(scriptletNode, name) {
|
|
|
21527
21553
|
* @param quoteType Preferred quote type
|
|
21528
21554
|
*/
|
|
21529
21555
|
function setScriptletQuoteType(scriptletNode, quoteType) {
|
|
21530
|
-
|
|
21531
|
-
for (let i = 0; i < scriptletNode.children.length; i += 1) {
|
|
21532
|
-
const child = scriptletNode.children[i];
|
|
21533
|
-
if (isNull(child)) {
|
|
21534
|
-
continue;
|
|
21535
|
-
}
|
|
21536
|
-
// eslint-disable-next-line no-param-reassign
|
|
21537
|
-
child.value = QuoteUtils.setStringQuoteType(child.value, quoteType);
|
|
21538
|
-
}
|
|
21539
|
-
}
|
|
21556
|
+
transformAllScriptletArguments(scriptletNode, value => QuoteUtils.setStringQuoteType(value, quoteType));
|
|
21540
21557
|
}
|
|
21541
21558
|
|
|
21542
21559
|
/**
|
|
21543
|
-
* @file
|
|
21560
|
+
* @file Compatibility tables for redirects.
|
|
21544
21561
|
*/
|
|
21545
|
-
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
21546
|
-
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
21547
21562
|
/**
|
|
21548
|
-
*
|
|
21563
|
+
* Prefix for resource redirection names.
|
|
21564
|
+
*/
|
|
21565
|
+
const ABP_RESOURCE_PREFIX = 'abp-resource:';
|
|
21566
|
+
const ABP_RESOURCE_PREFIX_LENGTH = ABP_RESOURCE_PREFIX.length;
|
|
21567
|
+
/**
|
|
21568
|
+
* Transforms the name of an ABP redirect to a normalized form.
|
|
21549
21569
|
*
|
|
21550
|
-
* @
|
|
21570
|
+
* @param name Redirect name to normalize.
|
|
21571
|
+
*
|
|
21572
|
+
* @returns Normalized redirect name.
|
|
21573
|
+
*
|
|
21574
|
+
* @example
|
|
21575
|
+
* abpRedirectNameNormalizer('abp-resource:my-resource') // => 'my-resource'
|
|
21551
21576
|
*/
|
|
21552
|
-
|
|
21577
|
+
const abpRedirectNameNormalizer = name => {
|
|
21578
|
+
if (name.startsWith(ABP_RESOURCE_PREFIX)) {
|
|
21579
|
+
return name.slice(ABP_RESOURCE_PREFIX_LENGTH);
|
|
21580
|
+
}
|
|
21581
|
+
return name;
|
|
21582
|
+
};
|
|
21583
|
+
/**
|
|
21584
|
+
* Compatibility table for redirects.
|
|
21585
|
+
*/
|
|
21586
|
+
class RedirectsCompatibilityTable extends CompatibilityTableBase {
|
|
21553
21587
|
/**
|
|
21554
|
-
*
|
|
21588
|
+
* Creates a new instance of the compatibility table for redirects.
|
|
21555
21589
|
*
|
|
21556
|
-
* @param
|
|
21557
|
-
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
21558
|
-
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
21559
|
-
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
21560
|
-
* @throws If the rule is invalid or cannot be converted
|
|
21590
|
+
* @param data Compatibility table data.
|
|
21561
21591
|
*/
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
if (rule.syntax === AdblockSyntax.Adg) {
|
|
21565
|
-
return createNodeConversionResult([rule], false);
|
|
21566
|
-
}
|
|
21567
|
-
const separator = rule.separator.value;
|
|
21568
|
-
let convertedSeparator = separator;
|
|
21569
|
-
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgJsInjectionException : CosmeticRuleSeparator.AdgJsInjection;
|
|
21570
|
-
const convertedScriptlets = [];
|
|
21571
|
-
for (const scriptlet of rule.body.children) {
|
|
21572
|
-
// Clone the node to avoid any side effects
|
|
21573
|
-
const scriptletClone = cloneScriptletRuleNode(scriptlet);
|
|
21574
|
-
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
21575
|
-
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
|
|
21576
|
-
// Add prefix if it's not already there
|
|
21577
|
-
let prefix;
|
|
21578
|
-
switch (rule.syntax) {
|
|
21579
|
-
case AdblockSyntax.Abp:
|
|
21580
|
-
prefix = ABP_SCRIPTLET_PREFIX;
|
|
21581
|
-
break;
|
|
21582
|
-
case AdblockSyntax.Ubo:
|
|
21583
|
-
prefix = UBO_SCRIPTLET_PREFIX;
|
|
21584
|
-
break;
|
|
21585
|
-
default:
|
|
21586
|
-
prefix = EMPTY;
|
|
21587
|
-
}
|
|
21588
|
-
if (!scriptletName.startsWith(prefix)) {
|
|
21589
|
-
setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
|
|
21590
|
-
}
|
|
21591
|
-
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
21592
|
-
setScriptletQuoteType(scriptletClone, QuoteType.Single);
|
|
21593
|
-
convertedScriptlets.push(scriptletClone);
|
|
21594
|
-
}
|
|
21595
|
-
return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
|
|
21596
|
-
const res = {
|
|
21597
|
-
category: rule.category,
|
|
21598
|
-
type: rule.type,
|
|
21599
|
-
syntax: AdblockSyntax.Adg,
|
|
21600
|
-
exception: rule.exception,
|
|
21601
|
-
domains: cloneDomainListNode(rule.domains),
|
|
21602
|
-
separator: {
|
|
21603
|
-
type: 'Value',
|
|
21604
|
-
value: convertedSeparator
|
|
21605
|
-
},
|
|
21606
|
-
body: {
|
|
21607
|
-
type: rule.body.type,
|
|
21608
|
-
children: [scriptlet]
|
|
21609
|
-
}
|
|
21610
|
-
};
|
|
21611
|
-
if (rule.modifiers) {
|
|
21612
|
-
res.modifiers = cloneModifierListNode(rule.modifiers);
|
|
21613
|
-
}
|
|
21614
|
-
return res;
|
|
21615
|
-
}), true);
|
|
21592
|
+
constructor(data) {
|
|
21593
|
+
super(data, abpRedirectNameNormalizer);
|
|
21616
21594
|
}
|
|
21617
21595
|
}
|
|
21596
|
+
/**
|
|
21597
|
+
* Deep freeze the compatibility table data to avoid accidental modifications.
|
|
21598
|
+
*/
|
|
21599
|
+
deepFreeze(redirectsCompatibilityTableData);
|
|
21600
|
+
/**
|
|
21601
|
+
* Compatibility table instance for redirects.
|
|
21602
|
+
*/
|
|
21603
|
+
const redirectsCompatibilityTable = new RedirectsCompatibilityTable(redirectsCompatibilityTableData);
|
|
21618
21604
|
|
|
21619
21605
|
/**
|
|
21620
|
-
* @file
|
|
21606
|
+
* @file Compatibility tables for scriptlets.
|
|
21621
21607
|
*/
|
|
21622
21608
|
/**
|
|
21623
|
-
*
|
|
21624
|
-
*
|
|
21625
|
-
* @param name Name of the modifier
|
|
21626
|
-
* @param value Value of the modifier
|
|
21627
|
-
* @param exception Whether the modifier is an exception
|
|
21628
|
-
* @returns Modifier node
|
|
21609
|
+
* Compatibility table for scriptlets.
|
|
21629
21610
|
*/
|
|
21630
|
-
|
|
21631
|
-
const result = {
|
|
21632
|
-
type: 'Modifier',
|
|
21633
|
-
exception,
|
|
21634
|
-
name: {
|
|
21635
|
-
type: 'Value',
|
|
21636
|
-
value: name
|
|
21637
|
-
}
|
|
21638
|
-
};
|
|
21639
|
-
if (!isUndefined(value)) {
|
|
21640
|
-
result.value = {
|
|
21641
|
-
type: 'Value',
|
|
21642
|
-
value
|
|
21643
|
-
};
|
|
21644
|
-
}
|
|
21645
|
-
return result;
|
|
21646
|
-
}
|
|
21611
|
+
class ScriptletsCompatibilityTable extends CompatibilityTableBase {}
|
|
21647
21612
|
/**
|
|
21648
|
-
*
|
|
21649
|
-
*
|
|
21650
|
-
* @param modifiers Modifiers to put in the list (optional, defaults to an empty list)
|
|
21651
|
-
* @returns Modifier list node
|
|
21613
|
+
* Deep freeze the compatibility table data to avoid accidental modifications.
|
|
21652
21614
|
*/
|
|
21653
|
-
|
|
21654
|
-
const result = {
|
|
21655
|
-
type: 'ModifierList',
|
|
21656
|
-
// We need to clone the modifiers to avoid side effects
|
|
21657
|
-
children: modifiers.length ? clone(modifiers) : []
|
|
21658
|
-
};
|
|
21659
|
-
return result;
|
|
21660
|
-
}
|
|
21661
|
-
|
|
21615
|
+
deepFreeze(scriptletsCompatibilityTableData);
|
|
21662
21616
|
/**
|
|
21663
|
-
*
|
|
21664
|
-
* by storing them in an array.
|
|
21665
|
-
*
|
|
21666
|
-
* @todo Add more methods if needed
|
|
21617
|
+
* Compatibility table instance for scriptlets.
|
|
21667
21618
|
*/
|
|
21668
|
-
|
|
21669
|
-
/**
|
|
21670
|
-
* Adds a value to the map. If the key already exists, the value will be appended to the existing array,
|
|
21671
|
-
* otherwise a new array will be created for the key.
|
|
21672
|
-
*
|
|
21673
|
-
* @param key Key to add
|
|
21674
|
-
* @param values Value(s) to add
|
|
21675
|
-
*/
|
|
21676
|
-
add(key, ...values) {
|
|
21677
|
-
let currentValues = super.get(key);
|
|
21678
|
-
if (isUndefined(currentValues)) {
|
|
21679
|
-
currentValues = [];
|
|
21680
|
-
super.set(key, values);
|
|
21681
|
-
}
|
|
21682
|
-
currentValues.push(...values);
|
|
21683
|
-
}
|
|
21684
|
-
}
|
|
21619
|
+
const scriptletsCompatibilityTable = new ScriptletsCompatibilityTable(scriptletsCompatibilityTableData);
|
|
21685
21620
|
|
|
21621
|
+
/* eslint-disable no-bitwise */
|
|
21686
21622
|
/**
|
|
21687
|
-
* @file
|
|
21623
|
+
* @file Platform schema.
|
|
21688
21624
|
*/
|
|
21689
|
-
const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
|
|
21690
|
-
const ADG_PATH_MODIFIER = 'path';
|
|
21691
21625
|
/**
|
|
21692
|
-
*
|
|
21626
|
+
* Platform separator, e.g. 'adg_os_any|adg_safari_any' means any AdGuard OS platform and
|
|
21627
|
+
* any AdGuard Safari content blocker platform.
|
|
21693
21628
|
*/
|
|
21694
|
-
const
|
|
21629
|
+
const PLATFORM_SEPARATOR = '|';
|
|
21695
21630
|
/**
|
|
21696
|
-
*
|
|
21631
|
+
* Platform negation character, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except
|
|
21632
|
+
* Safari content blockers.
|
|
21697
21633
|
*/
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
}
|
|
21717
|
-
const value = RegExpUtils.isRegexPattern(modifier.value.value) ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS) : modifier.value.value;
|
|
21718
|
-
// Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
|
|
21719
|
-
conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
|
|
21720
|
-
// We should negate the regexp if the modifier is an exception
|
|
21721
|
-
modifier.exception
|
|
21722
|
-
// eslint-disable-next-line max-len
|
|
21723
|
-
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}` : value));
|
|
21724
|
-
}
|
|
21725
|
-
});
|
|
21726
|
-
// Check if we have any converted modifiers
|
|
21727
|
-
if (conversionMap.size) {
|
|
21728
|
-
const modifierListClone = clone(modifierList);
|
|
21729
|
-
// Replace the original modifiers with the converted ones
|
|
21730
|
-
modifierListClone.children = modifierListClone.children.map((modifier, index) => {
|
|
21731
|
-
const convertedModifier = conversionMap.get(index);
|
|
21732
|
-
return convertedModifier ?? modifier;
|
|
21733
|
-
}).flat();
|
|
21734
|
-
return createConversionResult(modifierListClone, true);
|
|
21634
|
+
const PLATFORM_NEGATION = '~';
|
|
21635
|
+
/**
|
|
21636
|
+
* Parses a raw platform string into a platform bitmask.
|
|
21637
|
+
*
|
|
21638
|
+
* @param rawPlatforms Raw platform string, e.g. 'adg_safari_any|adg_os_any'.
|
|
21639
|
+
*
|
|
21640
|
+
* @returns Platform bitmask.
|
|
21641
|
+
*/
|
|
21642
|
+
const parseRawPlatforms = rawPlatforms => {
|
|
21643
|
+
// e.g. 'adg_safari_any|adg_os_any'
|
|
21644
|
+
const rawPlatformList = rawPlatforms.split(PLATFORM_SEPARATOR).map(rawPlatform => rawPlatform.trim());
|
|
21645
|
+
let result = 0;
|
|
21646
|
+
for (let rawPlatform of rawPlatformList) {
|
|
21647
|
+
// negation, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except Safari content blockers
|
|
21648
|
+
let negated = false;
|
|
21649
|
+
if (rawPlatform.startsWith(PLATFORM_NEGATION)) {
|
|
21650
|
+
negated = true;
|
|
21651
|
+
rawPlatform = rawPlatform.slice(1).trim();
|
|
21735
21652
|
}
|
|
21736
|
-
|
|
21737
|
-
|
|
21738
|
-
|
|
21739
|
-
}
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
21653
|
+
const platform = SPECIFIC_PLATFORM_MAP.get(rawPlatform) ?? GENERIC_PLATFORM_MAP.get(rawPlatform);
|
|
21654
|
+
if (isUndefined(platform)) {
|
|
21655
|
+
throw new Error(`Unknown platform: ${rawPlatform}`);
|
|
21656
|
+
}
|
|
21657
|
+
if (negated) {
|
|
21658
|
+
result &= ~platform;
|
|
21659
|
+
} else {
|
|
21660
|
+
result |= platform;
|
|
21661
|
+
}
|
|
21662
|
+
}
|
|
21663
|
+
if (result === 0) {
|
|
21664
|
+
throw new Error('No platforms specified');
|
|
21665
|
+
}
|
|
21666
|
+
return result;
|
|
21743
21667
|
};
|
|
21744
|
-
var PseudoClasses;
|
|
21745
|
-
(function (PseudoClasses) {
|
|
21746
|
-
PseudoClasses["AbpContains"] = "-abp-contains";
|
|
21747
|
-
PseudoClasses["AbpHas"] = "-abp-has";
|
|
21748
|
-
PseudoClasses["Contains"] = "contains";
|
|
21749
|
-
PseudoClasses["Has"] = "has";
|
|
21750
|
-
PseudoClasses["HasText"] = "has-text";
|
|
21751
|
-
PseudoClasses["MatchesCss"] = "matches-css";
|
|
21752
|
-
PseudoClasses["MatchesCssAfter"] = "matches-css-after";
|
|
21753
|
-
PseudoClasses["MatchesCssBefore"] = "matches-css-before";
|
|
21754
|
-
PseudoClasses["Not"] = "not";
|
|
21755
|
-
})(PseudoClasses || (PseudoClasses = {}));
|
|
21756
|
-
var PseudoElements;
|
|
21757
|
-
(function (PseudoElements) {
|
|
21758
|
-
PseudoElements["After"] = "after";
|
|
21759
|
-
PseudoElements["Before"] = "before";
|
|
21760
|
-
})(PseudoElements || (PseudoElements = {}));
|
|
21761
|
-
const PSEUDO_ELEMENT_NAMES = new Set([PseudoElements.After, PseudoElements.Before]);
|
|
21762
21668
|
/**
|
|
21763
|
-
*
|
|
21764
|
-
*
|
|
21765
|
-
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
21669
|
+
* Platform schema.
|
|
21766
21670
|
*/
|
|
21767
|
-
|
|
21768
|
-
|
|
21769
|
-
|
|
21770
|
-
*
|
|
21771
|
-
* @param selectorList Selector list to convert
|
|
21772
|
-
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
21773
|
-
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
21774
|
-
* If the node was not converted, the result will contain the original node with the same object reference
|
|
21775
|
-
* @throws If the rule is invalid or incompatible
|
|
21776
|
-
*/
|
|
21777
|
-
static convertToAdg(selectorList) {
|
|
21778
|
-
const stream = selectorList instanceof CssTokenStream ? selectorList : new CssTokenStream(selectorList);
|
|
21779
|
-
const converted = [];
|
|
21780
|
-
const convertAndPushPseudo = pseudo => {
|
|
21781
|
-
switch (pseudo) {
|
|
21782
|
-
case PseudoClasses.AbpContains:
|
|
21783
|
-
case PseudoClasses.HasText:
|
|
21784
|
-
converted.push(PseudoClasses.Contains);
|
|
21785
|
-
converted.push(OPEN_PARENTHESIS);
|
|
21786
|
-
break;
|
|
21787
|
-
case PseudoClasses.AbpHas:
|
|
21788
|
-
converted.push(PseudoClasses.Has);
|
|
21789
|
-
converted.push(OPEN_PARENTHESIS);
|
|
21790
|
-
break;
|
|
21791
|
-
// a bit special case:
|
|
21792
|
-
// - `:matches-css-before(...)` → `:matches-css(before, ...)`
|
|
21793
|
-
// - `:matches-css-after(...)` → `:matches-css(after, ...)`
|
|
21794
|
-
case PseudoClasses.MatchesCssBefore:
|
|
21795
|
-
case PseudoClasses.MatchesCssAfter:
|
|
21796
|
-
converted.push(PseudoClasses.MatchesCss);
|
|
21797
|
-
converted.push(OPEN_PARENTHESIS);
|
|
21798
|
-
converted.push(pseudo.substring(PseudoClasses.MatchesCss.length + 1));
|
|
21799
|
-
converted.push(COMMA);
|
|
21800
|
-
break;
|
|
21801
|
-
default:
|
|
21802
|
-
converted.push(pseudo);
|
|
21803
|
-
converted.push(OPEN_PARENTHESIS);
|
|
21804
|
-
break;
|
|
21805
|
-
}
|
|
21806
|
-
};
|
|
21807
|
-
while (!stream.isEof()) {
|
|
21808
|
-
const token = stream.getOrFail();
|
|
21809
|
-
if (token.type === TokenType$1.Colon) {
|
|
21810
|
-
// Advance colon
|
|
21811
|
-
stream.advance();
|
|
21812
|
-
converted.push(COLON);
|
|
21813
|
-
const tempToken = stream.getOrFail();
|
|
21814
|
-
// Double colon is a pseudo-element
|
|
21815
|
-
if (tempToken.type === TokenType$1.Colon) {
|
|
21816
|
-
stream.advance();
|
|
21817
|
-
converted.push(COLON);
|
|
21818
|
-
continue;
|
|
21819
|
-
}
|
|
21820
|
-
if (tempToken.type === TokenType$1.Ident) {
|
|
21821
|
-
const name = stream.source.slice(tempToken.start, tempToken.end);
|
|
21822
|
-
if (PSEUDO_ELEMENT_NAMES.has(name)) {
|
|
21823
|
-
// Add an extra colon to the name
|
|
21824
|
-
converted.push(COLON);
|
|
21825
|
-
converted.push(name);
|
|
21826
|
-
} else {
|
|
21827
|
-
// Add the name as is
|
|
21828
|
-
converted.push(name);
|
|
21829
|
-
}
|
|
21830
|
-
// Advance the names
|
|
21831
|
-
stream.advance();
|
|
21832
|
-
} else if (tempToken.type === TokenType$1.Function) {
|
|
21833
|
-
const name = stream.source.slice(tempToken.start, tempToken.end - 1); // omit the last parenthesis
|
|
21834
|
-
// :-abp-contains(...) → :contains(...)
|
|
21835
|
-
// :has-text(...) → :contains(...)
|
|
21836
|
-
// :-abp-has(...) → :has(...)
|
|
21837
|
-
convertAndPushPseudo(name);
|
|
21838
|
-
// Advance the function name
|
|
21839
|
-
stream.advance();
|
|
21840
|
-
}
|
|
21841
|
-
} else if (token.type === TokenType$1.OpenSquareBracket) {
|
|
21842
|
-
let tempToken;
|
|
21843
|
-
const {
|
|
21844
|
-
start
|
|
21845
|
-
} = token;
|
|
21846
|
-
stream.advance();
|
|
21847
|
-
// Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
|
|
21848
|
-
// For example:
|
|
21849
|
-
// - `[-ext-has=...]` → `:has(...)`
|
|
21850
|
-
// - `[-ext-contains=...]` → `:contains(...)`
|
|
21851
|
-
// - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
|
|
21852
|
-
stream.skipWhitespace();
|
|
21853
|
-
stream.expect(TokenType$1.Ident);
|
|
21854
|
-
tempToken = stream.getOrFail();
|
|
21855
|
-
let attr = stream.source.slice(tempToken.start, tempToken.end);
|
|
21856
|
-
// Skip if the attribute name is not a legacy Extended CSS one
|
|
21857
|
-
if (!(attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX) || attr.startsWith(ABP_EXT_CSS_PREFIX))) {
|
|
21858
|
-
converted.push(stream.source.slice(start, tempToken.end));
|
|
21859
|
-
stream.advance();
|
|
21860
|
-
continue;
|
|
21861
|
-
}
|
|
21862
|
-
if (attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)) {
|
|
21863
|
-
attr = attr.slice(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
|
|
21864
|
-
}
|
|
21865
|
-
stream.advance();
|
|
21866
|
-
stream.skipWhitespace();
|
|
21867
|
-
// Next token should be an equality operator (=), because Extended CSS attribute selectors
|
|
21868
|
-
// do not support other operators
|
|
21869
|
-
stream.expect(TokenType$1.Delim, {
|
|
21870
|
-
value: EQUALS
|
|
21871
|
-
});
|
|
21872
|
-
stream.advance();
|
|
21873
|
-
// Skip optional whitespace after the operator
|
|
21874
|
-
stream.skipWhitespace();
|
|
21875
|
-
// Parse attribute value
|
|
21876
|
-
tempToken = stream.getOrFail();
|
|
21877
|
-
// According to the spec, attribute value should be an identifier or a string
|
|
21878
|
-
if (tempToken.type !== TokenType$1.Ident && tempToken.type !== TokenType$1.String) {
|
|
21879
|
-
throw new Error(sprintf(ERROR_MESSAGES$1.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), stream.source.slice(tempToken.start, tempToken.end)));
|
|
21880
|
-
}
|
|
21881
|
-
const value = stream.source.slice(tempToken.start, tempToken.end);
|
|
21882
|
-
// Advance the attribute value
|
|
21883
|
-
stream.advance();
|
|
21884
|
-
// Skip optional whitespace after the attribute value
|
|
21885
|
-
stream.skipWhitespace();
|
|
21886
|
-
// Next character should be a closing square bracket
|
|
21887
|
-
// We don't allow flags for Extended CSS attribute selectors
|
|
21888
|
-
stream.expect(TokenType$1.CloseSquareBracket);
|
|
21889
|
-
stream.advance();
|
|
21890
|
-
converted.push(COLON);
|
|
21891
|
-
convertAndPushPseudo(attr);
|
|
21892
|
-
let processedValue = value.slice(1, -1); // omit the quotes
|
|
21893
|
-
if (attr === PseudoClasses.Has) {
|
|
21894
|
-
// TODO: Optimize this to avoid double tokenization
|
|
21895
|
-
processedValue = CssSelectorConverter.convertToAdg(processedValue).result;
|
|
21896
|
-
}
|
|
21897
|
-
converted.push(processedValue);
|
|
21898
|
-
converted.push(CLOSE_PARENTHESIS);
|
|
21899
|
-
} else {
|
|
21900
|
-
converted.push(stream.source.slice(token.start, token.end));
|
|
21901
|
-
// Advance the token
|
|
21902
|
-
stream.advance();
|
|
21903
|
-
}
|
|
21904
|
-
}
|
|
21905
|
-
const convertedSelectorList = converted.join(EMPTY);
|
|
21906
|
-
return createConversionResult(convertedSelectorList, stream.source !== convertedSelectorList);
|
|
21907
|
-
}
|
|
21671
|
+
zod.string().min(1).transform(value => parseRawPlatforms(value));
|
|
21672
|
+
function getDefaultExportFromCjs(x) {
|
|
21673
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
21908
21674
|
}
|
|
21675
|
+
var mapObj$1 = {
|
|
21676
|
+
exports: {}
|
|
21677
|
+
};
|
|
21678
|
+
const isObject$1 = value => typeof value === 'object' && value !== null;
|
|
21679
|
+
const mapObjectSkip = Symbol('skip');
|
|
21909
21680
|
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
* Converts a CSS injection rule to AdGuard format, if possible.
|
|
21921
|
-
*
|
|
21922
|
-
* @param rule Rule node to convert
|
|
21923
|
-
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
21924
|
-
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
21925
|
-
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
21926
|
-
* @throws If the rule is invalid or cannot be converted
|
|
21927
|
-
*/
|
|
21928
|
-
static convertToAdg(rule) {
|
|
21929
|
-
const separator = rule.separator.value;
|
|
21930
|
-
let convertedSeparator = separator;
|
|
21931
|
-
const stream = new CssTokenStream(rule.body.selectorList.value);
|
|
21932
|
-
const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
|
|
21933
|
-
// Change the separator if the rule contains ExtendedCSS elements,
|
|
21934
|
-
// but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
|
|
21935
|
-
// because sometimes we use it to force executing ExtendedCSS library.
|
|
21936
|
-
if (stream.hasAnySelectorExtendedCssNodeStrict() || rule.body.remove) {
|
|
21937
|
-
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgExtendedCssInjectionException : CosmeticRuleSeparator.AdgExtendedCssInjection;
|
|
21938
|
-
} else if (rule.syntax !== AdblockSyntax.Adg) {
|
|
21939
|
-
// If the original rule syntax is not AdGuard, use the default separator
|
|
21940
|
-
// e.g. if the input rule is from uBO, we need to convert ## to #$#.
|
|
21941
|
-
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgCssInjectionException : CosmeticRuleSeparator.AdgCssInjection;
|
|
21942
|
-
}
|
|
21943
|
-
// Check if the rule needs to be converted
|
|
21944
|
-
if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
|
|
21945
|
-
// TODO: Replace with custom clone method
|
|
21946
|
-
const ruleClone = clone(rule);
|
|
21947
|
-
ruleClone.syntax = AdblockSyntax.Adg;
|
|
21948
|
-
ruleClone.separator.value = convertedSeparator;
|
|
21949
|
-
ruleClone.body.selectorList.value = convertedSelectorList.result;
|
|
21950
|
-
return createNodeConversionResult([ruleClone], true);
|
|
21951
|
-
}
|
|
21952
|
-
// Otherwise, return the original rule
|
|
21953
|
-
return createNodeConversionResult([rule], false);
|
|
21681
|
+
// Customized for this use-case
|
|
21682
|
+
const isObjectCustom = value => isObject$1(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
21683
|
+
const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
|
|
21684
|
+
options = {
|
|
21685
|
+
deep: false,
|
|
21686
|
+
target: {},
|
|
21687
|
+
...options
|
|
21688
|
+
};
|
|
21689
|
+
if (isSeen.has(object)) {
|
|
21690
|
+
return isSeen.get(object);
|
|
21954
21691
|
}
|
|
21955
|
-
|
|
21692
|
+
isSeen.set(object, options.target);
|
|
21693
|
+
const {
|
|
21694
|
+
target
|
|
21695
|
+
} = options;
|
|
21696
|
+
delete options.target;
|
|
21697
|
+
const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element);
|
|
21698
|
+
if (Array.isArray(object)) {
|
|
21699
|
+
return mapArray(object);
|
|
21700
|
+
}
|
|
21701
|
+
for (const [key, value] of Object.entries(object)) {
|
|
21702
|
+
const mapResult = mapper(key, value, object);
|
|
21703
|
+
if (mapResult === mapObjectSkip) {
|
|
21704
|
+
continue;
|
|
21705
|
+
}
|
|
21706
|
+
let [newKey, newValue, {
|
|
21707
|
+
shouldRecurse = true
|
|
21708
|
+
} = {}] = mapResult;
|
|
21956
21709
|
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
/**
|
|
21961
|
-
* Element hiding rule converter class
|
|
21962
|
-
*
|
|
21963
|
-
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
21964
|
-
*/
|
|
21965
|
-
class ElementHidingRuleConverter extends RuleConverterBase {
|
|
21966
|
-
/**
|
|
21967
|
-
* Converts an element hiding rule to AdGuard format, if possible.
|
|
21968
|
-
*
|
|
21969
|
-
* @param rule Rule node to convert
|
|
21970
|
-
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
21971
|
-
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
21972
|
-
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
21973
|
-
* @throws If the rule is invalid or cannot be converted
|
|
21974
|
-
*/
|
|
21975
|
-
static convertToAdg(rule) {
|
|
21976
|
-
const separator = rule.separator.value;
|
|
21977
|
-
let convertedSeparator = separator;
|
|
21978
|
-
const stream = new CssTokenStream(rule.body.selectorList.value);
|
|
21979
|
-
const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
|
|
21980
|
-
// Change the separator if the rule contains ExtendedCSS elements,
|
|
21981
|
-
// but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
|
|
21982
|
-
// because sometimes we use it to force executing ExtendedCSS library.
|
|
21983
|
-
if (stream.hasAnySelectorExtendedCssNodeStrict()) {
|
|
21984
|
-
convertedSeparator = rule.exception ? CosmeticRuleSeparator.ExtendedElementHidingException : CosmeticRuleSeparator.ExtendedElementHiding;
|
|
21710
|
+
// Drop `__proto__` keys.
|
|
21711
|
+
if (newKey === '__proto__') {
|
|
21712
|
+
continue;
|
|
21985
21713
|
}
|
|
21986
|
-
|
|
21987
|
-
|
|
21988
|
-
// TODO: Replace with custom clone method
|
|
21989
|
-
const ruleClone = clone(rule);
|
|
21990
|
-
ruleClone.syntax = AdblockSyntax.Adg;
|
|
21991
|
-
ruleClone.separator.value = convertedSeparator;
|
|
21992
|
-
ruleClone.body.selectorList.value = convertedSelectorList.result;
|
|
21993
|
-
return createNodeConversionResult([ruleClone], true);
|
|
21714
|
+
if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
|
|
21715
|
+
newValue = Array.isArray(newValue) ? mapArray(newValue) : mapObject(newValue, mapper, options, isSeen);
|
|
21994
21716
|
}
|
|
21995
|
-
|
|
21996
|
-
return createNodeConversionResult([rule], false);
|
|
21717
|
+
target[newKey] = newValue;
|
|
21997
21718
|
}
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
21719
|
+
return target;
|
|
21720
|
+
};
|
|
21721
|
+
mapObj$1.exports = (object, mapper, options) => {
|
|
21722
|
+
if (!isObject$1(object)) {
|
|
21723
|
+
throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
|
|
21724
|
+
}
|
|
21725
|
+
return mapObject(object, mapper, options);
|
|
21726
|
+
};
|
|
21727
|
+
mapObj$1.exports.mapObjectSkip = mapObjectSkip;
|
|
21728
|
+
var mapObjExports = mapObj$1.exports;
|
|
21729
|
+
var camelcase = {
|
|
21730
|
+
exports: {}
|
|
21731
|
+
};
|
|
21732
|
+
const UPPERCASE = /[\p{Lu}]/u;
|
|
21733
|
+
const LOWERCASE = /[\p{Ll}]/u;
|
|
21734
|
+
const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
|
|
21735
|
+
const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
|
|
21736
|
+
const SEPARATORS = /[_.\- ]+/;
|
|
21737
|
+
const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
|
|
21738
|
+
const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
|
|
21739
|
+
const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
|
|
21740
|
+
const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
|
|
21741
|
+
let isLastCharLower = false;
|
|
21742
|
+
let isLastCharUpper = false;
|
|
21743
|
+
let isLastLastCharUpper = false;
|
|
21744
|
+
for (let i = 0; i < string.length; i++) {
|
|
21745
|
+
const character = string[i];
|
|
21746
|
+
if (isLastCharLower && UPPERCASE.test(character)) {
|
|
21747
|
+
string = string.slice(0, i) + '-' + string.slice(i);
|
|
21748
|
+
isLastCharLower = false;
|
|
21749
|
+
isLastLastCharUpper = isLastCharUpper;
|
|
21750
|
+
isLastCharUpper = true;
|
|
21751
|
+
i++;
|
|
21752
|
+
} else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
|
|
21753
|
+
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
|
|
21754
|
+
isLastLastCharUpper = isLastCharUpper;
|
|
21755
|
+
isLastCharUpper = false;
|
|
21756
|
+
isLastCharLower = true;
|
|
21757
|
+
} else {
|
|
21758
|
+
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
|
|
21759
|
+
isLastLastCharUpper = isLastCharUpper;
|
|
21760
|
+
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
|
|
22021
21761
|
}
|
|
21762
|
+
}
|
|
21763
|
+
return string;
|
|
21764
|
+
};
|
|
21765
|
+
const preserveConsecutiveUppercase = (input, toLowerCase) => {
|
|
21766
|
+
LEADING_CAPITAL.lastIndex = 0;
|
|
21767
|
+
return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
|
|
21768
|
+
};
|
|
21769
|
+
const postProcess = (input, toUpperCase) => {
|
|
21770
|
+
SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
|
|
21771
|
+
NUMBERS_AND_IDENTIFIER.lastIndex = 0;
|
|
21772
|
+
return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)).replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
|
|
21773
|
+
};
|
|
21774
|
+
const camelCase$1 = (input, options) => {
|
|
21775
|
+
if (!(typeof input === 'string' || Array.isArray(input))) {
|
|
21776
|
+
throw new TypeError('Expected the input to be `string | string[]`');
|
|
21777
|
+
}
|
|
21778
|
+
options = {
|
|
21779
|
+
pascalCase: false,
|
|
21780
|
+
preserveConsecutiveUppercase: false,
|
|
21781
|
+
...options
|
|
22022
21782
|
};
|
|
22023
|
-
if (
|
|
22024
|
-
|
|
21783
|
+
if (Array.isArray(input)) {
|
|
21784
|
+
input = input.map(x => x.trim()).filter(x => x.length).join('-');
|
|
21785
|
+
} else {
|
|
21786
|
+
input = input.trim();
|
|
22025
21787
|
}
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
const
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
21788
|
+
if (input.length === 0) {
|
|
21789
|
+
return '';
|
|
21790
|
+
}
|
|
21791
|
+
const toLowerCase = options.locale === false ? string => string.toLowerCase() : string => string.toLocaleLowerCase(options.locale);
|
|
21792
|
+
const toUpperCase = options.locale === false ? string => string.toUpperCase() : string => string.toLocaleUpperCase(options.locale);
|
|
21793
|
+
if (input.length === 1) {
|
|
21794
|
+
return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
|
|
21795
|
+
}
|
|
21796
|
+
const hasUpperCase = input !== toLowerCase(input);
|
|
21797
|
+
if (hasUpperCase) {
|
|
21798
|
+
input = preserveCamelCase(input, toLowerCase, toUpperCase);
|
|
21799
|
+
}
|
|
21800
|
+
input = input.replace(LEADING_SEPARATORS, '');
|
|
21801
|
+
if (options.preserveConsecutiveUppercase) {
|
|
21802
|
+
input = preserveConsecutiveUppercase(input, toLowerCase);
|
|
21803
|
+
} else {
|
|
21804
|
+
input = toLowerCase(input);
|
|
21805
|
+
}
|
|
21806
|
+
if (options.pascalCase) {
|
|
21807
|
+
input = toUpperCase(input.charAt(0)) + input.slice(1);
|
|
21808
|
+
}
|
|
21809
|
+
return postProcess(input, toUpperCase);
|
|
22038
21810
|
};
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
*
|
|
22048
|
-
* @param rule Rule node to convert
|
|
22049
|
-
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22050
|
-
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22051
|
-
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22052
|
-
* @throws If the rule is invalid or cannot be converted
|
|
22053
|
-
* @example
|
|
22054
|
-
* If the input rule is:
|
|
22055
|
-
* ```adblock
|
|
22056
|
-
* example.com##^responseheader(header-name)
|
|
22057
|
-
* ```
|
|
22058
|
-
* The output will be:
|
|
22059
|
-
* ```adblock
|
|
22060
|
-
* ||example.com^$removeheader=header-name
|
|
22061
|
-
* ```
|
|
22062
|
-
*/
|
|
22063
|
-
static convertToAdg(rule) {
|
|
22064
|
-
// TODO: Add support for ABP syntax once it starts supporting header removal rules
|
|
22065
|
-
// Leave the rule as is if it's not a header removal rule
|
|
22066
|
-
if (rule.category !== RuleCategory.Cosmetic || rule.type !== CosmeticRuleType.HtmlFilteringRule) {
|
|
22067
|
-
return createNodeConversionResult([rule], false);
|
|
21811
|
+
camelcase.exports = camelCase$1;
|
|
21812
|
+
// TODO: Remove this for the next major release
|
|
21813
|
+
camelcase.exports.default = camelCase$1;
|
|
21814
|
+
var camelcaseExports = camelcase.exports;
|
|
21815
|
+
class QuickLRU {
|
|
21816
|
+
constructor(options = {}) {
|
|
21817
|
+
if (!(options.maxSize && options.maxSize > 0)) {
|
|
21818
|
+
throw new TypeError('`maxSize` must be a number greater than 0');
|
|
22068
21819
|
}
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
21820
|
+
this.maxSize = options.maxSize;
|
|
21821
|
+
this.onEviction = options.onEviction;
|
|
21822
|
+
this.cache = new Map();
|
|
21823
|
+
this.oldCache = new Map();
|
|
21824
|
+
this._size = 0;
|
|
21825
|
+
}
|
|
21826
|
+
_set(key, value) {
|
|
21827
|
+
this.cache.set(key, value);
|
|
21828
|
+
this._size++;
|
|
21829
|
+
if (this._size >= this.maxSize) {
|
|
21830
|
+
this._size = 0;
|
|
21831
|
+
if (typeof this.onEviction === 'function') {
|
|
21832
|
+
for (const [key, value] of this.oldCache.entries()) {
|
|
21833
|
+
this.onEviction(key, value);
|
|
21834
|
+
}
|
|
21835
|
+
}
|
|
21836
|
+
this.oldCache = this.cache;
|
|
21837
|
+
this.cache = new Map();
|
|
22077
21838
|
}
|
|
22078
|
-
|
|
22079
|
-
|
|
22080
|
-
if (
|
|
22081
|
-
return
|
|
21839
|
+
}
|
|
21840
|
+
get(key) {
|
|
21841
|
+
if (this.cache.has(key)) {
|
|
21842
|
+
return this.cache.get(key);
|
|
21843
|
+
}
|
|
21844
|
+
if (this.oldCache.has(key)) {
|
|
21845
|
+
const value = this.oldCache.get(key);
|
|
21846
|
+
this.oldCache.delete(key);
|
|
21847
|
+
this._set(key, value);
|
|
21848
|
+
return value;
|
|
21849
|
+
}
|
|
21850
|
+
}
|
|
21851
|
+
set(key, value) {
|
|
21852
|
+
if (this.cache.has(key)) {
|
|
21853
|
+
this.cache.set(key, value);
|
|
21854
|
+
} else {
|
|
21855
|
+
this._set(key, value);
|
|
21856
|
+
}
|
|
21857
|
+
return this;
|
|
21858
|
+
}
|
|
21859
|
+
has(key) {
|
|
21860
|
+
return this.cache.has(key) || this.oldCache.has(key);
|
|
21861
|
+
}
|
|
21862
|
+
peek(key) {
|
|
21863
|
+
if (this.cache.has(key)) {
|
|
21864
|
+
return this.cache.get(key);
|
|
22082
21865
|
}
|
|
22083
|
-
|
|
22084
|
-
|
|
22085
|
-
return createNodeConversionResult([rule], false);
|
|
21866
|
+
if (this.oldCache.has(key)) {
|
|
21867
|
+
return this.oldCache.get(key);
|
|
22086
21868
|
}
|
|
22087
|
-
|
|
22088
|
-
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
|
|
22092
|
-
// Do not allow empty parameter
|
|
22093
|
-
if (param.length === 0) {
|
|
22094
|
-
throw new RuleConversionError(ERROR_MESSAGES.EMPTY_PARAMETER);
|
|
21869
|
+
}
|
|
21870
|
+
delete(key) {
|
|
21871
|
+
const deleted = this.cache.delete(key);
|
|
21872
|
+
if (deleted) {
|
|
21873
|
+
this._size--;
|
|
22095
21874
|
}
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
22100
|
-
|
|
22101
|
-
|
|
22102
|
-
|
|
22103
|
-
|
|
21875
|
+
return this.oldCache.delete(key) || deleted;
|
|
21876
|
+
}
|
|
21877
|
+
clear() {
|
|
21878
|
+
this.cache.clear();
|
|
21879
|
+
this.oldCache.clear();
|
|
21880
|
+
this._size = 0;
|
|
21881
|
+
}
|
|
21882
|
+
*keys() {
|
|
21883
|
+
for (const [key] of this) {
|
|
21884
|
+
yield key;
|
|
22104
21885
|
}
|
|
22105
|
-
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
|
|
22109
|
-
// ||single-domain-from-the-rule^
|
|
22110
|
-
pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
|
|
22111
|
-
} else if (rule.domains.children.length > 1) {
|
|
22112
|
-
// TODO: Add support for multiple domains, for example:
|
|
22113
|
-
// example.com,example.org,example.net##^responseheader(header-name)
|
|
22114
|
-
// We should consider allowing $domain with $removeheader modifier,
|
|
22115
|
-
// for example:
|
|
22116
|
-
// $removeheader=header-name,domain=example.com|example.org|example.net
|
|
22117
|
-
throw new RuleConversionError(ERROR_MESSAGES.MULTIPLE_DOMAINS_NOT_SUPPORTED);
|
|
21886
|
+
}
|
|
21887
|
+
*values() {
|
|
21888
|
+
for (const [, value] of this) {
|
|
21889
|
+
yield value;
|
|
22118
21890
|
}
|
|
22119
|
-
// Prepare network rule modifiers
|
|
22120
|
-
const modifiers = createModifierListNode();
|
|
22121
|
-
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, param));
|
|
22122
|
-
// Construct the network rule
|
|
22123
|
-
return createNodeConversionResult([createNetworkRuleNode(pattern.join(EMPTY), modifiers,
|
|
22124
|
-
// Copy the exception flag
|
|
22125
|
-
rule.exception, AdblockSyntax.Adg)], true);
|
|
22126
21891
|
}
|
|
22127
|
-
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
* @file Cosmetic rule converter
|
|
22131
|
-
*/
|
|
22132
|
-
/**
|
|
22133
|
-
* Cosmetic rule converter class (also known as "non-basic rule converter")
|
|
22134
|
-
*
|
|
22135
|
-
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22136
|
-
*/
|
|
22137
|
-
class CosmeticRuleConverter extends RuleConverterBase {
|
|
22138
|
-
/**
|
|
22139
|
-
* Converts a cosmetic rule to AdGuard syntax, if possible.
|
|
22140
|
-
*
|
|
22141
|
-
* @param rule Rule node to convert
|
|
22142
|
-
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22143
|
-
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22144
|
-
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22145
|
-
* @throws If the rule is invalid or cannot be converted
|
|
22146
|
-
*/
|
|
22147
|
-
static convertToAdg(rule) {
|
|
22148
|
-
let subconverterResult;
|
|
22149
|
-
// Convert cosmetic rule based on its type
|
|
22150
|
-
switch (rule.type) {
|
|
22151
|
-
case CosmeticRuleType.ElementHidingRule:
|
|
22152
|
-
subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
|
|
22153
|
-
break;
|
|
22154
|
-
case CosmeticRuleType.ScriptletInjectionRule:
|
|
22155
|
-
subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
|
|
22156
|
-
break;
|
|
22157
|
-
case CosmeticRuleType.CssInjectionRule:
|
|
22158
|
-
subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
|
|
22159
|
-
break;
|
|
22160
|
-
case CosmeticRuleType.HtmlFilteringRule:
|
|
22161
|
-
// Handle special case: uBO response header filtering rule
|
|
22162
|
-
// TODO: Optimize double CSS tokenization here
|
|
22163
|
-
subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
|
|
22164
|
-
if (subconverterResult.isConverted) {
|
|
22165
|
-
break;
|
|
22166
|
-
}
|
|
22167
|
-
subconverterResult = HtmlRuleConverter.convertToAdg(rule);
|
|
22168
|
-
break;
|
|
22169
|
-
// Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
|
|
22170
|
-
case CosmeticRuleType.JsInjectionRule:
|
|
22171
|
-
subconverterResult = createNodeConversionResult([rule], false);
|
|
22172
|
-
break;
|
|
22173
|
-
default:
|
|
22174
|
-
throw new RuleConversionError('Unsupported cosmetic rule type');
|
|
21892
|
+
*[Symbol.iterator]() {
|
|
21893
|
+
for (const item of this.cache) {
|
|
21894
|
+
yield item;
|
|
22175
21895
|
}
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
// uBO doesn't support this rule:
|
|
22181
|
-
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
22182
|
-
if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
22183
|
-
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
22184
|
-
}
|
|
22185
|
-
convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
|
|
22186
|
-
} else if (rule.syntax === AdblockSyntax.Abp) {
|
|
22187
|
-
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
22188
|
-
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
21896
|
+
for (const item of this.oldCache) {
|
|
21897
|
+
const [key] = item;
|
|
21898
|
+
if (!this.cache.has(key)) {
|
|
21899
|
+
yield item;
|
|
22189
21900
|
}
|
|
22190
21901
|
}
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
|
|
22198
|
-
});
|
|
22199
|
-
return subconverterResult;
|
|
21902
|
+
}
|
|
21903
|
+
get size() {
|
|
21904
|
+
let oldCacheSize = 0;
|
|
21905
|
+
for (const key of this.oldCache.keys()) {
|
|
21906
|
+
if (!this.cache.has(key)) {
|
|
21907
|
+
oldCacheSize++;
|
|
21908
|
+
}
|
|
22200
21909
|
}
|
|
22201
|
-
return
|
|
21910
|
+
return Math.min(this._size + oldCacheSize, this.maxSize);
|
|
22202
21911
|
}
|
|
22203
21912
|
}
|
|
21913
|
+
var quickLru = QuickLRU;
|
|
21914
|
+
const mapObj = mapObjExports;
|
|
21915
|
+
const camelCase = camelcaseExports;
|
|
21916
|
+
const QuickLru = quickLru;
|
|
21917
|
+
const has = (array, key) => array.some(x => {
|
|
21918
|
+
if (typeof x === 'string') {
|
|
21919
|
+
return x === key;
|
|
21920
|
+
}
|
|
21921
|
+
x.lastIndex = 0;
|
|
21922
|
+
return x.test(key);
|
|
21923
|
+
});
|
|
21924
|
+
const cache = new QuickLru({
|
|
21925
|
+
maxSize: 100000
|
|
21926
|
+
});
|
|
22204
21927
|
|
|
22205
|
-
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
*/
|
|
22211
|
-
const ABP_RESOURCE_PREFIX = 'abp-resource:';
|
|
22212
|
-
const ABP_RESOURCE_PREFIX_LENGTH = ABP_RESOURCE_PREFIX.length;
|
|
22213
|
-
/**
|
|
22214
|
-
* Transforms the name of an ABP redirect to a normalized form.
|
|
22215
|
-
*
|
|
22216
|
-
* @param name Redirect name to normalize.
|
|
22217
|
-
*
|
|
22218
|
-
* @returns Normalized redirect name.
|
|
22219
|
-
*
|
|
22220
|
-
* @example
|
|
22221
|
-
* abpRedirectNameNormalizer('abp-resource:my-resource') // => 'my-resource'
|
|
22222
|
-
*/
|
|
22223
|
-
const abpRedirectNameNormalizer = name => {
|
|
22224
|
-
if (name.startsWith(ABP_RESOURCE_PREFIX)) {
|
|
22225
|
-
return name.slice(ABP_RESOURCE_PREFIX_LENGTH);
|
|
21928
|
+
// Reproduces behavior from `map-obj`
|
|
21929
|
+
const isObject = value => typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
21930
|
+
const camelCaseConvert = (input, options) => {
|
|
21931
|
+
if (!isObject(input)) {
|
|
21932
|
+
return input;
|
|
22226
21933
|
}
|
|
22227
|
-
|
|
21934
|
+
options = {
|
|
21935
|
+
deep: false,
|
|
21936
|
+
pascalCase: false,
|
|
21937
|
+
...options
|
|
21938
|
+
};
|
|
21939
|
+
const {
|
|
21940
|
+
exclude,
|
|
21941
|
+
pascalCase,
|
|
21942
|
+
stopPaths,
|
|
21943
|
+
deep
|
|
21944
|
+
} = options;
|
|
21945
|
+
const stopPathsSet = new Set(stopPaths);
|
|
21946
|
+
const makeMapper = parentPath => (key, value) => {
|
|
21947
|
+
if (deep && isObject(value)) {
|
|
21948
|
+
const path = parentPath === undefined ? key : `${parentPath}.${key}`;
|
|
21949
|
+
if (!stopPathsSet.has(path)) {
|
|
21950
|
+
value = mapObj(value, makeMapper(path));
|
|
21951
|
+
}
|
|
21952
|
+
}
|
|
21953
|
+
if (!(exclude && has(exclude, key))) {
|
|
21954
|
+
const cacheKey = pascalCase ? `${key}_` : key;
|
|
21955
|
+
if (cache.has(cacheKey)) {
|
|
21956
|
+
key = cache.get(cacheKey);
|
|
21957
|
+
} else {
|
|
21958
|
+
const returnValue = camelCase(key, {
|
|
21959
|
+
pascalCase,
|
|
21960
|
+
locale: false
|
|
21961
|
+
});
|
|
21962
|
+
if (key.length < 100) {
|
|
21963
|
+
// Prevent abuse
|
|
21964
|
+
cache.set(cacheKey, returnValue);
|
|
21965
|
+
}
|
|
21966
|
+
key = returnValue;
|
|
21967
|
+
}
|
|
21968
|
+
}
|
|
21969
|
+
return [key, value];
|
|
21970
|
+
};
|
|
21971
|
+
return mapObj(input, makeMapper(undefined));
|
|
22228
21972
|
};
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
class RedirectsCompatibilityTable extends CompatibilityTableBase {
|
|
22233
|
-
/**
|
|
22234
|
-
* Creates a new instance of the compatibility table for redirects.
|
|
22235
|
-
*
|
|
22236
|
-
* @param data Compatibility table data.
|
|
22237
|
-
*/
|
|
22238
|
-
constructor(data) {
|
|
22239
|
-
super(data, abpRedirectNameNormalizer);
|
|
21973
|
+
var camelcaseKeys = (input, options) => {
|
|
21974
|
+
if (Array.isArray(input)) {
|
|
21975
|
+
return Object.keys(input).map(key => camelCaseConvert(input[key], options));
|
|
22240
21976
|
}
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
*/
|
|
22245
|
-
deepFreeze(redirectsCompatibilityTableData);
|
|
22246
|
-
/**
|
|
22247
|
-
* Compatibility table instance for redirects.
|
|
22248
|
-
*/
|
|
22249
|
-
const redirectsCompatibilityTable = new RedirectsCompatibilityTable(redirectsCompatibilityTableData);
|
|
21977
|
+
return camelCaseConvert(input, options);
|
|
21978
|
+
};
|
|
21979
|
+
var camelCaseKeys = /*@__PURE__*/getDefaultExportFromCjs(camelcaseKeys);
|
|
22250
21980
|
|
|
22251
21981
|
/**
|
|
22252
|
-
* @file
|
|
21982
|
+
* @file Zod camelCase utility.
|
|
22253
21983
|
*/
|
|
21984
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
22254
21985
|
/**
|
|
22255
|
-
*
|
|
21986
|
+
* Transforms Zod schema to camelCase.
|
|
21987
|
+
*
|
|
21988
|
+
* @param zod Zod schema.
|
|
21989
|
+
*
|
|
21990
|
+
* @returns Zod schema with camelCase properties.
|
|
21991
|
+
*
|
|
21992
|
+
* @see {@link https://github.com/colinhacks/zod/issues/486#issuecomment-1501097361}
|
|
22256
21993
|
*/
|
|
22257
|
-
|
|
21994
|
+
const zodToCamelCase = zod => {
|
|
21995
|
+
return zod.transform(val => camelCaseKeys(val));
|
|
21996
|
+
};
|
|
21997
|
+
|
|
22258
21998
|
/**
|
|
22259
|
-
*
|
|
21999
|
+
* @file Base compatibility data schema, which is commonly used in compatibility tables.
|
|
22260
22000
|
*/
|
|
22261
|
-
deepFreeze(scriptletsCompatibilityTableData);
|
|
22262
22001
|
/**
|
|
22263
|
-
*
|
|
22002
|
+
* Zod schema for boolean values. Accepts both boolean and string values.
|
|
22264
22003
|
*/
|
|
22265
|
-
const
|
|
22266
|
-
|
|
22267
|
-
/* eslint-disable no-bitwise */
|
|
22004
|
+
const booleanSchema = zod.union([zod.string().transform(val => val.trim().toLowerCase() === 'true'), zod.boolean()]);
|
|
22268
22005
|
/**
|
|
22269
|
-
*
|
|
22006
|
+
* Zod schema for non-empty string values.
|
|
22270
22007
|
*/
|
|
22008
|
+
const nonEmptyStringSchema = zod.string().transform(val => val.trim()).pipe(zod.string().min(1));
|
|
22271
22009
|
/**
|
|
22272
|
-
*
|
|
22273
|
-
*
|
|
22010
|
+
* Zod schema for base compatibility data.
|
|
22011
|
+
* Here we use snake_case properties because the compatibility data is stored in YAML files.
|
|
22274
22012
|
*/
|
|
22275
|
-
const
|
|
22013
|
+
const baseCompatibilityDataSchema = zod.object({
|
|
22014
|
+
/**
|
|
22015
|
+
* Name of the actual entity.
|
|
22016
|
+
*/
|
|
22017
|
+
name: nonEmptyStringSchema,
|
|
22018
|
+
/**
|
|
22019
|
+
* List of aliases for the entity (if any).
|
|
22020
|
+
*/
|
|
22021
|
+
aliases: zod.array(nonEmptyStringSchema).nullable().default(null),
|
|
22022
|
+
/**
|
|
22023
|
+
* Short description of the actual entity.
|
|
22024
|
+
* If not specified or it's value is `null`, then the description is not available.
|
|
22025
|
+
*/
|
|
22026
|
+
description: nonEmptyStringSchema.nullable().default(null),
|
|
22027
|
+
/**
|
|
22028
|
+
* Link to the documentation. If not specified or it's value is `null`, then the documentation is not available.
|
|
22029
|
+
*/
|
|
22030
|
+
docs: nonEmptyStringSchema.nullable().default(null),
|
|
22031
|
+
/**
|
|
22032
|
+
* The version of the adblocker in which the entity was added.
|
|
22033
|
+
* For AdGuard resources, the version of the library is specified.
|
|
22034
|
+
*/
|
|
22035
|
+
version_added: nonEmptyStringSchema.nullable().default(null),
|
|
22036
|
+
/**
|
|
22037
|
+
* The version of the adblocker when the entity was removed.
|
|
22038
|
+
*/
|
|
22039
|
+
version_removed: nonEmptyStringSchema.nullable().default(null),
|
|
22040
|
+
/**
|
|
22041
|
+
* Describes whether the entity is deprecated.
|
|
22042
|
+
*/
|
|
22043
|
+
deprecated: booleanSchema.default(false),
|
|
22044
|
+
/**
|
|
22045
|
+
* Message that describes why the entity is deprecated.
|
|
22046
|
+
* If not specified or it's value is `null`, then the message is not available.
|
|
22047
|
+
* It's value is omitted if the entity is not marked as deprecated.
|
|
22048
|
+
*/
|
|
22049
|
+
deprecation_message: nonEmptyStringSchema.nullable().default(null),
|
|
22050
|
+
/**
|
|
22051
|
+
* Describes whether the entity is removed; for *already removed* features.
|
|
22052
|
+
*/
|
|
22053
|
+
removed: booleanSchema.default(false),
|
|
22054
|
+
/**
|
|
22055
|
+
* Message that describes why the entity is removed.
|
|
22056
|
+
* If not specified or it's value is `null`, then the message is not available.
|
|
22057
|
+
* It's value is omitted if the entity is not marked as deprecated.
|
|
22058
|
+
*/
|
|
22059
|
+
removal_message: nonEmptyStringSchema.nullable().default(null)
|
|
22060
|
+
});
|
|
22276
22061
|
/**
|
|
22277
|
-
*
|
|
22278
|
-
* Safari content blockers.
|
|
22062
|
+
* Zod schema for base compatibility data with camelCase properties.
|
|
22279
22063
|
*/
|
|
22280
|
-
|
|
22064
|
+
zodToCamelCase(baseCompatibilityDataSchema);
|
|
22281
22065
|
/**
|
|
22282
|
-
*
|
|
22283
|
-
*
|
|
22284
|
-
* @param rawPlatforms Raw platform string, e.g. 'adg_safari_any|adg_os_any'.
|
|
22066
|
+
* Refinement logic for base compatibility data.
|
|
22285
22067
|
*
|
|
22286
|
-
* @
|
|
22287
|
-
|
|
22288
|
-
const parseRawPlatforms = rawPlatforms => {
|
|
22289
|
-
// e.g. 'adg_safari_any|adg_os_any'
|
|
22290
|
-
const rawPlatformList = rawPlatforms.split(PLATFORM_SEPARATOR).map(rawPlatform => rawPlatform.trim());
|
|
22291
|
-
let result = 0;
|
|
22292
|
-
for (let rawPlatform of rawPlatformList) {
|
|
22293
|
-
// negation, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except Safari content blockers
|
|
22294
|
-
let negated = false;
|
|
22295
|
-
if (rawPlatform.startsWith(PLATFORM_NEGATION)) {
|
|
22296
|
-
negated = true;
|
|
22297
|
-
rawPlatform = rawPlatform.slice(1).trim();
|
|
22298
|
-
}
|
|
22299
|
-
const platform = SPECIFIC_PLATFORM_MAP.get(rawPlatform) ?? GENERIC_PLATFORM_MAP.get(rawPlatform);
|
|
22300
|
-
if (isUndefined(platform)) {
|
|
22301
|
-
throw new Error(`Unknown platform: ${rawPlatform}`);
|
|
22302
|
-
}
|
|
22303
|
-
if (negated) {
|
|
22304
|
-
result &= ~platform;
|
|
22305
|
-
} else {
|
|
22306
|
-
result |= platform;
|
|
22307
|
-
}
|
|
22308
|
-
}
|
|
22309
|
-
if (result === 0) {
|
|
22310
|
-
throw new Error('No platforms specified');
|
|
22311
|
-
}
|
|
22312
|
-
return result;
|
|
22313
|
-
};
|
|
22314
|
-
/**
|
|
22315
|
-
* Platform schema.
|
|
22068
|
+
* @param data Base compatibility data.
|
|
22069
|
+
* @param ctx Refinement context.
|
|
22316
22070
|
*/
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
};
|
|
22324
|
-
const isObject$1 = value => typeof value === 'object' && value !== null;
|
|
22325
|
-
const mapObjectSkip = Symbol('skip');
|
|
22326
|
-
|
|
22327
|
-
// Customized for this use-case
|
|
22328
|
-
const isObjectCustom = value => isObject$1(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
22329
|
-
const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
|
|
22330
|
-
options = {
|
|
22331
|
-
deep: false,
|
|
22332
|
-
target: {},
|
|
22333
|
-
...options
|
|
22334
|
-
};
|
|
22335
|
-
if (isSeen.has(object)) {
|
|
22336
|
-
return isSeen.get(object);
|
|
22337
|
-
}
|
|
22338
|
-
isSeen.set(object, options.target);
|
|
22339
|
-
const {
|
|
22340
|
-
target
|
|
22341
|
-
} = options;
|
|
22342
|
-
delete options.target;
|
|
22343
|
-
const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element);
|
|
22344
|
-
if (Array.isArray(object)) {
|
|
22345
|
-
return mapArray(object);
|
|
22346
|
-
}
|
|
22347
|
-
for (const [key, value] of Object.entries(object)) {
|
|
22348
|
-
const mapResult = mapper(key, value, object);
|
|
22349
|
-
if (mapResult === mapObjectSkip) {
|
|
22350
|
-
continue;
|
|
22351
|
-
}
|
|
22352
|
-
let [newKey, newValue, {
|
|
22353
|
-
shouldRecurse = true
|
|
22354
|
-
} = {}] = mapResult;
|
|
22355
|
-
|
|
22356
|
-
// Drop `__proto__` keys.
|
|
22357
|
-
if (newKey === '__proto__') {
|
|
22358
|
-
continue;
|
|
22359
|
-
}
|
|
22360
|
-
if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
|
|
22361
|
-
newValue = Array.isArray(newValue) ? mapArray(newValue) : mapObject(newValue, mapper, options, isSeen);
|
|
22362
|
-
}
|
|
22363
|
-
target[newKey] = newValue;
|
|
22364
|
-
}
|
|
22365
|
-
return target;
|
|
22366
|
-
};
|
|
22367
|
-
mapObj$1.exports = (object, mapper, options) => {
|
|
22368
|
-
if (!isObject$1(object)) {
|
|
22369
|
-
throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
|
|
22370
|
-
}
|
|
22371
|
-
return mapObject(object, mapper, options);
|
|
22372
|
-
};
|
|
22373
|
-
mapObj$1.exports.mapObjectSkip = mapObjectSkip;
|
|
22374
|
-
var mapObjExports = mapObj$1.exports;
|
|
22375
|
-
var camelcase = {
|
|
22376
|
-
exports: {}
|
|
22377
|
-
};
|
|
22378
|
-
const UPPERCASE = /[\p{Lu}]/u;
|
|
22379
|
-
const LOWERCASE = /[\p{Ll}]/u;
|
|
22380
|
-
const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
|
|
22381
|
-
const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
|
|
22382
|
-
const SEPARATORS = /[_.\- ]+/;
|
|
22383
|
-
const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
|
|
22384
|
-
const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
|
|
22385
|
-
const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
|
|
22386
|
-
const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
|
|
22387
|
-
let isLastCharLower = false;
|
|
22388
|
-
let isLastCharUpper = false;
|
|
22389
|
-
let isLastLastCharUpper = false;
|
|
22390
|
-
for (let i = 0; i < string.length; i++) {
|
|
22391
|
-
const character = string[i];
|
|
22392
|
-
if (isLastCharLower && UPPERCASE.test(character)) {
|
|
22393
|
-
string = string.slice(0, i) + '-' + string.slice(i);
|
|
22394
|
-
isLastCharLower = false;
|
|
22395
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
22396
|
-
isLastCharUpper = true;
|
|
22397
|
-
i++;
|
|
22398
|
-
} else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
|
|
22399
|
-
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
|
|
22400
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
22401
|
-
isLastCharUpper = false;
|
|
22402
|
-
isLastCharLower = true;
|
|
22403
|
-
} else {
|
|
22404
|
-
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
|
|
22405
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
22406
|
-
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
|
|
22407
|
-
}
|
|
22408
|
-
}
|
|
22409
|
-
return string;
|
|
22410
|
-
};
|
|
22411
|
-
const preserveConsecutiveUppercase = (input, toLowerCase) => {
|
|
22412
|
-
LEADING_CAPITAL.lastIndex = 0;
|
|
22413
|
-
return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
|
|
22414
|
-
};
|
|
22415
|
-
const postProcess = (input, toUpperCase) => {
|
|
22416
|
-
SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
|
|
22417
|
-
NUMBERS_AND_IDENTIFIER.lastIndex = 0;
|
|
22418
|
-
return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)).replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
|
|
22419
|
-
};
|
|
22420
|
-
const camelCase$1 = (input, options) => {
|
|
22421
|
-
if (!(typeof input === 'string' || Array.isArray(input))) {
|
|
22422
|
-
throw new TypeError('Expected the input to be `string | string[]`');
|
|
22423
|
-
}
|
|
22424
|
-
options = {
|
|
22425
|
-
pascalCase: false,
|
|
22426
|
-
preserveConsecutiveUppercase: false,
|
|
22427
|
-
...options
|
|
22428
|
-
};
|
|
22429
|
-
if (Array.isArray(input)) {
|
|
22430
|
-
input = input.map(x => x.trim()).filter(x => x.length).join('-');
|
|
22431
|
-
} else {
|
|
22432
|
-
input = input.trim();
|
|
22433
|
-
}
|
|
22434
|
-
if (input.length === 0) {
|
|
22435
|
-
return '';
|
|
22436
|
-
}
|
|
22437
|
-
const toLowerCase = options.locale === false ? string => string.toLowerCase() : string => string.toLocaleLowerCase(options.locale);
|
|
22438
|
-
const toUpperCase = options.locale === false ? string => string.toUpperCase() : string => string.toLocaleUpperCase(options.locale);
|
|
22439
|
-
if (input.length === 1) {
|
|
22440
|
-
return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
|
|
22441
|
-
}
|
|
22442
|
-
const hasUpperCase = input !== toLowerCase(input);
|
|
22443
|
-
if (hasUpperCase) {
|
|
22444
|
-
input = preserveCamelCase(input, toLowerCase, toUpperCase);
|
|
22071
|
+
const baseRefineLogic = (data, ctx) => {
|
|
22072
|
+
if (data.deprecated && !data.deprecation_message) {
|
|
22073
|
+
ctx.addIssue({
|
|
22074
|
+
code: zod.ZodIssueCode.custom,
|
|
22075
|
+
message: 'deprecation_message is required for deprecated modifiers'
|
|
22076
|
+
});
|
|
22445
22077
|
}
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22078
|
+
if (!data.deprecated && data.deprecation_message) {
|
|
22079
|
+
ctx.addIssue({
|
|
22080
|
+
code: zod.ZodIssueCode.custom,
|
|
22081
|
+
message: 'deprecation_message is only allowed for deprecated modifiers'
|
|
22082
|
+
});
|
|
22451
22083
|
}
|
|
22452
|
-
if (
|
|
22453
|
-
|
|
22084
|
+
if (data.aliases && data.aliases.length !== new Set(data.aliases).size) {
|
|
22085
|
+
ctx.addIssue({
|
|
22086
|
+
code: zod.ZodIssueCode.custom,
|
|
22087
|
+
message: 'Aliases must be unique'
|
|
22088
|
+
});
|
|
22454
22089
|
}
|
|
22455
|
-
return postProcess(input, toUpperCase);
|
|
22456
22090
|
};
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
this._size = 0;
|
|
22477
|
-
if (typeof this.onEviction === 'function') {
|
|
22478
|
-
for (const [key, value] of this.oldCache.entries()) {
|
|
22479
|
-
this.onEviction(key, value);
|
|
22480
|
-
}
|
|
22481
|
-
}
|
|
22482
|
-
this.oldCache = this.cache;
|
|
22483
|
-
this.cache = new Map();
|
|
22484
|
-
}
|
|
22091
|
+
|
|
22092
|
+
/**
|
|
22093
|
+
* Checks if error has message.
|
|
22094
|
+
*
|
|
22095
|
+
* @param error Error object.
|
|
22096
|
+
* @returns If param is error.
|
|
22097
|
+
*/
|
|
22098
|
+
function isErrorWithMessage(error) {
|
|
22099
|
+
return typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string';
|
|
22100
|
+
}
|
|
22101
|
+
/**
|
|
22102
|
+
* Converts error to the error with message.
|
|
22103
|
+
*
|
|
22104
|
+
* @param maybeError Possible error.
|
|
22105
|
+
* @returns Error with message.
|
|
22106
|
+
*/
|
|
22107
|
+
function toErrorWithMessage(maybeError) {
|
|
22108
|
+
if (isErrorWithMessage(maybeError)) {
|
|
22109
|
+
return maybeError;
|
|
22485
22110
|
}
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
this.oldCache.delete(key);
|
|
22493
|
-
this._set(key, value);
|
|
22494
|
-
return value;
|
|
22495
|
-
}
|
|
22111
|
+
try {
|
|
22112
|
+
return new Error(JSON.stringify(maybeError));
|
|
22113
|
+
} catch {
|
|
22114
|
+
// fallback in case there's an error stringifying the maybeError
|
|
22115
|
+
// like with circular references for example.
|
|
22116
|
+
return new Error(String(maybeError));
|
|
22496
22117
|
}
|
|
22497
|
-
|
|
22498
|
-
|
|
22499
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22118
|
+
}
|
|
22119
|
+
/**
|
|
22120
|
+
* Converts error object to error with message. This method might be helpful to handle thrown errors.
|
|
22121
|
+
*
|
|
22122
|
+
* @param error Error object.
|
|
22123
|
+
*
|
|
22124
|
+
* @returns Message of the error.
|
|
22125
|
+
*/
|
|
22126
|
+
function getErrorMessage(error) {
|
|
22127
|
+
return toErrorWithMessage(error).message;
|
|
22128
|
+
}
|
|
22129
|
+
|
|
22130
|
+
/**
|
|
22131
|
+
* @file Schema for modifier data.
|
|
22132
|
+
*/
|
|
22133
|
+
/**
|
|
22134
|
+
* Known validators that don't need to be validated as regex.
|
|
22135
|
+
*/
|
|
22136
|
+
const KNOWN_VALIDATORS = new Set(['domain', 'pipe_separated_domains', 'regexp', 'url']);
|
|
22137
|
+
/**
|
|
22138
|
+
* Zod schema for modifier data.
|
|
22139
|
+
*/
|
|
22140
|
+
zodToCamelCase(baseCompatibilityDataSchema.extend({
|
|
22141
|
+
/**
|
|
22142
|
+
* List of modifiers that are incompatible with the actual one.
|
|
22143
|
+
*/
|
|
22144
|
+
conflicts: zod.array(nonEmptyStringSchema).nullable().default(null),
|
|
22145
|
+
/**
|
|
22146
|
+
* The actual modifier is incompatible with all other modifiers, except the ones listed in `conflicts`.
|
|
22147
|
+
*/
|
|
22148
|
+
inverse_conflicts: booleanSchema.default(false),
|
|
22149
|
+
/**
|
|
22150
|
+
* Describes whether the actual modifier supports value assignment. For example, `$domain` is assignable,
|
|
22151
|
+
* so it can be used like this: `$domain=domain.com\|~subdomain.domain.com`, where `=` is the assignment operator
|
|
22152
|
+
* and `domain.com\|~subdomain.domain.com` is the value.
|
|
22153
|
+
*/
|
|
22154
|
+
assignable: booleanSchema.default(false),
|
|
22155
|
+
/**
|
|
22156
|
+
* Describes whether the actual modifier can be negated. For example, `$third-party` is negatable,
|
|
22157
|
+
* so it can be used like this: `$~third-party`.
|
|
22158
|
+
*/
|
|
22159
|
+
negatable: booleanSchema.default(true),
|
|
22160
|
+
/**
|
|
22161
|
+
* The actual modifier can only be used in blocking rules, it cannot be used in exceptions.
|
|
22162
|
+
* If it's value is `true`, then the modifier can be used only in blocking rules.
|
|
22163
|
+
* `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
|
|
22164
|
+
*/
|
|
22165
|
+
block_only: booleanSchema.default(false),
|
|
22166
|
+
/**
|
|
22167
|
+
* The actual modifier can only be used in exceptions, it cannot be used in blocking rules.
|
|
22168
|
+
* If it's value is `true`, then the modifier can be used only in exceptions.
|
|
22169
|
+
* `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
|
|
22170
|
+
*/
|
|
22171
|
+
exception_only: booleanSchema.default(false),
|
|
22172
|
+
/**
|
|
22173
|
+
* Describes whether the *assignable* modifier value is required.
|
|
22174
|
+
* For example, `$cookie` is assignable but it can be used without a value in exception rules:
|
|
22175
|
+
* `@@\|\|example.com^$cookie`.
|
|
22176
|
+
* If `false`, the `value_format` is required, e.g. the value of `$app` should always be specified
|
|
22177
|
+
*/
|
|
22178
|
+
value_optional: booleanSchema.default(false),
|
|
22179
|
+
/**
|
|
22180
|
+
* Describes the format of the value for the *assignable* modifier.
|
|
22181
|
+
* Its value can be a regex pattern or a known validator name (e.g. `domain`, `pipe_separated_domains`, etc.).
|
|
22182
|
+
*/
|
|
22183
|
+
value_format: nonEmptyStringSchema.nullable().default(null)
|
|
22184
|
+
}).superRefine((data, ctx) => {
|
|
22185
|
+
// TODO: find something better, for now we can't add refine logic to the base schema:
|
|
22186
|
+
// https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
|
|
22187
|
+
baseRefineLogic(data, ctx);
|
|
22188
|
+
if (data.block_only && data.exception_only) {
|
|
22189
|
+
ctx.addIssue({
|
|
22190
|
+
code: zod.ZodIssueCode.custom,
|
|
22191
|
+
message: 'block_only and exception_only are mutually exclusive'
|
|
22192
|
+
});
|
|
22504
22193
|
}
|
|
22505
|
-
|
|
22506
|
-
|
|
22194
|
+
if (data.assignable && !data.value_format) {
|
|
22195
|
+
ctx.addIssue({
|
|
22196
|
+
code: zod.ZodIssueCode.custom,
|
|
22197
|
+
message: 'value_format is required for assignable modifiers'
|
|
22198
|
+
});
|
|
22507
22199
|
}
|
|
22508
|
-
|
|
22509
|
-
|
|
22510
|
-
|
|
22511
|
-
|
|
22512
|
-
|
|
22513
|
-
return this.oldCache.get(key);
|
|
22200
|
+
if (data.value_format) {
|
|
22201
|
+
const valueFormat = data.value_format.trim();
|
|
22202
|
+
// if it is a known validator, we don't need to validate it further
|
|
22203
|
+
if (KNOWN_VALIDATORS.has(valueFormat)) {
|
|
22204
|
+
return;
|
|
22514
22205
|
}
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22206
|
+
// otherwise, we need to validate it as a regex
|
|
22207
|
+
try {
|
|
22208
|
+
XRegExp(valueFormat);
|
|
22209
|
+
} catch (error) {
|
|
22210
|
+
ctx.addIssue({
|
|
22211
|
+
code: zod.ZodIssueCode.custom,
|
|
22212
|
+
message: getErrorMessage(error)
|
|
22213
|
+
});
|
|
22520
22214
|
}
|
|
22521
|
-
return this.oldCache.delete(key) || deleted;
|
|
22522
22215
|
}
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22216
|
+
}));
|
|
22217
|
+
|
|
22218
|
+
/**
|
|
22219
|
+
* @file Schema for redirect data.
|
|
22220
|
+
*/
|
|
22221
|
+
/**
|
|
22222
|
+
* Zod schema for redirect data.
|
|
22223
|
+
*/
|
|
22224
|
+
zodToCamelCase(baseCompatibilityDataSchema.extend({
|
|
22225
|
+
/**
|
|
22226
|
+
* Whether the redirect is blocking.
|
|
22227
|
+
*/
|
|
22228
|
+
is_blocking: booleanSchema.default(false)
|
|
22229
|
+
}).superRefine(baseRefineLogic));
|
|
22230
|
+
|
|
22231
|
+
/**
|
|
22232
|
+
* @file Schema for scriptlet data.
|
|
22233
|
+
*/
|
|
22234
|
+
/**
|
|
22235
|
+
* Zod schema for scriptlet parameter data.
|
|
22236
|
+
*/
|
|
22237
|
+
const scriptletParameterSchema = zod.object({
|
|
22238
|
+
/**
|
|
22239
|
+
* Name of the actual parameter.
|
|
22240
|
+
*/
|
|
22241
|
+
name: nonEmptyStringSchema,
|
|
22242
|
+
/**
|
|
22243
|
+
* Describes whether the parameter is required. Empty parameters are not allowed.
|
|
22244
|
+
*/
|
|
22245
|
+
required: booleanSchema,
|
|
22246
|
+
/**
|
|
22247
|
+
* Short description of the parameter.
|
|
22248
|
+
* If not specified or it's value is `null`,then the description is not available.
|
|
22249
|
+
*/
|
|
22250
|
+
description: nonEmptyStringSchema.nullable().default(null),
|
|
22251
|
+
/**
|
|
22252
|
+
* Regular expression that matches the value of the parameter.
|
|
22253
|
+
* If it's value is `null`, then the parameter value is not checked.
|
|
22254
|
+
*/
|
|
22255
|
+
pattern: nonEmptyStringSchema.nullable().default(null),
|
|
22256
|
+
/**
|
|
22257
|
+
* Default value of the parameter (if any).
|
|
22258
|
+
*/
|
|
22259
|
+
default: nonEmptyStringSchema.nullable().default(null),
|
|
22260
|
+
/**
|
|
22261
|
+
* Describes whether the parameter is used only for debugging purposes.
|
|
22262
|
+
*/
|
|
22263
|
+
debug: booleanSchema.default(false)
|
|
22264
|
+
});
|
|
22265
|
+
/**
|
|
22266
|
+
* Zod schema for scriptlet parameters.
|
|
22267
|
+
*/
|
|
22268
|
+
const scriptletParametersSchema = zod.array(scriptletParameterSchema);
|
|
22269
|
+
/**
|
|
22270
|
+
* Zod schema for scriptlet data.
|
|
22271
|
+
*/
|
|
22272
|
+
zodToCamelCase(baseCompatibilityDataSchema.extend({
|
|
22273
|
+
/**
|
|
22274
|
+
* List of parameters that the scriptlet accepts.
|
|
22275
|
+
* **Every** parameter should be listed here, because we check that the scriptlet is used correctly
|
|
22276
|
+
* (e.g. that the number of parameters is correct).
|
|
22277
|
+
*/
|
|
22278
|
+
parameters: scriptletParametersSchema.optional()
|
|
22279
|
+
}).superRefine((data, ctx) => {
|
|
22280
|
+
// TODO: find something better, for now we can't add refine logic to the base schema:
|
|
22281
|
+
// https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
|
|
22282
|
+
baseRefineLogic(data, ctx);
|
|
22283
|
+
// we don't allow required parameters after optional ones
|
|
22284
|
+
if (!data.parameters) {
|
|
22285
|
+
return;
|
|
22527
22286
|
}
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22287
|
+
let optionalFound = false;
|
|
22288
|
+
for (const parameter of data.parameters) {
|
|
22289
|
+
if (optionalFound && parameter.required) {
|
|
22290
|
+
ctx.addIssue({
|
|
22291
|
+
code: zod.ZodIssueCode.custom,
|
|
22292
|
+
message: 'Required parameters must be before optional ones'
|
|
22293
|
+
});
|
|
22531
22294
|
}
|
|
22532
|
-
|
|
22533
|
-
|
|
22534
|
-
for (const [, value] of this) {
|
|
22535
|
-
yield value;
|
|
22295
|
+
if (!parameter.required) {
|
|
22296
|
+
optionalFound = true;
|
|
22536
22297
|
}
|
|
22537
22298
|
}
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22299
|
+
}));
|
|
22300
|
+
|
|
22301
|
+
/**
|
|
22302
|
+
* @file Scriptlet injection rule converter
|
|
22303
|
+
*/
|
|
22304
|
+
const ABP_SCRIPTLET_PREFIX = 'abp-';
|
|
22305
|
+
const UBO_SCRIPTLET_PREFIX = 'ubo-';
|
|
22306
|
+
const UBO_SCRIPTLET_PREFIX_LENGTH = UBO_SCRIPTLET_PREFIX.length;
|
|
22307
|
+
const UBO_SCRIPTLET_JS_SUFFIX = '.js';
|
|
22308
|
+
const UBO_SCRIPTLET_JS_SUFFIX_LENGTH = UBO_SCRIPTLET_JS_SUFFIX.length;
|
|
22309
|
+
const COMMA_SEPARATOR = ',';
|
|
22310
|
+
const ADG_SET_CONSTANT_NAME = 'set-constant';
|
|
22311
|
+
const ADG_SET_CONSTANT_EMPTY_STRING = '';
|
|
22312
|
+
const ADG_SET_CONSTANT_EMPTY_ARRAY = 'emptyArr';
|
|
22313
|
+
const ADG_SET_CONSTANT_EMPTY_OBJECT = 'emptyObj';
|
|
22314
|
+
const UBO_SET_CONSTANT_EMPTY_STRING = '\'\'';
|
|
22315
|
+
const UBO_SET_CONSTANT_EMPTY_ARRAY = '[]';
|
|
22316
|
+
const UBO_SET_CONSTANT_EMPTY_OBJECT = '{}';
|
|
22317
|
+
const ADG_PREVENT_FETCH_NAME = 'prevent-fetch';
|
|
22318
|
+
const ADG_PREVENT_FETCH_EMPTY_STRING = '';
|
|
22319
|
+
const ADG_PREVENT_FETCH_WILDCARD = '*';
|
|
22320
|
+
const UBO_NO_FETCH_IF_WILDCARD = '/^/';
|
|
22321
|
+
const setConstantAdgToUboMap = {
|
|
22322
|
+
[ADG_SET_CONSTANT_EMPTY_STRING]: UBO_SET_CONSTANT_EMPTY_STRING,
|
|
22323
|
+
[ADG_SET_CONSTANT_EMPTY_ARRAY]: UBO_SET_CONSTANT_EMPTY_ARRAY,
|
|
22324
|
+
[ADG_SET_CONSTANT_EMPTY_OBJECT]: UBO_SET_CONSTANT_EMPTY_OBJECT
|
|
22325
|
+
};
|
|
22326
|
+
/**
|
|
22327
|
+
* Scriptlet injection rule converter class
|
|
22328
|
+
*
|
|
22329
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22330
|
+
*/
|
|
22331
|
+
class ScriptletRuleConverter extends RuleConverterBase {
|
|
22332
|
+
/**
|
|
22333
|
+
* Converts a scriptlet injection rule to AdGuard format, if possible.
|
|
22334
|
+
*
|
|
22335
|
+
* @param rule Rule node to convert
|
|
22336
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22337
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22338
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22339
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22340
|
+
*/
|
|
22341
|
+
static convertToAdg(rule) {
|
|
22342
|
+
// Ignore AdGuard rules
|
|
22343
|
+
if (rule.syntax === AdblockSyntax.Adg) {
|
|
22344
|
+
return createNodeConversionResult([rule], false);
|
|
22541
22345
|
}
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
|
|
22545
|
-
|
|
22346
|
+
const separator = rule.separator.value;
|
|
22347
|
+
let convertedSeparator = separator;
|
|
22348
|
+
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgJsInjectionException : CosmeticRuleSeparator.AdgJsInjection;
|
|
22349
|
+
const convertedScriptlets = [];
|
|
22350
|
+
// Special case: empty uBO exception scriptlet, e.g. `example.com#@#+js()`
|
|
22351
|
+
if (rule.syntax === AdblockSyntax.Ubo && rule.body.children.length === 1 && rule.body.children[0].children.length === 0) {
|
|
22352
|
+
convertedScriptlets.push(rule.body.children[0]);
|
|
22353
|
+
} else {
|
|
22354
|
+
for (const scriptlet of rule.body.children) {
|
|
22355
|
+
// Clone the node to avoid any side effects
|
|
22356
|
+
const scriptletClone = cloneScriptletRuleNode(scriptlet);
|
|
22357
|
+
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
22358
|
+
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
|
|
22359
|
+
// Add prefix if it's not already there
|
|
22360
|
+
let prefix;
|
|
22361
|
+
switch (rule.syntax) {
|
|
22362
|
+
case AdblockSyntax.Abp:
|
|
22363
|
+
prefix = ABP_SCRIPTLET_PREFIX;
|
|
22364
|
+
break;
|
|
22365
|
+
case AdblockSyntax.Ubo:
|
|
22366
|
+
prefix = UBO_SCRIPTLET_PREFIX;
|
|
22367
|
+
break;
|
|
22368
|
+
default:
|
|
22369
|
+
prefix = EMPTY;
|
|
22370
|
+
}
|
|
22371
|
+
if (!scriptletName.startsWith(prefix)) {
|
|
22372
|
+
setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
|
|
22373
|
+
}
|
|
22374
|
+
// ADG scriptlet parameters should be quoted, and single quoted are preferred
|
|
22375
|
+
setScriptletQuoteType(scriptletClone, QuoteType.Single);
|
|
22376
|
+
convertedScriptlets.push(scriptletClone);
|
|
22546
22377
|
}
|
|
22547
22378
|
}
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22379
|
+
return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
|
|
22380
|
+
const res = {
|
|
22381
|
+
category: rule.category,
|
|
22382
|
+
type: rule.type,
|
|
22383
|
+
syntax: AdblockSyntax.Adg,
|
|
22384
|
+
exception: rule.exception,
|
|
22385
|
+
domains: cloneDomainListNode(rule.domains),
|
|
22386
|
+
separator: {
|
|
22387
|
+
type: 'Value',
|
|
22388
|
+
value: convertedSeparator
|
|
22389
|
+
},
|
|
22390
|
+
body: {
|
|
22391
|
+
type: rule.body.type,
|
|
22392
|
+
children: [scriptlet]
|
|
22393
|
+
}
|
|
22394
|
+
};
|
|
22395
|
+
if (rule.modifiers) {
|
|
22396
|
+
res.modifiers = cloneModifierListNode(rule.modifiers);
|
|
22554
22397
|
}
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
}
|
|
22558
|
-
}
|
|
22559
|
-
var quickLru = QuickLRU;
|
|
22560
|
-
const mapObj = mapObjExports;
|
|
22561
|
-
const camelCase = camelcaseExports;
|
|
22562
|
-
const QuickLru = quickLru;
|
|
22563
|
-
const has = (array, key) => array.some(x => {
|
|
22564
|
-
if (typeof x === 'string') {
|
|
22565
|
-
return x === key;
|
|
22566
|
-
}
|
|
22567
|
-
x.lastIndex = 0;
|
|
22568
|
-
return x.test(key);
|
|
22569
|
-
});
|
|
22570
|
-
const cache = new QuickLru({
|
|
22571
|
-
maxSize: 100000
|
|
22572
|
-
});
|
|
22573
|
-
|
|
22574
|
-
// Reproduces behavior from `map-obj`
|
|
22575
|
-
const isObject = value => typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
|
|
22576
|
-
const camelCaseConvert = (input, options) => {
|
|
22577
|
-
if (!isObject(input)) {
|
|
22578
|
-
return input;
|
|
22398
|
+
return res;
|
|
22399
|
+
}), true);
|
|
22579
22400
|
}
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
if (deep && isObject(value)) {
|
|
22594
|
-
const path = parentPath === undefined ? key : `${parentPath}.${key}`;
|
|
22595
|
-
if (!stopPathsSet.has(path)) {
|
|
22596
|
-
value = mapObj(value, makeMapper(path));
|
|
22597
|
-
}
|
|
22401
|
+
/**
|
|
22402
|
+
* Converts a scriptlet injection rule to uBlock format, if possible.
|
|
22403
|
+
*
|
|
22404
|
+
* @param rule Rule node to convert
|
|
22405
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22406
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22407
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22408
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22409
|
+
*/
|
|
22410
|
+
static convertToUbo(rule) {
|
|
22411
|
+
// Ignore uBlock rules
|
|
22412
|
+
if (rule.syntax === AdblockSyntax.Ubo) {
|
|
22413
|
+
return createNodeConversionResult([rule], false);
|
|
22598
22414
|
}
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22415
|
+
const separator = rule.separator.value;
|
|
22416
|
+
let convertedSeparator = separator;
|
|
22417
|
+
convertedSeparator = rule.exception ? CosmeticRuleSeparator.ElementHidingException : CosmeticRuleSeparator.ElementHiding;
|
|
22418
|
+
const convertedScriptlets = [];
|
|
22419
|
+
// Special case: empty AdGuard exception scriptlet, e.g. `example.com#%#//scriptlet()`
|
|
22420
|
+
if (rule.syntax === AdblockSyntax.Adg && rule.body.children.length === 1 && rule.body.children[0].children.length === 0) {
|
|
22421
|
+
convertedScriptlets.push(rule.body.children[0]);
|
|
22422
|
+
} else {
|
|
22423
|
+
for (const scriptlet of rule.body.children) {
|
|
22424
|
+
// Clone the node to avoid any side effects
|
|
22425
|
+
const scriptletClone = cloneScriptletRuleNode(scriptlet);
|
|
22426
|
+
// Remove possible quotes just to make it easier to work with the scriptlet name
|
|
22427
|
+
const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
|
|
22428
|
+
let uboScriptletName;
|
|
22429
|
+
if (rule.syntax === AdblockSyntax.Adg && scriptletName.startsWith(UBO_SCRIPTLET_PREFIX)) {
|
|
22430
|
+
// Special case: AdGuard syntax 'preserves' the original scriptlet name,
|
|
22431
|
+
// so we need to convert it back by removing the uBO prefix
|
|
22432
|
+
uboScriptletName = scriptletName.slice(UBO_SCRIPTLET_PREFIX_LENGTH);
|
|
22433
|
+
} else {
|
|
22434
|
+
// Otherwise, try to find the corresponding uBO scriptlet name, or use the original one if not found
|
|
22435
|
+
const uboScriptlet = scriptletsCompatibilityTable.getFirst(scriptletName, GenericPlatform.UboAny);
|
|
22436
|
+
uboScriptletName = uboScriptlet?.name ?? scriptletName;
|
|
22437
|
+
}
|
|
22438
|
+
// Remove the '.js' suffix if it's there - its presence is not mandatory
|
|
22439
|
+
if (uboScriptletName.endsWith(UBO_SCRIPTLET_JS_SUFFIX)) {
|
|
22440
|
+
uboScriptletName = uboScriptletName.slice(0, -UBO_SCRIPTLET_JS_SUFFIX_LENGTH);
|
|
22441
|
+
}
|
|
22442
|
+
setScriptletName(scriptletClone, uboScriptletName);
|
|
22443
|
+
setScriptletQuoteType(scriptletClone, QuoteType.None);
|
|
22444
|
+
// Escape unescaped commas in parameters, because uBlock Origin uses them as separators.
|
|
22445
|
+
// For example, the following AdGuard rule:
|
|
22446
|
+
//
|
|
22447
|
+
// example.com#%#//scriptlet('spoof-css', '.adsbygoogle, #ads', 'visibility', 'visible')
|
|
22448
|
+
//
|
|
22449
|
+
// ↓↓ should be converted to ↓↓
|
|
22450
|
+
//
|
|
22451
|
+
// example.com##+js(spoof-css.js, .adsbygoogle\, #ads, visibility, visible)
|
|
22452
|
+
// ------------ ------------------- ---------- -------
|
|
22453
|
+
// arg 0 arg 1 arg 2 arg 3
|
|
22454
|
+
//
|
|
22455
|
+
// and we need to escape the comma in the second argument to prevent it from being treated
|
|
22456
|
+
// as two separate arguments.
|
|
22457
|
+
transformAllScriptletArguments(scriptletClone, value => {
|
|
22458
|
+
return QuoteUtils.escapeUnescapedOccurrences(value, COMMA_SEPARATOR);
|
|
22607
22459
|
});
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
22460
|
+
// Some scriptlets have special values that need to be converted
|
|
22461
|
+
switch (scriptletName) {
|
|
22462
|
+
case ADG_SET_CONSTANT_NAME:
|
|
22463
|
+
transformNthScriptletArgument(scriptletClone, 2, value => {
|
|
22464
|
+
return setConstantAdgToUboMap[value] ?? value;
|
|
22465
|
+
});
|
|
22466
|
+
break;
|
|
22467
|
+
case ADG_PREVENT_FETCH_NAME:
|
|
22468
|
+
transformNthScriptletArgument(scriptletClone, 1, value => {
|
|
22469
|
+
if (value === ADG_PREVENT_FETCH_EMPTY_STRING || value === ADG_PREVENT_FETCH_WILDCARD) {
|
|
22470
|
+
return UBO_NO_FETCH_IF_WILDCARD;
|
|
22471
|
+
}
|
|
22472
|
+
return value;
|
|
22473
|
+
});
|
|
22474
|
+
break;
|
|
22611
22475
|
}
|
|
22612
|
-
|
|
22476
|
+
convertedScriptlets.push(scriptletClone);
|
|
22613
22477
|
}
|
|
22614
22478
|
}
|
|
22615
|
-
return
|
|
22616
|
-
|
|
22617
|
-
|
|
22618
|
-
|
|
22619
|
-
|
|
22620
|
-
|
|
22621
|
-
|
|
22479
|
+
return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
|
|
22480
|
+
const res = {
|
|
22481
|
+
category: rule.category,
|
|
22482
|
+
type: rule.type,
|
|
22483
|
+
syntax: AdblockSyntax.Ubo,
|
|
22484
|
+
exception: rule.exception,
|
|
22485
|
+
domains: cloneDomainListNode(rule.domains),
|
|
22486
|
+
separator: {
|
|
22487
|
+
type: 'Value',
|
|
22488
|
+
value: convertedSeparator
|
|
22489
|
+
},
|
|
22490
|
+
body: {
|
|
22491
|
+
type: rule.body.type,
|
|
22492
|
+
children: [scriptlet]
|
|
22493
|
+
}
|
|
22494
|
+
};
|
|
22495
|
+
if (rule.modifiers) {
|
|
22496
|
+
res.modifiers = cloneModifierListNode(rule.modifiers);
|
|
22497
|
+
}
|
|
22498
|
+
return res;
|
|
22499
|
+
}), true);
|
|
22622
22500
|
}
|
|
22623
|
-
|
|
22624
|
-
};
|
|
22625
|
-
var camelCaseKeys = /*@__PURE__*/getDefaultExportFromCjs(camelcaseKeys);
|
|
22501
|
+
}
|
|
22626
22502
|
|
|
22627
22503
|
/**
|
|
22628
|
-
* @file
|
|
22504
|
+
* @file Utility functions for working with modifier nodes
|
|
22629
22505
|
*/
|
|
22630
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
22631
22506
|
/**
|
|
22632
|
-
*
|
|
22633
|
-
*
|
|
22634
|
-
* @param zod Zod schema.
|
|
22507
|
+
* Creates a modifier node
|
|
22635
22508
|
*
|
|
22636
|
-
* @
|
|
22509
|
+
* @param name Name of the modifier
|
|
22510
|
+
* @param value Value of the modifier
|
|
22511
|
+
* @param exception Whether the modifier is an exception
|
|
22512
|
+
* @returns Modifier node
|
|
22513
|
+
*/
|
|
22514
|
+
function createModifierNode(name, value = undefined, exception = false) {
|
|
22515
|
+
const result = {
|
|
22516
|
+
type: 'Modifier',
|
|
22517
|
+
exception,
|
|
22518
|
+
name: {
|
|
22519
|
+
type: 'Value',
|
|
22520
|
+
value: name
|
|
22521
|
+
}
|
|
22522
|
+
};
|
|
22523
|
+
if (!isUndefined(value)) {
|
|
22524
|
+
result.value = {
|
|
22525
|
+
type: 'Value',
|
|
22526
|
+
value
|
|
22527
|
+
};
|
|
22528
|
+
}
|
|
22529
|
+
return result;
|
|
22530
|
+
}
|
|
22531
|
+
/**
|
|
22532
|
+
* Creates a modifier list node
|
|
22637
22533
|
*
|
|
22638
|
-
* @
|
|
22534
|
+
* @param modifiers Modifiers to put in the list (optional, defaults to an empty list)
|
|
22535
|
+
* @returns Modifier list node
|
|
22639
22536
|
*/
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22537
|
+
function createModifierListNode(modifiers = []) {
|
|
22538
|
+
const result = {
|
|
22539
|
+
type: 'ModifierList',
|
|
22540
|
+
// We need to clone the modifiers to avoid side effects
|
|
22541
|
+
children: modifiers.length ? clone(modifiers) : []
|
|
22542
|
+
};
|
|
22543
|
+
return result;
|
|
22544
|
+
}
|
|
22643
22545
|
|
|
22644
22546
|
/**
|
|
22645
|
-
*
|
|
22547
|
+
* A very simple map extension that allows to store multiple values for the same key
|
|
22548
|
+
* by storing them in an array.
|
|
22549
|
+
*
|
|
22550
|
+
* @todo Add more methods if needed
|
|
22646
22551
|
*/
|
|
22552
|
+
class MultiValueMap extends Map {
|
|
22553
|
+
/**
|
|
22554
|
+
* Adds a value to the map. If the key already exists, the value will be appended to the existing array,
|
|
22555
|
+
* otherwise a new array will be created for the key.
|
|
22556
|
+
*
|
|
22557
|
+
* @param key Key to add
|
|
22558
|
+
* @param values Value(s) to add
|
|
22559
|
+
*/
|
|
22560
|
+
add(key, ...values) {
|
|
22561
|
+
let currentValues = super.get(key);
|
|
22562
|
+
if (isUndefined(currentValues)) {
|
|
22563
|
+
currentValues = [];
|
|
22564
|
+
super.set(key, values);
|
|
22565
|
+
}
|
|
22566
|
+
currentValues.push(...values);
|
|
22567
|
+
}
|
|
22568
|
+
}
|
|
22569
|
+
|
|
22647
22570
|
/**
|
|
22648
|
-
*
|
|
22571
|
+
* @file Cosmetic rule modifier converter from uBO to ADG
|
|
22649
22572
|
*/
|
|
22650
|
-
const
|
|
22573
|
+
const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
|
|
22574
|
+
const ADG_PATH_MODIFIER = 'path';
|
|
22651
22575
|
/**
|
|
22652
|
-
*
|
|
22576
|
+
* Special characters in modifier regexps that should be escaped
|
|
22653
22577
|
*/
|
|
22654
|
-
const
|
|
22578
|
+
const SPECIAL_MODIFIER_REGEX_CHARS = new Set([OPEN_SQUARE_BRACKET, CLOSE_SQUARE_BRACKET, COMMA, ESCAPE_CHARACTER]);
|
|
22655
22579
|
/**
|
|
22656
|
-
*
|
|
22657
|
-
* Here we use snake_case properties because the compatibility data is stored in YAML files.
|
|
22580
|
+
* Helper class for converting cosmetic rule modifiers from uBO to ADG
|
|
22658
22581
|
*/
|
|
22659
|
-
|
|
22660
|
-
/**
|
|
22661
|
-
* Name of the actual entity.
|
|
22662
|
-
*/
|
|
22663
|
-
name: nonEmptyStringSchema,
|
|
22664
|
-
/**
|
|
22665
|
-
* List of aliases for the entity (if any).
|
|
22666
|
-
*/
|
|
22667
|
-
aliases: zod.array(nonEmptyStringSchema).nullable().default(null),
|
|
22668
|
-
/**
|
|
22669
|
-
* Short description of the actual entity.
|
|
22670
|
-
* If not specified or it's value is `null`, then the description is not available.
|
|
22671
|
-
*/
|
|
22672
|
-
description: nonEmptyStringSchema.nullable().default(null),
|
|
22673
|
-
/**
|
|
22674
|
-
* Link to the documentation. If not specified or it's value is `null`, then the documentation is not available.
|
|
22675
|
-
*/
|
|
22676
|
-
docs: nonEmptyStringSchema.nullable().default(null),
|
|
22677
|
-
/**
|
|
22678
|
-
* The version of the adblocker in which the entity was added.
|
|
22679
|
-
* For AdGuard resources, the version of the library is specified.
|
|
22680
|
-
*/
|
|
22681
|
-
version_added: nonEmptyStringSchema.nullable().default(null),
|
|
22682
|
-
/**
|
|
22683
|
-
* The version of the adblocker when the entity was removed.
|
|
22684
|
-
*/
|
|
22685
|
-
version_removed: nonEmptyStringSchema.nullable().default(null),
|
|
22686
|
-
/**
|
|
22687
|
-
* Describes whether the entity is deprecated.
|
|
22688
|
-
*/
|
|
22689
|
-
deprecated: booleanSchema.default(false),
|
|
22690
|
-
/**
|
|
22691
|
-
* Message that describes why the entity is deprecated.
|
|
22692
|
-
* If not specified or it's value is `null`, then the message is not available.
|
|
22693
|
-
* It's value is omitted if the entity is not marked as deprecated.
|
|
22694
|
-
*/
|
|
22695
|
-
deprecation_message: nonEmptyStringSchema.nullable().default(null),
|
|
22582
|
+
class AdgCosmeticRuleModifierConverter {
|
|
22696
22583
|
/**
|
|
22697
|
-
*
|
|
22584
|
+
* Converts a uBO cosmetic rule modifier list to ADG, if possible.
|
|
22585
|
+
*
|
|
22586
|
+
* @param modifierList Cosmetic rule modifier list node to convert
|
|
22587
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
22588
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
22589
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
22590
|
+
* @throws If the modifier list cannot be converted
|
|
22591
|
+
* @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
|
|
22698
22592
|
*/
|
|
22699
|
-
|
|
22593
|
+
static convertFromUbo(modifierList) {
|
|
22594
|
+
const conversionMap = new MultiValueMap();
|
|
22595
|
+
modifierList.children.forEach((modifier, index) => {
|
|
22596
|
+
// :matches-path
|
|
22597
|
+
if (modifier.name.value === UBO_MATCHES_PATH_OPERATOR) {
|
|
22598
|
+
if (!modifier.value) {
|
|
22599
|
+
throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
|
|
22600
|
+
}
|
|
22601
|
+
const value = RegExpUtils.isRegexPattern(modifier.value.value) ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS) : modifier.value.value;
|
|
22602
|
+
// Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
|
|
22603
|
+
conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
|
|
22604
|
+
// We should negate the regexp if the modifier is an exception
|
|
22605
|
+
modifier.exception
|
|
22606
|
+
// eslint-disable-next-line max-len
|
|
22607
|
+
? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}` : value));
|
|
22608
|
+
}
|
|
22609
|
+
});
|
|
22610
|
+
// Check if we have any converted modifiers
|
|
22611
|
+
if (conversionMap.size) {
|
|
22612
|
+
const modifierListClone = clone(modifierList);
|
|
22613
|
+
// Replace the original modifiers with the converted ones
|
|
22614
|
+
modifierListClone.children = modifierListClone.children.map((modifier, index) => {
|
|
22615
|
+
const convertedModifier = conversionMap.get(index);
|
|
22616
|
+
return convertedModifier ?? modifier;
|
|
22617
|
+
}).flat();
|
|
22618
|
+
return createConversionResult(modifierListClone, true);
|
|
22619
|
+
}
|
|
22620
|
+
// Otherwise, just return the original modifier list
|
|
22621
|
+
return createConversionResult(modifierList, false);
|
|
22622
|
+
}
|
|
22623
|
+
}
|
|
22624
|
+
const ERROR_MESSAGES$1 = {
|
|
22625
|
+
// eslint-disable-next-line max-len
|
|
22626
|
+
INVALID_ATTRIBUTE_VALUE: `Expected '${getFormattedTokenName(TokenType$1.Ident)}' or '${getFormattedTokenName(TokenType$1.String)}' as attribute value, but got '%s' with value '%s`
|
|
22627
|
+
};
|
|
22628
|
+
var PseudoClasses;
|
|
22629
|
+
(function (PseudoClasses) {
|
|
22630
|
+
PseudoClasses["AbpContains"] = "-abp-contains";
|
|
22631
|
+
PseudoClasses["AbpHas"] = "-abp-has";
|
|
22632
|
+
PseudoClasses["Contains"] = "contains";
|
|
22633
|
+
PseudoClasses["Has"] = "has";
|
|
22634
|
+
PseudoClasses["HasText"] = "has-text";
|
|
22635
|
+
PseudoClasses["MatchesCss"] = "matches-css";
|
|
22636
|
+
PseudoClasses["MatchesCssAfter"] = "matches-css-after";
|
|
22637
|
+
PseudoClasses["MatchesCssBefore"] = "matches-css-before";
|
|
22638
|
+
PseudoClasses["Not"] = "not";
|
|
22639
|
+
})(PseudoClasses || (PseudoClasses = {}));
|
|
22640
|
+
var PseudoElements;
|
|
22641
|
+
(function (PseudoElements) {
|
|
22642
|
+
PseudoElements["After"] = "after";
|
|
22643
|
+
PseudoElements["Before"] = "before";
|
|
22644
|
+
})(PseudoElements || (PseudoElements = {}));
|
|
22645
|
+
const PSEUDO_ELEMENT_NAMES = new Set([PseudoElements.After, PseudoElements.Before]);
|
|
22646
|
+
/**
|
|
22647
|
+
* CSS selector converter
|
|
22648
|
+
*
|
|
22649
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22650
|
+
*/
|
|
22651
|
+
class CssSelectorConverter extends ConverterBase {
|
|
22700
22652
|
/**
|
|
22701
|
-
*
|
|
22702
|
-
*
|
|
22703
|
-
*
|
|
22653
|
+
* Converts Extended CSS elements to AdGuard-compatible ones
|
|
22654
|
+
*
|
|
22655
|
+
* @param selectorList Selector list to convert
|
|
22656
|
+
* @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
|
|
22657
|
+
* the converted node, and its `isConverted` flag indicates whether the original node was converted.
|
|
22658
|
+
* If the node was not converted, the result will contain the original node with the same object reference
|
|
22659
|
+
* @throws If the rule is invalid or incompatible
|
|
22704
22660
|
*/
|
|
22705
|
-
|
|
22706
|
-
|
|
22661
|
+
static convertToAdg(selectorList) {
|
|
22662
|
+
const stream = selectorList instanceof CssTokenStream ? selectorList : new CssTokenStream(selectorList);
|
|
22663
|
+
const converted = [];
|
|
22664
|
+
const convertAndPushPseudo = pseudo => {
|
|
22665
|
+
switch (pseudo) {
|
|
22666
|
+
case PseudoClasses.AbpContains:
|
|
22667
|
+
case PseudoClasses.HasText:
|
|
22668
|
+
converted.push(PseudoClasses.Contains);
|
|
22669
|
+
converted.push(OPEN_PARENTHESIS);
|
|
22670
|
+
break;
|
|
22671
|
+
case PseudoClasses.AbpHas:
|
|
22672
|
+
converted.push(PseudoClasses.Has);
|
|
22673
|
+
converted.push(OPEN_PARENTHESIS);
|
|
22674
|
+
break;
|
|
22675
|
+
// a bit special case:
|
|
22676
|
+
// - `:matches-css-before(...)` → `:matches-css(before, ...)`
|
|
22677
|
+
// - `:matches-css-after(...)` → `:matches-css(after, ...)`
|
|
22678
|
+
case PseudoClasses.MatchesCssBefore:
|
|
22679
|
+
case PseudoClasses.MatchesCssAfter:
|
|
22680
|
+
converted.push(PseudoClasses.MatchesCss);
|
|
22681
|
+
converted.push(OPEN_PARENTHESIS);
|
|
22682
|
+
converted.push(pseudo.substring(PseudoClasses.MatchesCss.length + 1));
|
|
22683
|
+
converted.push(COMMA);
|
|
22684
|
+
break;
|
|
22685
|
+
default:
|
|
22686
|
+
converted.push(pseudo);
|
|
22687
|
+
converted.push(OPEN_PARENTHESIS);
|
|
22688
|
+
break;
|
|
22689
|
+
}
|
|
22690
|
+
};
|
|
22691
|
+
while (!stream.isEof()) {
|
|
22692
|
+
const token = stream.getOrFail();
|
|
22693
|
+
if (token.type === TokenType$1.Colon) {
|
|
22694
|
+
// Advance colon
|
|
22695
|
+
stream.advance();
|
|
22696
|
+
converted.push(COLON);
|
|
22697
|
+
const tempToken = stream.getOrFail();
|
|
22698
|
+
// Double colon is a pseudo-element
|
|
22699
|
+
if (tempToken.type === TokenType$1.Colon) {
|
|
22700
|
+
stream.advance();
|
|
22701
|
+
converted.push(COLON);
|
|
22702
|
+
continue;
|
|
22703
|
+
}
|
|
22704
|
+
if (tempToken.type === TokenType$1.Ident) {
|
|
22705
|
+
const name = stream.source.slice(tempToken.start, tempToken.end);
|
|
22706
|
+
if (PSEUDO_ELEMENT_NAMES.has(name)) {
|
|
22707
|
+
// Add an extra colon to the name
|
|
22708
|
+
converted.push(COLON);
|
|
22709
|
+
converted.push(name);
|
|
22710
|
+
} else {
|
|
22711
|
+
// Add the name as is
|
|
22712
|
+
converted.push(name);
|
|
22713
|
+
}
|
|
22714
|
+
// Advance the names
|
|
22715
|
+
stream.advance();
|
|
22716
|
+
} else if (tempToken.type === TokenType$1.Function) {
|
|
22717
|
+
const name = stream.source.slice(tempToken.start, tempToken.end - 1); // omit the last parenthesis
|
|
22718
|
+
// :-abp-contains(...) → :contains(...)
|
|
22719
|
+
// :has-text(...) → :contains(...)
|
|
22720
|
+
// :-abp-has(...) → :has(...)
|
|
22721
|
+
convertAndPushPseudo(name);
|
|
22722
|
+
// Advance the function name
|
|
22723
|
+
stream.advance();
|
|
22724
|
+
}
|
|
22725
|
+
} else if (token.type === TokenType$1.OpenSquareBracket) {
|
|
22726
|
+
let tempToken;
|
|
22727
|
+
const {
|
|
22728
|
+
start
|
|
22729
|
+
} = token;
|
|
22730
|
+
stream.advance();
|
|
22731
|
+
// Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
|
|
22732
|
+
// For example:
|
|
22733
|
+
// - `[-ext-has=...]` → `:has(...)`
|
|
22734
|
+
// - `[-ext-contains=...]` → `:contains(...)`
|
|
22735
|
+
// - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
|
|
22736
|
+
stream.skipWhitespace();
|
|
22737
|
+
stream.expect(TokenType$1.Ident);
|
|
22738
|
+
tempToken = stream.getOrFail();
|
|
22739
|
+
let attr = stream.source.slice(tempToken.start, tempToken.end);
|
|
22740
|
+
// Skip if the attribute name is not a legacy Extended CSS one
|
|
22741
|
+
if (!(attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX) || attr.startsWith(ABP_EXT_CSS_PREFIX))) {
|
|
22742
|
+
converted.push(stream.source.slice(start, tempToken.end));
|
|
22743
|
+
stream.advance();
|
|
22744
|
+
continue;
|
|
22745
|
+
}
|
|
22746
|
+
if (attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)) {
|
|
22747
|
+
attr = attr.slice(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
|
|
22748
|
+
}
|
|
22749
|
+
stream.advance();
|
|
22750
|
+
stream.skipWhitespace();
|
|
22751
|
+
// Next token should be an equality operator (=), because Extended CSS attribute selectors
|
|
22752
|
+
// do not support other operators
|
|
22753
|
+
stream.expect(TokenType$1.Delim, {
|
|
22754
|
+
value: EQUALS
|
|
22755
|
+
});
|
|
22756
|
+
stream.advance();
|
|
22757
|
+
// Skip optional whitespace after the operator
|
|
22758
|
+
stream.skipWhitespace();
|
|
22759
|
+
// Parse attribute value
|
|
22760
|
+
tempToken = stream.getOrFail();
|
|
22761
|
+
// According to the spec, attribute value should be an identifier or a string
|
|
22762
|
+
if (tempToken.type !== TokenType$1.Ident && tempToken.type !== TokenType$1.String) {
|
|
22763
|
+
throw new Error(sprintf(ERROR_MESSAGES$1.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), stream.source.slice(tempToken.start, tempToken.end)));
|
|
22764
|
+
}
|
|
22765
|
+
const value = stream.source.slice(tempToken.start, tempToken.end);
|
|
22766
|
+
// Advance the attribute value
|
|
22767
|
+
stream.advance();
|
|
22768
|
+
// Skip optional whitespace after the attribute value
|
|
22769
|
+
stream.skipWhitespace();
|
|
22770
|
+
// Next character should be a closing square bracket
|
|
22771
|
+
// We don't allow flags for Extended CSS attribute selectors
|
|
22772
|
+
stream.expect(TokenType$1.CloseSquareBracket);
|
|
22773
|
+
stream.advance();
|
|
22774
|
+
converted.push(COLON);
|
|
22775
|
+
convertAndPushPseudo(attr);
|
|
22776
|
+
let processedValue = value.slice(1, -1); // omit the quotes
|
|
22777
|
+
if (attr === PseudoClasses.Has) {
|
|
22778
|
+
// TODO: Optimize this to avoid double tokenization
|
|
22779
|
+
processedValue = CssSelectorConverter.convertToAdg(processedValue).result;
|
|
22780
|
+
}
|
|
22781
|
+
converted.push(processedValue);
|
|
22782
|
+
converted.push(CLOSE_PARENTHESIS);
|
|
22783
|
+
} else {
|
|
22784
|
+
converted.push(stream.source.slice(token.start, token.end));
|
|
22785
|
+
// Advance the token
|
|
22786
|
+
stream.advance();
|
|
22787
|
+
}
|
|
22788
|
+
}
|
|
22789
|
+
const convertedSelectorList = converted.join(EMPTY);
|
|
22790
|
+
return createConversionResult(convertedSelectorList, stream.source !== convertedSelectorList);
|
|
22791
|
+
}
|
|
22792
|
+
}
|
|
22793
|
+
|
|
22707
22794
|
/**
|
|
22708
|
-
*
|
|
22795
|
+
* @file CSS injection rule converter
|
|
22709
22796
|
*/
|
|
22710
|
-
zodToCamelCase(baseCompatibilityDataSchema);
|
|
22711
22797
|
/**
|
|
22712
|
-
*
|
|
22798
|
+
* CSS injection rule converter class
|
|
22713
22799
|
*
|
|
22714
|
-
* @
|
|
22715
|
-
* @param ctx Refinement context.
|
|
22800
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22716
22801
|
*/
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22802
|
+
class CssInjectionRuleConverter extends RuleConverterBase {
|
|
22803
|
+
/**
|
|
22804
|
+
* Converts a CSS injection rule to AdGuard format, if possible.
|
|
22805
|
+
*
|
|
22806
|
+
* @param rule Rule node to convert
|
|
22807
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22808
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22809
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22810
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22811
|
+
*/
|
|
22812
|
+
static convertToAdg(rule) {
|
|
22813
|
+
const separator = rule.separator.value;
|
|
22814
|
+
let convertedSeparator = separator;
|
|
22815
|
+
const stream = new CssTokenStream(rule.body.selectorList.value);
|
|
22816
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
|
|
22817
|
+
// Change the separator if the rule contains ExtendedCSS elements,
|
|
22818
|
+
// but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
|
|
22819
|
+
// because sometimes we use it to force executing ExtendedCSS library.
|
|
22820
|
+
if (stream.hasAnySelectorExtendedCssNodeStrict() || rule.body.remove) {
|
|
22821
|
+
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgExtendedCssInjectionException : CosmeticRuleSeparator.AdgExtendedCssInjection;
|
|
22822
|
+
} else if (rule.syntax !== AdblockSyntax.Adg) {
|
|
22823
|
+
// If the original rule syntax is not AdGuard, use the default separator
|
|
22824
|
+
// e.g. if the input rule is from uBO, we need to convert ## to #$#.
|
|
22825
|
+
convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgCssInjectionException : CosmeticRuleSeparator.AdgCssInjection;
|
|
22826
|
+
}
|
|
22827
|
+
// Check if the rule needs to be converted
|
|
22828
|
+
if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
|
|
22829
|
+
// TODO: Replace with custom clone method
|
|
22830
|
+
const ruleClone = clone(rule);
|
|
22831
|
+
ruleClone.syntax = AdblockSyntax.Adg;
|
|
22832
|
+
ruleClone.separator.value = convertedSeparator;
|
|
22833
|
+
ruleClone.body.selectorList.value = convertedSelectorList.result;
|
|
22834
|
+
return createNodeConversionResult([ruleClone], true);
|
|
22835
|
+
}
|
|
22836
|
+
// Otherwise, return the original rule
|
|
22837
|
+
return createNodeConversionResult([rule], false);
|
|
22735
22838
|
}
|
|
22736
|
-
}
|
|
22839
|
+
}
|
|
22737
22840
|
|
|
22738
22841
|
/**
|
|
22739
|
-
*
|
|
22740
|
-
*
|
|
22741
|
-
* @param error Error object.
|
|
22742
|
-
* @returns If param is error.
|
|
22842
|
+
* @file Element hiding rule converter
|
|
22743
22843
|
*/
|
|
22744
|
-
function isErrorWithMessage(error) {
|
|
22745
|
-
return typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string';
|
|
22746
|
-
}
|
|
22747
22844
|
/**
|
|
22748
|
-
*
|
|
22845
|
+
* Element hiding rule converter class
|
|
22749
22846
|
*
|
|
22750
|
-
* @
|
|
22751
|
-
* @returns Error with message.
|
|
22847
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22752
22848
|
*/
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22849
|
+
class ElementHidingRuleConverter extends RuleConverterBase {
|
|
22850
|
+
/**
|
|
22851
|
+
* Converts an element hiding rule to AdGuard format, if possible.
|
|
22852
|
+
*
|
|
22853
|
+
* @param rule Rule node to convert
|
|
22854
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22855
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22856
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22857
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22858
|
+
*/
|
|
22859
|
+
static convertToAdg(rule) {
|
|
22860
|
+
const separator = rule.separator.value;
|
|
22861
|
+
let convertedSeparator = separator;
|
|
22862
|
+
const stream = new CssTokenStream(rule.body.selectorList.value);
|
|
22863
|
+
const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
|
|
22864
|
+
// Change the separator if the rule contains ExtendedCSS elements,
|
|
22865
|
+
// but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
|
|
22866
|
+
// because sometimes we use it to force executing ExtendedCSS library.
|
|
22867
|
+
if (stream.hasAnySelectorExtendedCssNodeStrict()) {
|
|
22868
|
+
convertedSeparator = rule.exception ? CosmeticRuleSeparator.ExtendedElementHidingException : CosmeticRuleSeparator.ExtendedElementHiding;
|
|
22869
|
+
}
|
|
22870
|
+
// Check if the rule needs to be converted
|
|
22871
|
+
if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
|
|
22872
|
+
// TODO: Replace with custom clone method
|
|
22873
|
+
const ruleClone = clone(rule);
|
|
22874
|
+
ruleClone.syntax = AdblockSyntax.Adg;
|
|
22875
|
+
ruleClone.separator.value = convertedSeparator;
|
|
22876
|
+
ruleClone.body.selectorList.value = convertedSelectorList.result;
|
|
22877
|
+
return createNodeConversionResult([ruleClone], true);
|
|
22878
|
+
}
|
|
22879
|
+
// Otherwise, return the original rule
|
|
22880
|
+
return createNodeConversionResult([rule], false);
|
|
22763
22881
|
}
|
|
22764
22882
|
}
|
|
22765
|
-
/**
|
|
22766
|
-
* Converts error object to error with message. This method might be helpful to handle thrown errors.
|
|
22767
|
-
*
|
|
22768
|
-
* @param error Error object.
|
|
22769
|
-
*
|
|
22770
|
-
* @returns Message of the error.
|
|
22771
|
-
*/
|
|
22772
|
-
function getErrorMessage(error) {
|
|
22773
|
-
return toErrorWithMessage(error).message;
|
|
22774
|
-
}
|
|
22775
22883
|
|
|
22776
22884
|
/**
|
|
22777
|
-
* @file
|
|
22778
|
-
*/
|
|
22779
|
-
/**
|
|
22780
|
-
* Known validators that don't need to be validated as regex.
|
|
22885
|
+
* @file Utility functions for working with network rule nodes
|
|
22781
22886
|
*/
|
|
22782
|
-
const KNOWN_VALIDATORS = new Set(['domain', 'pipe_separated_domains', 'regexp', 'url']);
|
|
22783
22887
|
/**
|
|
22784
|
-
*
|
|
22888
|
+
* Creates a network rule node
|
|
22889
|
+
*
|
|
22890
|
+
* @param pattern Rule pattern
|
|
22891
|
+
* @param modifiers Rule modifiers (optional, default: undefined)
|
|
22892
|
+
* @param exception Exception rule flag (optional, default: false)
|
|
22893
|
+
* @param syntax Adblock syntax (optional, default: Common)
|
|
22894
|
+
* @returns Network rule node
|
|
22785
22895
|
*/
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
/**
|
|
22796
|
-
* Describes whether the actual modifier supports value assignment. For example, `$domain` is assignable,
|
|
22797
|
-
* so it can be used like this: `$domain=domain.com\|~subdomain.domain.com`, where `=` is the assignment operator
|
|
22798
|
-
* and `domain.com\|~subdomain.domain.com` is the value.
|
|
22799
|
-
*/
|
|
22800
|
-
assignable: booleanSchema.default(false),
|
|
22801
|
-
/**
|
|
22802
|
-
* Describes whether the actual modifier can be negated. For example, `$third-party` is negatable,
|
|
22803
|
-
* so it can be used like this: `$~third-party`.
|
|
22804
|
-
*/
|
|
22805
|
-
negatable: booleanSchema.default(true),
|
|
22806
|
-
/**
|
|
22807
|
-
* The actual modifier can only be used in blocking rules, it cannot be used in exceptions.
|
|
22808
|
-
* If it's value is `true`, then the modifier can be used only in blocking rules.
|
|
22809
|
-
* `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
|
|
22810
|
-
*/
|
|
22811
|
-
block_only: booleanSchema.default(false),
|
|
22812
|
-
/**
|
|
22813
|
-
* The actual modifier can only be used in exceptions, it cannot be used in blocking rules.
|
|
22814
|
-
* If it's value is `true`, then the modifier can be used only in exceptions.
|
|
22815
|
-
* `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
|
|
22816
|
-
*/
|
|
22817
|
-
exception_only: booleanSchema.default(false),
|
|
22818
|
-
/**
|
|
22819
|
-
* Describes whether the *assignable* modifier value is required.
|
|
22820
|
-
* For example, `$cookie` is assignable but it can be used without a value in exception rules:
|
|
22821
|
-
* `@@\|\|example.com^$cookie`.
|
|
22822
|
-
* If `false`, the `value_format` is required, e.g. the value of `$app` should always be specified
|
|
22823
|
-
*/
|
|
22824
|
-
value_optional: booleanSchema.default(false),
|
|
22825
|
-
/**
|
|
22826
|
-
* Describes the format of the value for the *assignable* modifier.
|
|
22827
|
-
* Its value can be a regex pattern or a known validator name (e.g. `domain`, `pipe_separated_domains`, etc.).
|
|
22828
|
-
*/
|
|
22829
|
-
value_format: nonEmptyStringSchema.nullable().default(null)
|
|
22830
|
-
}).superRefine((data, ctx) => {
|
|
22831
|
-
// TODO: find something better, for now we can't add refine logic to the base schema:
|
|
22832
|
-
// https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
|
|
22833
|
-
baseRefineLogic(data, ctx);
|
|
22834
|
-
if (data.block_only && data.exception_only) {
|
|
22835
|
-
ctx.addIssue({
|
|
22836
|
-
code: zod.ZodIssueCode.custom,
|
|
22837
|
-
message: 'block_only and exception_only are mutually exclusive'
|
|
22838
|
-
});
|
|
22839
|
-
}
|
|
22840
|
-
if (data.assignable && !data.value_format) {
|
|
22841
|
-
ctx.addIssue({
|
|
22842
|
-
code: zod.ZodIssueCode.custom,
|
|
22843
|
-
message: 'value_format is required for assignable modifiers'
|
|
22844
|
-
});
|
|
22845
|
-
}
|
|
22846
|
-
if (data.value_format) {
|
|
22847
|
-
const valueFormat = data.value_format.trim();
|
|
22848
|
-
// if it is a known validator, we don't need to validate it further
|
|
22849
|
-
if (KNOWN_VALIDATORS.has(valueFormat)) {
|
|
22850
|
-
return;
|
|
22851
|
-
}
|
|
22852
|
-
// otherwise, we need to validate it as a regex
|
|
22853
|
-
try {
|
|
22854
|
-
XRegExp(valueFormat);
|
|
22855
|
-
} catch (error) {
|
|
22856
|
-
ctx.addIssue({
|
|
22857
|
-
code: zod.ZodIssueCode.custom,
|
|
22858
|
-
message: getErrorMessage(error)
|
|
22859
|
-
});
|
|
22896
|
+
function createNetworkRuleNode(pattern, modifiers = undefined, exception = false, syntax = AdblockSyntax.Common) {
|
|
22897
|
+
const result = {
|
|
22898
|
+
category: RuleCategory.Network,
|
|
22899
|
+
type: NetworkRuleType.NetworkRule,
|
|
22900
|
+
syntax,
|
|
22901
|
+
exception,
|
|
22902
|
+
pattern: {
|
|
22903
|
+
type: 'Value',
|
|
22904
|
+
value: pattern
|
|
22860
22905
|
}
|
|
22906
|
+
};
|
|
22907
|
+
if (!isUndefined(modifiers)) {
|
|
22908
|
+
result.modifiers = clone(modifiers);
|
|
22861
22909
|
}
|
|
22862
|
-
|
|
22910
|
+
return result;
|
|
22911
|
+
}
|
|
22863
22912
|
|
|
22864
22913
|
/**
|
|
22865
|
-
* @file
|
|
22914
|
+
* @file Converter for request header removal rules
|
|
22866
22915
|
*/
|
|
22916
|
+
const UBO_RESPONSEHEADER_FN = 'responseheader';
|
|
22917
|
+
const ADG_REMOVEHEADER_MODIFIER = 'removeheader';
|
|
22918
|
+
const ERROR_MESSAGES = {
|
|
22919
|
+
EMPTY_PARAMETER: `Empty parameter for '${UBO_RESPONSEHEADER_FN}' function`,
|
|
22920
|
+
EXPECTED_END_OF_RULE: "Expected end of rule, but got '%s'",
|
|
22921
|
+
MULTIPLE_DOMAINS_NOT_SUPPORTED: 'Multiple domains are not supported yet'
|
|
22922
|
+
};
|
|
22867
22923
|
/**
|
|
22868
|
-
*
|
|
22924
|
+
* Converter for request header removal rules
|
|
22925
|
+
*
|
|
22926
|
+
* @todo Implement `convertToUbo` (ABP currently doesn't support header removal rules)
|
|
22869
22927
|
*/
|
|
22870
|
-
|
|
22928
|
+
class HeaderRemovalRuleConverter extends RuleConverterBase {
|
|
22871
22929
|
/**
|
|
22872
|
-
*
|
|
22930
|
+
* Converts a header removal rule to AdGuard syntax, if possible.
|
|
22931
|
+
*
|
|
22932
|
+
* @param rule Rule node to convert
|
|
22933
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
22934
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
22935
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
22936
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22937
|
+
* @example
|
|
22938
|
+
* If the input rule is:
|
|
22939
|
+
* ```adblock
|
|
22940
|
+
* example.com##^responseheader(header-name)
|
|
22941
|
+
* ```
|
|
22942
|
+
* The output will be:
|
|
22943
|
+
* ```adblock
|
|
22944
|
+
* ||example.com^$removeheader=header-name
|
|
22945
|
+
* ```
|
|
22873
22946
|
*/
|
|
22874
|
-
|
|
22875
|
-
|
|
22947
|
+
static convertToAdg(rule) {
|
|
22948
|
+
// TODO: Add support for ABP syntax once it starts supporting header removal rules
|
|
22949
|
+
// Leave the rule as is if it's not a header removal rule
|
|
22950
|
+
if (rule.category !== RuleCategory.Cosmetic || rule.type !== CosmeticRuleType.HtmlFilteringRule) {
|
|
22951
|
+
return createNodeConversionResult([rule], false);
|
|
22952
|
+
}
|
|
22953
|
+
const stream = new CssTokenStream(rule.body.value);
|
|
22954
|
+
let token;
|
|
22955
|
+
// Skip leading whitespace
|
|
22956
|
+
stream.skipWhitespace();
|
|
22957
|
+
// Next token should be the `^` followed by a `responseheader` function
|
|
22958
|
+
token = stream.get();
|
|
22959
|
+
if (!token || token.type !== TokenType$1.Delim || rule.body.value[token.start] !== UBO_HTML_MASK) {
|
|
22960
|
+
return createNodeConversionResult([rule], false);
|
|
22961
|
+
}
|
|
22962
|
+
stream.advance();
|
|
22963
|
+
token = stream.get();
|
|
22964
|
+
if (!token) {
|
|
22965
|
+
return createNodeConversionResult([rule], false);
|
|
22966
|
+
}
|
|
22967
|
+
const functionName = rule.body.value.slice(token.start, token.end - 1);
|
|
22968
|
+
if (functionName !== UBO_RESPONSEHEADER_FN) {
|
|
22969
|
+
return createNodeConversionResult([rule], false);
|
|
22970
|
+
}
|
|
22971
|
+
// Parse the parameter
|
|
22972
|
+
const paramStart = token.end;
|
|
22973
|
+
stream.skipUntilBalanced();
|
|
22974
|
+
const paramEnd = stream.getOrFail().end;
|
|
22975
|
+
const param = rule.body.value.slice(paramStart, paramEnd - 1).trim();
|
|
22976
|
+
// Do not allow empty parameter
|
|
22977
|
+
if (param.length === 0) {
|
|
22978
|
+
throw new RuleConversionError(ERROR_MESSAGES.EMPTY_PARAMETER);
|
|
22979
|
+
}
|
|
22980
|
+
stream.expect(TokenType$1.CloseParenthesis);
|
|
22981
|
+
stream.advance();
|
|
22982
|
+
// Skip trailing whitespace after the function call
|
|
22983
|
+
stream.skipWhitespace();
|
|
22984
|
+
// Expect the end of the rule - so nothing should be left in the stream
|
|
22985
|
+
if (!stream.isEof()) {
|
|
22986
|
+
token = stream.getOrFail();
|
|
22987
|
+
throw new RuleConversionError(sprintf(ERROR_MESSAGES.EXPECTED_END_OF_RULE, getFormattedTokenName(token.type)));
|
|
22988
|
+
}
|
|
22989
|
+
// Prepare network rule pattern
|
|
22990
|
+
const pattern = [];
|
|
22991
|
+
if (rule.domains.children.length === 1) {
|
|
22992
|
+
// If the rule has only one domain, we can use a simple network rule pattern:
|
|
22993
|
+
// ||single-domain-from-the-rule^
|
|
22994
|
+
pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
|
|
22995
|
+
} else if (rule.domains.children.length > 1) {
|
|
22996
|
+
// TODO: Add support for multiple domains, for example:
|
|
22997
|
+
// example.com,example.org,example.net##^responseheader(header-name)
|
|
22998
|
+
// We should consider allowing $domain with $removeheader modifier,
|
|
22999
|
+
// for example:
|
|
23000
|
+
// $removeheader=header-name,domain=example.com|example.org|example.net
|
|
23001
|
+
throw new RuleConversionError(ERROR_MESSAGES.MULTIPLE_DOMAINS_NOT_SUPPORTED);
|
|
23002
|
+
}
|
|
23003
|
+
// Prepare network rule modifiers
|
|
23004
|
+
const modifiers = createModifierListNode();
|
|
23005
|
+
modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, param));
|
|
23006
|
+
// Construct the network rule
|
|
23007
|
+
return createNodeConversionResult([createNetworkRuleNode(pattern.join(EMPTY), modifiers,
|
|
23008
|
+
// Copy the exception flag
|
|
23009
|
+
rule.exception, AdblockSyntax.Adg)], true);
|
|
23010
|
+
}
|
|
23011
|
+
}
|
|
22876
23012
|
|
|
22877
23013
|
/**
|
|
22878
|
-
* @file
|
|
22879
|
-
*/
|
|
22880
|
-
/**
|
|
22881
|
-
* Zod schema for scriptlet parameter data.
|
|
22882
|
-
*/
|
|
22883
|
-
const scriptletParameterSchema = zod.object({
|
|
22884
|
-
/**
|
|
22885
|
-
* Name of the actual parameter.
|
|
22886
|
-
*/
|
|
22887
|
-
name: nonEmptyStringSchema,
|
|
22888
|
-
/**
|
|
22889
|
-
* Describes whether the parameter is required. Empty parameters are not allowed.
|
|
22890
|
-
*/
|
|
22891
|
-
required: booleanSchema,
|
|
22892
|
-
/**
|
|
22893
|
-
* Short description of the parameter.
|
|
22894
|
-
* If not specified or it's value is `null`,then the description is not available.
|
|
22895
|
-
*/
|
|
22896
|
-
description: nonEmptyStringSchema.nullable().default(null),
|
|
22897
|
-
/**
|
|
22898
|
-
* Regular expression that matches the value of the parameter.
|
|
22899
|
-
* If it's value is `null`, then the parameter value is not checked.
|
|
22900
|
-
*/
|
|
22901
|
-
pattern: nonEmptyStringSchema.nullable().default(null),
|
|
22902
|
-
/**
|
|
22903
|
-
* Default value of the parameter (if any).
|
|
22904
|
-
*/
|
|
22905
|
-
default: nonEmptyStringSchema.nullable().default(null),
|
|
22906
|
-
/**
|
|
22907
|
-
* Describes whether the parameter is used only for debugging purposes.
|
|
22908
|
-
*/
|
|
22909
|
-
debug: booleanSchema.default(false)
|
|
22910
|
-
});
|
|
22911
|
-
/**
|
|
22912
|
-
* Zod schema for scriptlet parameters.
|
|
23014
|
+
* @file Cosmetic rule converter
|
|
22913
23015
|
*/
|
|
22914
|
-
const scriptletParametersSchema = zod.array(scriptletParameterSchema);
|
|
22915
23016
|
/**
|
|
22916
|
-
*
|
|
23017
|
+
* Cosmetic rule converter class (also known as "non-basic rule converter")
|
|
23018
|
+
*
|
|
23019
|
+
* @todo Implement `convertToUbo` and `convertToAbp`
|
|
22917
23020
|
*/
|
|
22918
|
-
|
|
23021
|
+
class CosmeticRuleConverter extends RuleConverterBase {
|
|
22919
23022
|
/**
|
|
22920
|
-
*
|
|
22921
|
-
*
|
|
22922
|
-
*
|
|
23023
|
+
* Converts a cosmetic rule to AdGuard syntax, if possible.
|
|
23024
|
+
*
|
|
23025
|
+
* @param rule Rule node to convert
|
|
23026
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
23027
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
23028
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
23029
|
+
* @throws If the rule is invalid or cannot be converted
|
|
22923
23030
|
*/
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
22932
|
-
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
|
|
22938
|
-
|
|
23031
|
+
static convertToAdg(rule) {
|
|
23032
|
+
let subconverterResult;
|
|
23033
|
+
// Convert cosmetic rule based on its type
|
|
23034
|
+
switch (rule.type) {
|
|
23035
|
+
case CosmeticRuleType.ElementHidingRule:
|
|
23036
|
+
subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
|
|
23037
|
+
break;
|
|
23038
|
+
case CosmeticRuleType.ScriptletInjectionRule:
|
|
23039
|
+
subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
|
|
23040
|
+
break;
|
|
23041
|
+
case CosmeticRuleType.CssInjectionRule:
|
|
23042
|
+
subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
|
|
23043
|
+
break;
|
|
23044
|
+
case CosmeticRuleType.HtmlFilteringRule:
|
|
23045
|
+
// Handle special case: uBO response header filtering rule
|
|
23046
|
+
// TODO: Optimize double CSS tokenization here
|
|
23047
|
+
subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
|
|
23048
|
+
if (subconverterResult.isConverted) {
|
|
23049
|
+
break;
|
|
23050
|
+
}
|
|
23051
|
+
subconverterResult = HtmlRuleConverter.convertToAdg(rule);
|
|
23052
|
+
break;
|
|
23053
|
+
// Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
|
|
23054
|
+
case CosmeticRuleType.JsInjectionRule:
|
|
23055
|
+
subconverterResult = createNodeConversionResult([rule], false);
|
|
23056
|
+
break;
|
|
23057
|
+
default:
|
|
23058
|
+
throw new RuleConversionError('Unsupported cosmetic rule type');
|
|
23059
|
+
}
|
|
23060
|
+
let convertedModifiers;
|
|
23061
|
+
// Convert cosmetic rule modifiers, if any
|
|
23062
|
+
if (rule.modifiers) {
|
|
23063
|
+
if (rule.syntax === AdblockSyntax.Ubo) {
|
|
23064
|
+
// uBO doesn't support this rule:
|
|
23065
|
+
// example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
23066
|
+
if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
23067
|
+
throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
|
|
23068
|
+
}
|
|
23069
|
+
convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
|
|
23070
|
+
} else if (rule.syntax === AdblockSyntax.Abp) {
|
|
23071
|
+
// TODO: Implement once ABP starts supporting cosmetic rule modifiers
|
|
23072
|
+
throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
|
|
23073
|
+
}
|
|
23074
|
+
}
|
|
23075
|
+
if (subconverterResult.result.length > 1 || subconverterResult.isConverted || convertedModifiers && convertedModifiers.isConverted) {
|
|
23076
|
+
// Add modifier list to the subconverter result rules
|
|
23077
|
+
subconverterResult.result.forEach(subconverterRule => {
|
|
23078
|
+
if (convertedModifiers && subconverterRule.category === RuleCategory.Cosmetic) {
|
|
23079
|
+
// eslint-disable-next-line no-param-reassign
|
|
23080
|
+
subconverterRule.modifiers = convertedModifiers.result;
|
|
23081
|
+
}
|
|
22939
23082
|
});
|
|
23083
|
+
return subconverterResult;
|
|
22940
23084
|
}
|
|
22941
|
-
|
|
22942
|
-
|
|
23085
|
+
return createNodeConversionResult([rule], false);
|
|
23086
|
+
}
|
|
23087
|
+
/**
|
|
23088
|
+
* Converts a cosmetic rule to uBlock Origin syntax, if possible.
|
|
23089
|
+
*
|
|
23090
|
+
* @param rule Rule node to convert
|
|
23091
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
23092
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
23093
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
23094
|
+
* @throws If the rule is invalid or cannot be converted
|
|
23095
|
+
*/
|
|
23096
|
+
// TODO: Add support for other cosmetic rule types
|
|
23097
|
+
static convertToUbo(rule) {
|
|
23098
|
+
// Convert cosmetic rule based on its type
|
|
23099
|
+
if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
|
|
23100
|
+
if (rule.syntax === AdblockSyntax.Adg && rule.modifiers?.children.length) {
|
|
23101
|
+
// e.g. example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
|
|
23102
|
+
throw new RuleConversionError('uBO scriptlet injection rules do not support cosmetic rule modifiers');
|
|
23103
|
+
}
|
|
23104
|
+
return ScriptletRuleConverter.convertToUbo(rule);
|
|
22943
23105
|
}
|
|
23106
|
+
return createNodeConversionResult([rule], false);
|
|
22944
23107
|
}
|
|
22945
|
-
}
|
|
23108
|
+
}
|
|
22946
23109
|
|
|
22947
23110
|
/**
|
|
22948
23111
|
* @file Network rule modifier list converter.
|
|
@@ -23214,6 +23377,22 @@ class RuleConverter extends RuleConverterBase {
|
|
|
23214
23377
|
throw new RuleConversionError('Unknown rule category');
|
|
23215
23378
|
}
|
|
23216
23379
|
}
|
|
23380
|
+
/**
|
|
23381
|
+
* Converts an adblock filtering rule to uBlock Origin format, if possible.
|
|
23382
|
+
*
|
|
23383
|
+
* @param rule Rule node to convert
|
|
23384
|
+
* @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
|
|
23385
|
+
* the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
|
|
23386
|
+
* If the rule was not converted, the result array will contain the original node with the same object reference
|
|
23387
|
+
* @throws If the rule is invalid or cannot be converted
|
|
23388
|
+
*/
|
|
23389
|
+
// TODO: Add support for other rule types
|
|
23390
|
+
static convertToUbo(rule) {
|
|
23391
|
+
if (rule.category === RuleCategory.Cosmetic) {
|
|
23392
|
+
return CosmeticRuleConverter.convertToUbo(rule);
|
|
23393
|
+
}
|
|
23394
|
+
return createConversionResult([rule], false);
|
|
23395
|
+
}
|
|
23217
23396
|
}
|
|
23218
23397
|
|
|
23219
23398
|
/**
|
|
@@ -23607,7 +23786,7 @@ class ByteBuffer {
|
|
|
23607
23786
|
* @see {@link https://stackoverflow.com/a/62797156}
|
|
23608
23787
|
*/
|
|
23609
23788
|
const isChromium = () => {
|
|
23610
|
-
return typeof window !== 'undefined' && (Object.prototype.hasOwnProperty.call(window, 'chrome') || typeof window.navigator !== 'undefined' && /chrome/i.test(window.navigator.userAgent
|
|
23789
|
+
return typeof window !== 'undefined' && (Object.prototype.hasOwnProperty.call(window, 'chrome') || typeof window.navigator !== 'undefined' && /chrome/i.test(window.navigator.userAgent));
|
|
23611
23790
|
};
|
|
23612
23791
|
|
|
23613
23792
|
/* eslint-disable no-param-reassign */
|
|
@@ -23698,16 +23877,23 @@ class OutputByteBuffer extends ByteBuffer {
|
|
|
23698
23877
|
*/
|
|
23699
23878
|
offset;
|
|
23700
23879
|
/**
|
|
23701
|
-
* Size of the shared buffer for encoding strings.
|
|
23880
|
+
* Size of the shared buffer for encoding strings in bytes.
|
|
23881
|
+
* This is a divisor of ByteBuffer.CHUNK_SIZE and experience shows that this value works optimally.
|
|
23882
|
+
* This is sufficient for most strings that occur in filter lists (we checked average string length in popular
|
|
23883
|
+
* filter lists).
|
|
23702
23884
|
*/
|
|
23703
23885
|
static ENCODER_BUFFER_SIZE = 8192;
|
|
23704
23886
|
/**
|
|
23705
|
-
*
|
|
23887
|
+
* Length threshold for using a shared buffer for encoding strings.
|
|
23888
|
+
* This temp buffer is needed because we write the short strings in it
|
|
23889
|
+
* (so there is no need to constantly allocate a new buffer).
|
|
23890
|
+
* The reason for dividing ENCODER_BUFFER_SIZE by 4 is to ensure that the encoded string fits in the buffer,
|
|
23891
|
+
* if we also take into account the worst possible case (each character is encoded with 4 bytes).
|
|
23706
23892
|
*/
|
|
23707
23893
|
static SHORT_STRING_THRESHOLD = 2048; // 8192 / 4
|
|
23708
23894
|
/**
|
|
23709
23895
|
* Represents the maximum value that can be written as a 'storage optimized' unsigned integer.
|
|
23710
|
-
* 0x1FFFFFFF means 32 bits minus 3 bits
|
|
23896
|
+
* 0x1FFFFFFF means 29 bits — 32 bits minus 3 bits — because the last bit in each byte is a flag indicating
|
|
23711
23897
|
* if there are more bytes (except for the last byte).
|
|
23712
23898
|
*/
|
|
23713
23899
|
static MAX_OPTIMIZED_UINT = 0x1FFFFFFF;
|
|
@@ -24254,7 +24440,7 @@ class RuleCategorizer {
|
|
|
24254
24440
|
}
|
|
24255
24441
|
}
|
|
24256
24442
|
}
|
|
24257
|
-
const version = "2.0.0
|
|
24443
|
+
const version = "2.0.0";
|
|
24258
24444
|
|
|
24259
24445
|
/**
|
|
24260
24446
|
* @file AGTree version
|