@adguard/agtree 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog][keepachangelog], and this project adheres to [Semantic Versioning][semver].
6
6
 
7
+
8
+ ## 1.1.5 - 2023-09-05
9
+
10
+ ### Changed
11
+
12
+ - Validation of `$csp` and `$permissions` modifiers value
13
+ by custom pre-defined validator instead of regular expression
14
+
15
+ ### Added
16
+
17
+ - Exports to `package.json`
18
+
19
+ ## 1.1.4 - 2023-08-30
20
+
21
+ ### Fixed
22
+
23
+ - Validation of `$redirect` and `$replace` modifiers by `ModifierValidator.validate()`
24
+
25
+
7
26
  ## 1.1.3 - 2023-08-28
8
27
 
9
28
  ### Added
package/dist/agtree.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v1.1.3 (build date: Mon, 28 Aug 2023 16:19:09 GMT)
2
+ * AGTree v1.1.5 (build date: Tue, 05 Sep 2023 15:10:53 GMT)
3
3
  * (c) 2023 AdGuard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -88,6 +88,9 @@ exports.AdblockSyntax = void 0;
88
88
  * @file Constant values used by all parts of the library
89
89
  */
90
90
  // General
91
+ /**
92
+ * Empty string.
93
+ */
91
94
  const EMPTY = '';
92
95
  const SPACE = ' ';
93
96
  const TAB = '\t';
@@ -6144,7 +6147,7 @@ var data$N = { adg_os_any:{ name:"csp",
6144
6147
  assignable:true,
6145
6148
  negatable:false,
6146
6149
  value_optional:true,
6147
- value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
6150
+ value_format:"csp_value" },
6148
6151
  adg_ext_any:{ name:"csp",
6149
6152
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
6150
6153
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
@@ -6156,7 +6159,7 @@ var data$N = { adg_os_any:{ name:"csp",
6156
6159
  assignable:true,
6157
6160
  negatable:false,
6158
6161
  value_optional:true,
6159
- value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
6162
+ value_format:"csp_value" },
6160
6163
  abp_ext_any:{ name:"csp",
6161
6164
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
6162
6165
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#content-security-policies",
@@ -6166,7 +6169,7 @@ var data$N = { adg_os_any:{ name:"csp",
6166
6169
  assignable:true,
6167
6170
  negatable:false,
6168
6171
  value_optional:true,
6169
- value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" },
6172
+ value_format:"csp_value" },
6170
6173
  ubo_ext_any:{ name:"csp",
6171
6174
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
6172
6175
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#csp",
@@ -6178,7 +6181,7 @@ var data$N = { adg_os_any:{ name:"csp",
6178
6181
  assignable:true,
6179
6182
  negatable:false,
6180
6183
  value_optional:true,
6181
- value_format:"(?xi)\n ^(\n base-uri|\n child-src|\n connect-src|\n default-src|\n font-src|\n form-action|\n frame-ancestors|\n frame-src|\n img-src|\n manifest-src|\n media-src|\n navigate-to|\n object-src|\n plugin-types|\n prefetch-src|\n report-to|\n report-uri|\n sandbox|\n script-src|\n style-src|\n upgrade-insecure-requests|\n worker-src|\n )\n \\s+\n \\S{1,}" } };
6184
+ value_format:"csp_value" } };
6182
6185
 
6183
6186
  var data$M = { adg_os_any:{ name:"denyallow",
6184
6187
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
@@ -6685,7 +6688,8 @@ var data$l = { adg_os_any:{ name:"permissions",
6685
6688
  inverse_conflicts:true,
6686
6689
  assignable:true,
6687
6690
  negatable:false,
6688
- value_format:"(?x)\n ^\n (\n ?:(\n accelerometer|\n ambient-light-sensor|\n autoplay|\n battery|\n camera|\n display-capture|\n document-domain|\n encrypted-media|\n execution-while-not-rendered|\n execution-while-out-of-viewport|\n fullscreen|\n gamepad|\n geolocation|\n gyroscope|\n hid|\n identity-credentials-get|\n idle-detection|\n local-fonts|\n magnetometer|\n microphone|\n midi|\n payment|\n picture-in-picture|\n publickey-credentials-create|\n publickey-credentials-get|\n screen-wake-lock|\n serial|\n speaker-selection|\n storage-access|\n usb|\n web-share|\n xr-spatial-tracking\n )\n =\\(\\)\n # optional escaped comma for multiple permissions\n (\\\\,(\\s+)?)?\n )+\n $" } };
6691
+ value_optional:true,
6692
+ value_format:"permissions_value" } };
6689
6693
 
6690
6694
  var data$k = { adg_any:{ name:"ping",
6691
6695
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
@@ -6813,14 +6817,14 @@ var data$g = { adg_os_any:{ name:"redirect",
6813
6817
  assignable:true,
6814
6818
  negatable:false,
6815
6819
  value_optional:true,
6816
- value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6820
+ value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n googletagmanager-gtm|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6817
6821
  adg_ext_any:{ name:"redirect",
6818
6822
  description:"Used to redirect web requests to a local \"resource\".",
6819
6823
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6820
6824
  assignable:true,
6821
6825
  negatable:false,
6822
6826
  value_optional:true,
6823
- value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6827
+ value_format:"(?x)\n ^(\n 1x1-transparent\\.gif|\n 2x2-transparent\\.png|\n 3x2-transparent\\.png|\n 32x32-transparent\\.png|\n noopframe|\n noopcss|\n noopjs|\n noopjson|\n nooptext|\n empty|\n noopvmap-1\\.0|\n noopvast-2\\.0|\n noopvast-3\\.0|\n noopvast-4\\.0|\n noopmp3-0\\.1s|\n noopmp4-1s|\n amazon-apstag|\n ati-smarttag|\n didomi-loader|\n fingerprintjs2|\n fingerprintjs3|\n gemius|\n google-analytics-ga|\n google-analytics|\n googletagmanager-gtm|\n google-ima3|\n googlesyndication-adsbygoogle|\n googletagservices-gpt|\n matomo|\n metrika-yandex-tag|\n metrika-yandex-watch|\n naver-wcslog|\n noeval|\n pardot-1\\.0|\n prebid-ads|\n prebid|\n prevent-bab|\n prevent-bab2|\n prevent-fab-3\\.2\\.0|\n prevent-popads-net|\n scorecardresearch-beacon|\n set-popads-dummy|\n click2load\\.html\n )?$" },
6824
6828
  ubo_ext_any:{ name:"redirect",
6825
6829
  description:"Used to redirect web requests to a local \"resource\".",
6826
6830
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect",
@@ -6929,7 +6933,7 @@ var data$d = { adg_os_any:{ name:"replace",
6929
6933
  inverse_conflicts:true,
6930
6934
  assignable:true,
6931
6935
  negatable:false,
6932
- value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+?)\n \\/\n # flags\n ([gimuy]*)?\n $" },
6936
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" },
6933
6937
  adg_ext_firefox:{ name:"replace",
6934
6938
  description:"This modifier completely changes the rule behavior.\nIf it is applied, the rule will not block the request. The response is going to be modified instead.",
6935
6939
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#replace-modifier",
@@ -6947,7 +6951,7 @@ var data$d = { adg_os_any:{ name:"replace",
6947
6951
  inverse_conflicts:true,
6948
6952
  assignable:true,
6949
6953
  negatable:false,
6950
- value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+?)\n \\/\n # flags\n ([gimuy]*)?\n $" } };
6954
+ value_format:"(?xi)\n ^\n \\/\n # the regexp to match by\n (.+)\n # separator\n \\/\n # replacement\n (.+)?\n \\/\n # flags\n ([gimuy]*)?\n $" } };
6951
6955
 
6952
6956
  var data$c = { adg_any:{ name:"script",
6953
6957
  description:"The rule corresponds to script requests, e.g. javascript, vbscript.",
@@ -7346,15 +7350,104 @@ const ALLOWED_STEALTH_OPTIONS = new Set([
7346
7350
  'xclientdata',
7347
7351
  'dpi',
7348
7352
  ]);
7353
+ /**
7354
+ * Allowed CSP directives for $csp modifier.
7355
+ *
7356
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives}
7357
+ */
7358
+ const ALLOWED_CSP_DIRECTIVES = new Set([
7359
+ 'base-uri',
7360
+ 'child-src',
7361
+ 'connect-src',
7362
+ 'default-src',
7363
+ 'font-src',
7364
+ 'form-action',
7365
+ 'frame-ancestors',
7366
+ 'frame-src',
7367
+ 'img-src',
7368
+ 'manifest-src',
7369
+ 'media-src',
7370
+ 'navigate-to',
7371
+ 'object-src',
7372
+ 'plugin-types',
7373
+ 'prefetch-src',
7374
+ 'report-to',
7375
+ 'report-uri',
7376
+ 'sandbox',
7377
+ 'script-src',
7378
+ 'style-src',
7379
+ 'upgrade-insecure-requests',
7380
+ 'worker-src',
7381
+ ]);
7382
+ /**
7383
+ * Allowed stealth options for $permissions modifier.
7384
+ *
7385
+ * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier}
7386
+ */
7387
+ const ALLOWED_PERMISSION_DIRECTIVES = new Set([
7388
+ 'accelerometer',
7389
+ 'ambient-light-sensor',
7390
+ 'autoplay',
7391
+ 'battery',
7392
+ 'camera',
7393
+ 'display-capture',
7394
+ 'document-domain',
7395
+ 'encrypted-media',
7396
+ 'execution-while-not-rendered',
7397
+ 'execution-while-out-of-viewport',
7398
+ 'fullscreen',
7399
+ 'gamepad',
7400
+ 'geolocation',
7401
+ 'gyroscope',
7402
+ 'hid',
7403
+ 'identity-credentials-get',
7404
+ 'idle-detection',
7405
+ 'local-fonts',
7406
+ 'magnetometer',
7407
+ 'microphone',
7408
+ 'midi',
7409
+ 'payment',
7410
+ 'picture-in-picture',
7411
+ 'publickey-credentials-create',
7412
+ 'publickey-credentials-get',
7413
+ 'screen-wake-lock',
7414
+ 'serial',
7415
+ 'speaker-selection',
7416
+ 'storage-access',
7417
+ 'usb',
7418
+ 'web-share',
7419
+ 'xr-spatial-tracking',
7420
+ ]);
7421
+ /**
7422
+ * One of available tokens for $permission modifier value.
7423
+ *
7424
+ * @see {@link https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization}
7425
+ */
7426
+ const PERMISSIONS_TOKEN_SELF = 'self';
7427
+ /**
7428
+ * One of allowlist values for $permissions modifier.
7429
+ *
7430
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy#allowlists}
7431
+ */
7432
+ const EMPTY_PERMISSIONS_ALLOWLIST = `${OPEN_PARENTHESIS}${CLOSE_PARENTHESIS}`;
7349
7433
  /**
7350
7434
  * Prefixes for error messages used in modifier validation.
7351
7435
  */
7352
7436
  const VALIDATION_ERROR_PREFIX = {
7353
7437
  BLOCK_ONLY: 'Only blocking rules may contain the modifier',
7354
7438
  EXCEPTION_ONLY: 'Only exception rules may contain the modifier',
7439
+ INVALID_CSP_DIRECTIVES: 'Invalid CSP directives for the modifier',
7355
7440
  INVALID_LIST_VALUES: 'Invalid values for the modifier',
7356
7441
  INVALID_NOOP: 'Invalid noop modifier',
7442
+ INVALID_PERMISSION_DIRECTIVE: 'Invalid Permissions-Policy directive for the modifier',
7443
+ INVALID_PERMISSION_ORIGINS: 'Origins in the value is invalid for the modifier and the directive',
7444
+ INVALID_PERMISSION_ORIGIN_QUOTES: 'Double quotes should be used for origins in the value of the modifier',
7357
7445
  MIXED_NEGATIONS: 'Simultaneous usage of negated and not negated values is forbidden for the modifier',
7446
+ NO_CSP_VALUE: 'No CSP value for the modifier and the directive',
7447
+ NO_CSP_DIRECTIVE_QUOTE: 'CSP directives should no be quoted for the modifier',
7448
+ NO_UNESCAPED_PERMISSION_COMMA: 'Unescaped comma in the value is not allowed for the modifier',
7449
+ // TODO: implement later for $scp and $permissions
7450
+ // NO_VALUE_ONLY_FOR_EXCEPTION: 'Modifier without value can be used only in exception rules',
7358
7451
  NOT_EXISTENT: 'Non-existent modifier',
7359
7452
  NOT_NEGATABLE_MODIFIER: 'Non-negatable modifier',
7360
7453
  NOT_NEGATABLE_VALUE: 'Values cannot be negated for the modifier',
@@ -7666,10 +7759,12 @@ class QuoteUtils {
7666
7759
  var CustomValueFormatValidatorName;
7667
7760
  (function (CustomValueFormatValidatorName) {
7668
7761
  CustomValueFormatValidatorName["App"] = "pipe_separated_apps";
7762
+ CustomValueFormatValidatorName["Csp"] = "csp_value";
7669
7763
  // there are some differences between $domain and $denyallow
7670
7764
  CustomValueFormatValidatorName["DenyAllow"] = "pipe_separated_denyallow_domains";
7671
7765
  CustomValueFormatValidatorName["Domain"] = "pipe_separated_domains";
7672
7766
  CustomValueFormatValidatorName["Method"] = "pipe_separated_methods";
7767
+ CustomValueFormatValidatorName["Permissions"] = "permissions_value";
7673
7768
  CustomValueFormatValidatorName["StealthOption"] = "pipe_separated_stealth_options";
7674
7769
  })(CustomValueFormatValidatorName || (CustomValueFormatValidatorName = {}));
7675
7770
  /**
@@ -7730,6 +7825,32 @@ const isValidMethodModifierValue = (value) => {
7730
7825
  const isValidStealthModifierValue = (value) => {
7731
7826
  return ALLOWED_STEALTH_OPTIONS.has(value);
7732
7827
  };
7828
+ /**
7829
+ * Checks whether the given `rawOrigin` is valid as Permissions Allowlist origin.
7830
+ *
7831
+ * @see {@link https://w3c.github.io/webappsec-permissions-policy/#allowlists}
7832
+ *
7833
+ * @param rawOrigin The raw origin.
7834
+ *
7835
+ * @returns True if the origin is valid, false otherwise.
7836
+ */
7837
+ const isValidPermissionsOrigin = (rawOrigin) => {
7838
+ // origins should be quoted by double quote
7839
+ const actualQuoteType = QuoteUtils.getStringQuoteType(rawOrigin);
7840
+ if (actualQuoteType !== exports.QuoteType.Double) {
7841
+ return false;
7842
+ }
7843
+ const origin = QuoteUtils.removeQuotes(rawOrigin);
7844
+ try {
7845
+ // validate the origin by URL constructor
7846
+ // https://w3c.github.io/webappsec-permissions-policy/#algo-parse-policy-directive
7847
+ new URL(origin);
7848
+ }
7849
+ catch (e) {
7850
+ return false;
7851
+ }
7852
+ return true;
7853
+ };
7733
7854
  /**
7734
7855
  * Checks whether the given `value` is valid domain as $denyallow modifier value.
7735
7856
  * Important: wildcard tld are not supported, compared to $domain.
@@ -7920,14 +8041,196 @@ const validatePipeSeparatedMethods = (modifier) => {
7920
8041
  const validatePipeSeparatedStealthOptions = (modifier) => {
7921
8042
  return validateListItemsModifier(modifier, (raw) => StealthOptionListParser.parse(raw), isValidStealthModifierValue, customNoNegatedListItemsValidator);
7922
8043
  };
8044
+ /**
8045
+ * Validates `csp_value` custom value format.
8046
+ * Used for $csp modifier.
8047
+ *
8048
+ * @param modifier Modifier AST node.
8049
+ *
8050
+ * @returns Validation result.
8051
+ */
8052
+ const validateCspValue = (modifier) => {
8053
+ const modifierName = modifier.modifier.value;
8054
+ if (!modifier.value?.value) {
8055
+ return getValueRequiredValidationResult(modifierName);
8056
+ }
8057
+ // $csp modifier value may contain multiple directives
8058
+ // e.g. "csp=child-src 'none'; frame-src 'self' *; worker-src 'none'"
8059
+ const policyDirectives = modifier.value.value
8060
+ .split(SEMICOLON)
8061
+ // rule with $csp modifier may end with semicolon
8062
+ // e.g. "$csp=sandbox allow-same-origin;"
8063
+ // TODO: add predicate helper for `(i) => !!i`
8064
+ .filter((i) => !!i);
8065
+ const invalidValueValidationResult = getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}': "${modifier.value.value}"`);
8066
+ if (policyDirectives.length === 0) {
8067
+ return invalidValueValidationResult;
8068
+ }
8069
+ const invalidDirectives = [];
8070
+ for (let i = 0; i < policyDirectives.length; i += 1) {
8071
+ const policyDirective = policyDirectives[i].trim();
8072
+ if (!policyDirective) {
8073
+ return invalidValueValidationResult;
8074
+ }
8075
+ const chunks = policyDirective.split(SPACE);
8076
+ const [directive, ...valueChunks] = chunks;
8077
+ // e.g. "csp=child-src 'none'; ; worker-src 'none'"
8078
+ // validator it here ↑
8079
+ if (!directive) {
8080
+ return invalidValueValidationResult;
8081
+ }
8082
+ if (!ALLOWED_CSP_DIRECTIVES.has(directive)) {
8083
+ // e.g. "csp='child-src' 'none'"
8084
+ if (ALLOWED_CSP_DIRECTIVES.has(QuoteUtils.removeQuotes(directive))) {
8085
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NO_CSP_DIRECTIVE_QUOTE}: '${modifierName}': ${directive}`);
8086
+ }
8087
+ invalidDirectives.push(directive);
8088
+ continue;
8089
+ }
8090
+ if (valueChunks.length === 0) {
8091
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NO_CSP_VALUE}: '${modifierName}': '${directive}'`);
8092
+ }
8093
+ }
8094
+ if (invalidDirectives.length > 0) {
8095
+ const directivesToStr = QuoteUtils.quoteAndJoinStrings(invalidDirectives, exports.QuoteType.Double);
8096
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_CSP_DIRECTIVES}: '${modifierName}': ${directivesToStr}`);
8097
+ }
8098
+ return { valid: true };
8099
+ };
8100
+ /**
8101
+ * Validates permission allowlist origins in the value of $permissions modifier.
8102
+ *
8103
+ * @see {@link https://w3c.github.io/webappsec-permissions-policy/#allowlists}
8104
+ *
8105
+ * @param allowlistChunks Array of allowlist chunks.
8106
+ * @param directive Permission directive name.
8107
+ * @param modifierName Modifier name.
8108
+ *
8109
+ * @returns Validation result.
8110
+ */
8111
+ const validatePermissionAllowlistOrigins = (allowlistChunks, directive, modifierName) => {
8112
+ const invalidOrigins = [];
8113
+ for (let i = 0; i < allowlistChunks.length; i += 1) {
8114
+ const chunk = allowlistChunks[i].trim();
8115
+ // skip few spaces between origins (they were splitted by space)
8116
+ // e.g. 'geolocation=("https://example.com" "https://*.example.com")'
8117
+ if (chunk.length === 0) {
8118
+ continue;
8119
+ }
8120
+ /**
8121
+ * 'self' should be checked case-insensitively
8122
+ *
8123
+ * @see {@link https://w3c.github.io/webappsec-permissions-policy/#algo-parse-policy-directive}
8124
+ *
8125
+ * @example 'geolocation=(self)'
8126
+ */
8127
+ if (chunk.toLowerCase() === PERMISSIONS_TOKEN_SELF) {
8128
+ continue;
8129
+ }
8130
+ if (QuoteUtils.getStringQuoteType(chunk) !== exports.QuoteType.Double) {
8131
+ return getInvalidValidationResult(
8132
+ // eslint-disable-next-line max-len
8133
+ `${VALIDATION_ERROR_PREFIX.INVALID_PERMISSION_ORIGIN_QUOTES}: '${modifierName}': '${directive}': '${QuoteUtils.removeQuotes(chunk)}'`);
8134
+ }
8135
+ if (!isValidPermissionsOrigin(chunk)) {
8136
+ invalidOrigins.push(chunk);
8137
+ }
8138
+ }
8139
+ if (invalidOrigins.length > 0) {
8140
+ const originsToStr = QuoteUtils.quoteAndJoinStrings(invalidOrigins);
8141
+ return getInvalidValidationResult(
8142
+ // eslint-disable-next-line max-len
8143
+ `${VALIDATION_ERROR_PREFIX.INVALID_PERMISSION_ORIGINS}: '${modifierName}': '${directive}': ${originsToStr}`);
8144
+ }
8145
+ return { valid: true };
8146
+ };
8147
+ /**
8148
+ * Validates permission allowlist in the modifier value.
8149
+ *
8150
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy#allowlists}
8151
+ * @see {@link https://w3c.github.io/webappsec-permissions-policy/#allowlists}
8152
+ *
8153
+ * @param allowlist Allowlist value.
8154
+ * @param directive Permission directive name.
8155
+ * @param modifierName Modifier name.
8156
+ *
8157
+ * @returns Validation result.
8158
+ */
8159
+ const validatePermissionAllowlist = (allowlist, directive, modifierName) => {
8160
+ // `*` is one of available permissions tokens
8161
+ // e.g. 'fullscreen=*'
8162
+ // https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization
8163
+ if (allowlist === WILDCARD$1
8164
+ // e.g. 'autoplay=()'
8165
+ || allowlist === EMPTY_PERMISSIONS_ALLOWLIST) {
8166
+ return { valid: true };
8167
+ }
8168
+ if (!(allowlist.startsWith(OPEN_PARENTHESIS) && allowlist.endsWith(CLOSE_PARENTHESIS))) {
8169
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}'`);
8170
+ }
8171
+ const allowlistChunks = allowlist.slice(1, -1).split(SPACE);
8172
+ return validatePermissionAllowlistOrigins(allowlistChunks, directive, modifierName);
8173
+ };
8174
+ /**
8175
+ * Validates single permission in the modifier value.
8176
+ *
8177
+ * @param permission Single permission value.
8178
+ * @param modifierName Modifier name.
8179
+ * @param modifierValue Modifier value.
8180
+ *
8181
+ * @returns Validation result.
8182
+ */
8183
+ const validateSinglePermission = (permission, modifierName, modifierValue) => {
8184
+ // empty permission in the rule
8185
+ // e.g. 'permissions=storage-access=()\\, \\, camera=()'
8186
+ // the validator is here ↑
8187
+ if (!permission) {
8188
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}'`);
8189
+ }
8190
+ if (permission.includes(COMMA)) {
8191
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.NO_UNESCAPED_PERMISSION_COMMA}: '${modifierName}': '${modifierValue}'`);
8192
+ }
8193
+ const [directive, allowlist] = permission.split(EQUALS);
8194
+ if (!ALLOWED_PERMISSION_DIRECTIVES.has(directive)) {
8195
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_PERMISSION_DIRECTIVE}: '${modifierName}': '${directive}'`);
8196
+ }
8197
+ return validatePermissionAllowlist(allowlist, directive, modifierName);
8198
+ };
8199
+ /**
8200
+ * Validates `permissions_value` custom value format.
8201
+ * Used for $permissions modifier.
8202
+ *
8203
+ * @param modifier Modifier AST node.
8204
+ *
8205
+ * @returns Validation result.
8206
+ */
8207
+ const validatePermissions = (modifier) => {
8208
+ if (!modifier.value?.value) {
8209
+ return getValueRequiredValidationResult(modifier.modifier.value);
8210
+ }
8211
+ const modifierName = modifier.modifier.value;
8212
+ const modifierValue = modifier.value.value;
8213
+ // multiple permissions may be separated by escaped commas
8214
+ const permissions = modifier.value.value.split(`${BACKSLASH}${COMMA}`);
8215
+ for (let i = 0; i < permissions.length; i += 1) {
8216
+ const permission = permissions[i].trim();
8217
+ const singlePermissionValidationResult = validateSinglePermission(permission, modifierName, modifierValue);
8218
+ if (!singlePermissionValidationResult.valid) {
8219
+ return singlePermissionValidationResult;
8220
+ }
8221
+ }
8222
+ return { valid: true };
8223
+ };
7923
8224
  /**
7924
8225
  * Map of all available pre-defined validators for modifiers with custom `value_format`.
7925
8226
  */
7926
8227
  const CUSTOM_VALUE_FORMAT_MAP = {
7927
8228
  [CustomValueFormatValidatorName.App]: validatePipeSeparatedApps,
8229
+ [CustomValueFormatValidatorName.Csp]: validateCspValue,
7928
8230
  [CustomValueFormatValidatorName.DenyAllow]: validatePipeSeparatedDenyAllowDomains,
7929
8231
  [CustomValueFormatValidatorName.Domain]: validatePipeSeparatedDomains,
7930
8232
  [CustomValueFormatValidatorName.Method]: validatePipeSeparatedMethods,
8233
+ [CustomValueFormatValidatorName.Permissions]: validatePermissions,
7931
8234
  [CustomValueFormatValidatorName.StealthOption]: validatePipeSeparatedStealthOptions,
7932
8235
  };
7933
8236
  /**
@@ -7954,9 +8257,8 @@ const validateValue = (modifier, valueFormat) => {
7954
8257
  return validator(modifier);
7955
8258
  }
7956
8259
  const modifierName = modifier.modifier.value;
7957
- const defaultInvalidValueResult = getValueRequiredValidationResult(modifierName);
7958
8260
  if (!modifier.value?.value) {
7959
- return defaultInvalidValueResult;
8261
+ return getValueRequiredValidationResult(modifierName);
7960
8262
  }
7961
8263
  let xRegExp;
7962
8264
  try {
@@ -7967,7 +8269,7 @@ const validateValue = (modifier, valueFormat) => {
7967
8269
  }
7968
8270
  const isValid = xRegExp.test(modifier.value?.value);
7969
8271
  if (!isValid) {
7970
- return defaultInvalidValueResult;
8272
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.VALUE_INVALID}: '${modifierName}'`);
7971
8273
  }
7972
8274
  return { valid: true };
7973
8275
  };
@@ -8031,6 +8333,10 @@ const validateForSpecificSyntax = (modifiersData, syntax, modifier, isException)
8031
8333
  // e.g. 'domain'
8032
8334
  if (specificBlockerData[SpecificKey.Assignable]) {
8033
8335
  if (!modifier.value) {
8336
+ // TODO: ditch value_optional after custom validators are implemented for value_format for all modifiers.
8337
+ // This checking should be done in each separate custom validator,
8338
+ // because $csp and $permissions without value can be used only in extension rules,
8339
+ // but $cookie with no value can be used in both blocking and exception rules.
8034
8340
  /**
8035
8341
  * Some assignable modifiers can be used without a value,
8036
8342
  * e.g. '@@||example.com^$cookie'.
@@ -9280,6 +9586,11 @@ class CosmeticRuleConverter extends RuleConverterBase {
9280
9586
  /**
9281
9587
  * @file Network rule modifier list converter.
9282
9588
  */
9589
+ // Since scriptlets library doesn't have ESM exports, we should import
9590
+ // the whole module and then extract the required functions from it here.
9591
+ // Otherwise importing AGTree will cause an error in ESM environment,
9592
+ // because scriptlets library doesn't support named exports.
9593
+ const { redirects } = scriptlets;
9283
9594
  /**
9284
9595
  * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#csp-modifier}
9285
9596
  */
@@ -9395,7 +9706,7 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9395
9706
  : modifierNode.modifier.value;
9396
9707
  // Try to convert the redirect resource name to ADG format
9397
9708
  // This function returns undefined if the resource name is unknown
9398
- const convertedRedirectResource = scriptlets.redirects.convertRedirectNameToAdg(redirectResource);
9709
+ const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
9399
9710
  convertedModifierList.children.push(createModifierNode(modifierName,
9400
9711
  // If the redirect resource name is unknown, fall back to the original one
9401
9712
  // Later, the validator will throw an error if the resource name is invalid
@@ -9616,7 +9927,7 @@ class LogicalExpressionUtils {
9616
9927
  }
9617
9928
  }
9618
9929
 
9619
- const version$1 = "1.1.3";
9930
+ const version$1 = "1.1.5";
9620
9931
 
9621
9932
  /**
9622
9933
  * @file AGTree version
package/dist/agtree.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v1.1.3 (build date: Mon, 28 Aug 2023 16:19:09 GMT)
2
+ * AGTree v1.1.5 (build date: Tue, 05 Sep 2023 15:10:53 GMT)
3
3
  * (c) 2023 AdGuard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme