@blamejs/blamejs-shop 0.5.19 → 0.5.21

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/SECURITY.md +17 -0
  4. package/lib/addresses.js +2 -2
  5. package/lib/admin.js +49 -45
  6. package/lib/affiliates.js +7 -23
  7. package/lib/announcement-bar.js +5 -22
  8. package/lib/api-keys.js +4 -13
  9. package/lib/asset-manifest.json +1 -1
  10. package/lib/banner-ab-tests.js +5 -9
  11. package/lib/blog-articles.js +8 -26
  12. package/lib/captcha-gate.js +7 -17
  13. package/lib/carrier-accounts.js +3 -10
  14. package/lib/catalog-drafts.js +5 -12
  15. package/lib/category-navigation.js +4 -20
  16. package/lib/cms-blocks.js +4 -7
  17. package/lib/compliance-export.js +4 -11
  18. package/lib/consent-ledger.js +2 -4
  19. package/lib/cost-layers.js +56 -21
  20. package/lib/customer-import.js +2 -2
  21. package/lib/customer-notes.js +7 -23
  22. package/lib/customer-roles.js +3 -5
  23. package/lib/customer-segments.js +17 -14
  24. package/lib/customer-surveys.js +7 -14
  25. package/lib/customers.js +2 -4
  26. package/lib/dispute-resolution.js +5 -13
  27. package/lib/dropship-forwarding.js +3 -5
  28. package/lib/dunning.js +2 -7
  29. package/lib/email-ab-tests.js +4 -8
  30. package/lib/email-campaigns.js +25 -14
  31. package/lib/email-warmup.js +2 -7
  32. package/lib/experiments.js +6 -10
  33. package/lib/externaldb-d1.js +78 -44
  34. package/lib/geolocation.js +18 -28
  35. package/lib/gift-options.js +5 -23
  36. package/lib/knowledge-base.js +7 -22
  37. package/lib/line-gift-wrap.js +3 -17
  38. package/lib/live-chat.js +5 -18
  39. package/lib/metered-usage.js +3 -8
  40. package/lib/notifications.js +3 -7
  41. package/lib/operator-accounts.js +3 -5
  42. package/lib/operator-approvals.js +6 -8
  43. package/lib/operator-audit-log.js +2 -7
  44. package/lib/operator-help-center.js +5 -21
  45. package/lib/operator-inbox.js +4 -11
  46. package/lib/operator-roles.js +5 -7
  47. package/lib/order-escalation.js +2 -2
  48. package/lib/order-export.js +24 -20
  49. package/lib/order-notes.js +4 -14
  50. package/lib/order-ratings.js +4 -10
  51. package/lib/payment-methods.js +5 -13
  52. package/lib/payment-retries.js +2 -7
  53. package/lib/payment.js +103 -58
  54. package/lib/pixel-events.js +8 -23
  55. package/lib/plan-changes.js +2 -7
  56. package/lib/product-qa.js +4 -8
  57. package/lib/promo-banners.js +7 -15
  58. package/lib/purchase-orders.js +3 -6
  59. package/lib/push-notifications.js +7 -32
  60. package/lib/pwa-manifest.js +6 -11
  61. package/lib/quotes.js +5 -9
  62. package/lib/referrals.js +10 -6
  63. package/lib/reviews.js +4 -10
  64. package/lib/robots-config.js +6 -14
  65. package/lib/sales-tax-filings.js +2 -2
  66. package/lib/search-facets.js +5 -5
  67. package/lib/search-ranking.js +4 -7
  68. package/lib/search-synonyms.js +7 -21
  69. package/lib/security-middleware.js +125 -70
  70. package/lib/seller-signup.js +10 -26
  71. package/lib/sidebar-widgets.js +6 -15
  72. package/lib/site-redirects.js +49 -137
  73. package/lib/sitemap-generator.js +3 -7
  74. package/lib/sms-dispatcher.js +7 -23
  75. package/lib/stock-receipts.js +4 -10
  76. package/lib/storefront-dashboards.js +2 -10
  77. package/lib/storefront-forms.js +9 -22
  78. package/lib/storefront-pages.js +6 -20
  79. package/lib/storefront.js +21 -23
  80. package/lib/subscription-billing.js +3 -8
  81. package/lib/subscription-controls.js +2 -7
  82. package/lib/suggestion-box.js +9 -26
  83. package/lib/support-tickets.js +6 -24
  84. package/lib/tax-remittance.js +2 -2
  85. package/lib/tenants.js +2 -7
  86. package/lib/text-guard.js +137 -35
  87. package/lib/theme-assets.js +4 -16
  88. package/lib/translations.js +3 -6
  89. package/lib/trust-badges.js +4 -7
  90. package/lib/vendor/MANIFEST.json +107 -97
  91. package/lib/vendor/blamejs/CHANGELOG.md +180 -0
  92. package/lib/vendor/blamejs/NOTICE +14 -10
  93. package/lib/vendor/blamejs/README.md +17 -5
  94. package/lib/vendor/blamejs/index.js +2 -0
  95. package/lib/vendor/blamejs/lib/acme.js +9 -9
  96. package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
  97. package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
  98. package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
  99. package/lib/vendor/blamejs/lib/archive.js +13 -24
  100. package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
  101. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
  102. package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
  103. package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
  104. package/lib/vendor/blamejs/lib/auth/password.js +1 -1
  105. package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
  106. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
  107. package/lib/vendor/blamejs/lib/backup/index.js +5 -5
  108. package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
  109. package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
  110. package/lib/vendor/blamejs/lib/constants.js +79 -0
  111. package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
  112. package/lib/vendor/blamejs/lib/error-page.js +20 -1
  113. package/lib/vendor/blamejs/lib/external-db.js +5 -1
  114. package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
  115. package/lib/vendor/blamejs/lib/flag.js +28 -2
  116. package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
  117. package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
  118. package/lib/vendor/blamejs/lib/http-client.js +217 -40
  119. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  120. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
  121. package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
  122. package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
  123. package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
  124. package/lib/vendor/blamejs/lib/mail-store.js +7 -1
  125. package/lib/vendor/blamejs/lib/mail.js +5 -1
  126. package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
  127. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
  128. package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
  129. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
  130. package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
  131. package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
  132. package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
  133. package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
  134. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
  135. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
  136. package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
  137. package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
  138. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
  139. package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
  140. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
  141. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
  142. package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
  143. package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
  144. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
  145. package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
  146. package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
  147. package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
  148. package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
  149. package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
  150. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  151. package/lib/vendor/blamejs/lib/network-dns.js +3 -3
  152. package/lib/vendor/blamejs/lib/network-nts.js +35 -14
  153. package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
  154. package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
  155. package/lib/vendor/blamejs/lib/network-tls.js +341 -11
  156. package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
  157. package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
  158. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
  160. package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
  161. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
  162. package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
  163. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
  164. package/lib/vendor/blamejs/lib/openapi.js +1 -1
  165. package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
  166. package/lib/vendor/blamejs/lib/problem-details.js +2 -1
  167. package/lib/vendor/blamejs/lib/redis-client.js +12 -1
  168. package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
  169. package/lib/vendor/blamejs/lib/render.js +531 -7
  170. package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
  171. package/lib/vendor/blamejs/lib/router.js +37 -13
  172. package/lib/vendor/blamejs/lib/safe-async.js +142 -0
  173. package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
  174. package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
  175. package/lib/vendor/blamejs/lib/self-update.js +2 -2
  176. package/lib/vendor/blamejs/lib/testing.js +54 -7
  177. package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
  178. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
  179. package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
  180. package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
  181. package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
  182. package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
  183. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
  184. package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
  185. package/lib/vendor/blamejs/lib/webhook.js +1 -1
  186. package/lib/vendor/blamejs/lib/ws-client.js +24 -2
  187. package/lib/vendor/blamejs/package.json +1 -1
  188. package/lib/vendor/blamejs/sbom.cdx.json +6 -6
  189. package/lib/vendor-invoices.js +3 -6
  190. package/lib/vendors.js +5 -19
  191. package/lib/webhook-receiver.js +46 -83
  192. package/lib/webhook-subscriptions.js +3 -13
  193. package/lib/wishlist-sharing.js +5 -10
  194. package/package.json +1 -1
@@ -21,8 +21,11 @@
21
21
  * Threat catalog: nested quantifiers (`(a+)+`, `(a*)+`, `(.+)+` —
22
22
  * the canonical ReDoS class, e.g. CVE-2024-21538 cross-spawn and
23
23
  * CVE-2022-25929 chartjs-adapter-luxon); alternation-with-
24
- * quantifier (`(a|b)+`, `(\d|\d{2})*`) where alternation overlap
25
- * amplifies search paths; quantifier-inside-lookaround
24
+ * quantifier (`(a|a)*`, `(\d|\d{2})*`) where two branches can match
25
+ * at the same position and the overlap amplifies search paths — an
26
+ * alternation whose branches cannot start on the same character is
27
+ * the character class it is written out long-hand as, and passes;
28
+ * quantifier-inside-lookaround
26
29
  * (`(?=.*+)`, `(?!a*)`) — catastrophic in some engines; bounded
27
30
  * repetition with a large upper bound (gated by
28
31
  * `maxBoundedRepeat`); per-pattern byte cap to defend against
@@ -41,7 +44,9 @@
41
44
  * gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
42
45
  * action). Detector regexes themselves are length-bounded by
43
46
  * `maxPatternBytes` so the screener can't be DoS'd by its own
44
- * inputs.
47
+ * inputs, and the unambiguity analysis spends a fixed work budget
48
+ * — a pattern too expensive to reason about exhausts it and stays
49
+ * refused, so cost cannot buy leniency.
45
50
  *
46
51
  * @card
47
52
  * Regex-pattern content-safety guard — refuses user-supplied pattern strings that exhibit catastrophic-backtracking (ReDoS) shapes BEFORE the framework compiles them with `new RegExp(...)`.
@@ -49,6 +54,8 @@
49
54
 
50
55
  var lazyRequire = require("./lazy-require");
51
56
  var gateContract = require("./gate-contract");
57
+ var boundedMap = require("./bounded-map");
58
+ var codepointClass = require("./codepoint-class");
52
59
  var C = require("./constants");
53
60
  var { GuardRegexError } = require("./framework-error");
54
61
 
@@ -65,22 +72,26 @@ var _err = GuardRegexError.factory;
65
72
  // and does not miscount a `(?:` group prefix as an inner quantifier, so it does
66
73
  // not false-positive on linear shapes like `(?:X+)?` / `(X+)?` / `(?:bar)*`.)
67
74
 
68
- // Alternation-with-quantifier — `(a|b|...)+`, `(a|b)*`.
69
- var ALTERNATION_QUANT_RE = /\([^()]*\|[^()]*\)\s*[*+]/;
70
-
71
- // Bounded repetition captures the upper bound when present.
72
- var BOUNDED_REPEAT_RE = /\{(\d+)(?:,(\d*))?\}/g;
73
-
74
- // Lookaround with internal quantifier `(?=.*+)`, `(?!a*)`.
75
- var LOOKAROUND_QUANT_RE = /\(\?[=!<][^()]*[*+]/;
75
+ // Alternation-with-quantifier — `(a|b|...)+`, `(a|b)*`. A shape check only:
76
+ // it says nothing about whether the branches can actually overlap, so a hit
77
+ // is passed to _alternationBranchesProvablyDisjoint before it becomes a
78
+ // finding. It walks groups the same way that analysis does, because a flat
79
+ // regex here disagreed with it twice over: `[^()]*` cannot span a nested
80
+ // group, so one paren around a branch — `((a)|a)+` — hid an exponential
81
+ // pattern the same shape refused as `(a|a)+`; and matching only `*`/`+` after
82
+ // the group missed `{n,}`, which is the identical unbounded repetition spelled
83
+ // differently. Both were accepted at every profile.
76
84
 
77
85
  // Nested extglob detector — picomatch `*(...)` / `+(...)` / `?(...)` /
78
86
  // `@(...)` / `!(...)` containing another extglob inside (CVE-2026-33671
79
87
  // nested-extglob catastrophic-backtracking class). Two extglob heads in
80
88
  // the same pattern with no closing paren between them indicates nesting.
81
- // The consecutive-star detector (CVE-2026-26996) walks the input by
82
- // char so doesn't need a regex literal.
83
- var EXTGLOB_HEAD_RE = /[*+?@!]\(/g; // allow:regex-no-length-cap input bounded by maxPatternBytes
89
+ //
90
+ // The characters that open one. Everything in this module reads its input a
91
+ // character at a time: a screen for catastrophic patterns must not be built out
92
+ // of patterns, or it carries the failure it exists to refuse — this module had
93
+ // its own runaway scan (2,177 ms on 1 KiB) while it was.
94
+ var EXTGLOB_HEADS = "*+?@!";
84
95
 
85
96
  // ---- Profile presets ----
86
97
 
@@ -91,6 +102,7 @@ var PROFILES = Object.freeze({
91
102
  alternationQuantPolicy: "reject",
92
103
  boundedRepeatPolicy: "reject",
93
104
  lookaroundQuantPolicy: "reject",
105
+ unanchoredScanPolicy: "reject",
94
106
  consecutiveStarPolicy: "reject",
95
107
  nestedExtglobPolicy: "reject",
96
108
  inputKind: "regex", // CVE-2026-26996 + CVE-2026-33671 detectors apply only when inputKind=="glob"
@@ -106,6 +118,7 @@ var PROFILES = Object.freeze({
106
118
  alternationQuantPolicy: "audit",
107
119
  boundedRepeatPolicy: "audit",
108
120
  lookaroundQuantPolicy: "audit",
121
+ unanchoredScanPolicy: "audit",
109
122
  consecutiveStarPolicy: "reject", // CVE-2026-26996 refused at every profile
110
123
  nestedExtglobPolicy: "reject", // CVE-2026-33671 refused at every profile
111
124
  maxBoundedRepeat: 1000, // bounded repeat ceiling
@@ -120,6 +133,7 @@ var PROFILES = Object.freeze({
120
133
  alternationQuantPolicy: "allow",
121
134
  boundedRepeatPolicy: "audit",
122
135
  lookaroundQuantPolicy: "audit",
136
+ unanchoredScanPolicy: "allow",
123
137
  consecutiveStarPolicy: "reject", // CVE-2026-26996 refused at every profile
124
138
  nestedExtglobPolicy: "reject", // CVE-2026-33671 refused at every profile
125
139
  maxBoundedRepeat: 10000, // bounded repeat ceiling
@@ -134,61 +148,2243 @@ var DEFAULTS = gateContract.strictDefaults(PROFILES);
134
148
 
135
149
  var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
136
150
 
137
- // Structural nested-unbounded-quantifier detector. NESTED_QUANT_RE is paren-
138
- // blind (its `[^()]*` can't span a nested group), so it misses WRAPPED forms
139
- // like `((a)+)+` / `(([a-z]+)*)*` / `((a+))+` adding one extra group around
140
- // the inner quantifier bypasses the regex while the pattern stays catastrophic.
141
- // This linear scan tracks group nesting and flags an unbounded-quantified group
142
- // (`)+`, `)*`, `){n,}`) whose body itself contains an unbounded quantifier the
143
- // two-nested-unbounded-quantifier ReDoS classat any group depth. Bounded
144
- // repeats (`{n}`, `{n,m}`, `?`) are not unbounded, so they don't trip it (the
145
- // large-bound case is handled separately by maxBoundedRepeat).
146
- function _hasNestedQuantifier(src) {
147
- var stack = []; // open groups: each { quant } — body has an unbounded quantifier
148
- var inClass = false; // inside a [...] character class
149
- var i = 0;
150
- var n = src.length;
151
- var UNBOUNDED_AFTER_GROUP = /^(?:[*+]\??|\{\d*,\})/; // )+ )* )+? )*? ){n,}
152
- while (i < n) {
153
- var c = src.charAt(i);
154
- if (c === "\\") { i += 2; continue; } // escaped atom — skip both chars
155
- if (inClass) { if (c === "]") inClass = false; i += 1; continue; }
156
- if (c === "[") { inClass = true; i += 1; continue; }
157
- if (c === "(") { stack.push({ quant: false }); i += 1; continue; }
158
- if (c === ")") {
159
- var grp = stack.pop() || { quant: false };
160
- var qm = UNBOUNDED_AFTER_GROUP.exec(src.slice(i + 1)); // allow:regex-no-length-cap bounded slice of a maxPatternBytes-capped input
161
- var closeUnbounded = qm !== null;
162
- if (grp.quant && closeUnbounded) return true; // nested unbounded quantifier catastrophic
163
- // The closing group contributes an unbounded quantifier to its PARENT's
164
- // body if its own body had one, or if it is itself unbounded-quantified.
165
- if (stack.length && (grp.quant || closeUnbounded)) stack[stack.length - 1].quant = true;
166
- i += 1 + (qm ? qm[0].length : 0);
151
+ var MAX_CLASS_RANGE = 256;
152
+
153
+ // The unambiguity analysis spends a fixed work budget. A pattern too expensive
154
+ // to reason about exhausts it and stays refused, so cost cannot buy leniency.
155
+ // The budget is charged where the work happens building a character set
156
+ // charges its width, and a set is folded once when it is built rather than
157
+ // again at each reader so the units bought are proportional to the work
158
+ // done. Everyday patterns spend a few hundred.
159
+ var ANALYSIS_BUDGET = 20000;
160
+
161
+ function _ignoresCase(flags) {
162
+ return typeof flags === "string" && flags.indexOf("i") !== -1;
163
+ }
164
+
165
+ // The `v` flag changes what a character class means, so a class this parser
166
+ // read under the old rules cannot be trusted to say what the engine sees.
167
+ // Nothing is proven safe under it.
168
+ function _declinesOnFlags(flags) {
169
+ return typeof flags === "string" && flags.indexOf("v") !== -1;
170
+ }
171
+
172
+ // Does the class starting at `from` use the set syntax the `v` flag brings —
173
+ // a nested class, a difference, an intersection, or a string literal? Read one
174
+ // character at a time from the opening bracket, honouring escapes, and stopping
175
+ // at the `]` that closes the OUTERMOST class. Only asked under `v`, where these
176
+ // spellings mean something; without it a nested `[` is an ordinary member.
177
+ function _classUsesSetSyntax(text, from) {
178
+ var depth = 0;
179
+ for (var i = from; i < text.length; i += 1) {
180
+ var c = text.charAt(i);
181
+ if (c === "\\") {
182
+ if (text.charAt(i + 1) === "q" && text.charAt(i + 2) === "{") return true;
183
+ i += 1; // skip the escaped one
167
184
  continue;
168
185
  }
169
- if (c === "*" || c === "+") { // unbounded quantifier on the preceding atom
170
- if (stack.length) stack[stack.length - 1].quant = true;
171
- i += 1; continue;
186
+ if (c === "[") {
187
+ depth += 1;
188
+ if (depth > 1) return true; // a class inside a class
189
+ continue;
172
190
  }
173
- if (c === "{") {
174
- var open = /^\{\d*,\}/.exec(src.slice(i)); // allow:regex-no-length-cap — bounded slice // {n,} unbounded
175
- if (open) { if (stack.length) stack[stack.length - 1].quant = true; i += open[0].length; continue; }
176
- var bounded = /^\{\d+(?:,\d+)?\}/.exec(src.slice(i)); // allow:regex-no-length-cap — bounded slice // {n} / {n,m} bounded
177
- if (bounded) { i += bounded[0].length; continue; }
178
- i += 1; continue; // literal `{`
191
+ if (c === "]") {
192
+ depth -= 1;
193
+ if (depth <= 0) return false; // the outermost closed
194
+ continue;
179
195
  }
196
+ if (depth >= 1 && (c === "-" || c === "&") && text.charAt(i + 1) === c) return true;
197
+ }
198
+ return false; // unterminated — not our call
199
+ }
200
+
201
+ // ---- pattern parsing ------------------------------------------------------
202
+ //
203
+ // Every analysis below reads a parse tree. None of them reads the pattern
204
+ // source. Reading source with regexes is what this module used to do, and each
205
+ // reader drew the token boundaries a little differently: one could not see
206
+ // past a nested group, one decided whether a `?` was a quantifier by looking at
207
+ // the previous CHARACTER (so the `?` in `\*?` read as a lazy marker and the
208
+ // length variation it contributes was lost), one capped the digits inside
209
+ // `{n,m}` (so a longer bound read as no quantifier at all). Each disagreement
210
+ // was a way to write a catastrophic pattern that one reader found and another
211
+ // waved through, and patching them one at a time only moved the edge.
212
+ //
213
+ // So: one tokenizer, one tree, and anything it cannot represent becomes an
214
+ // OPAQUE node — which every analysis treats as "cannot prove", never as
215
+ // "nothing here". Failing closed is a property of the representation rather
216
+ // than something each reader has to remember.
217
+
218
+ // ---- character sets ----
219
+ //
220
+ // `any` is the set this analysis cannot characterise. It contains everything
221
+ // and intersects everything, so it can never be proven disjoint and can never
222
+ // prove a delimiter unreachable — the conservative answer in both directions.
223
+
224
+ function _anySet() { return { any: true, negated: false, chars: null }; }
225
+
226
+ function _mkSet(chars, negated) {
227
+ var set = chars instanceof Set ? chars : new Set(chars);
228
+ return { any: false, negated: !!negated, chars: set };
229
+ }
230
+
231
+ function _setSize(s) { return s.any ? 0 : s.chars.size; }
232
+
233
+ // Fold a set so it covers both cases, the way the engine does under `i`.
234
+ // Widening a positive set makes an intersection MORE likely; widening a
235
+ // complement's exclusions makes the complement narrower. Both push toward
236
+ // declining to prove, which is the safe direction. Sets are folded once, when
237
+ // they are built, so no two readers can disagree about whether folding
238
+ // happened — the bug that let `[^ab]` unioned with `A` exclude the very `a`
239
+ // the `A` was contributing.
240
+ function _foldSet(s) {
241
+ if (s.any) return s;
242
+ var out = new Set();
243
+ s.chars.forEach(function (c) {
244
+ out.add(c);
245
+ _addIfOneCharacter(out, c.toLowerCase());
246
+ _addIfOneCharacter(out, c.toUpperCase());
247
+ });
248
+ return _mkSet(out, s.negated);
249
+ }
250
+
251
+ // A case partner counts when it is ONE character. That is a single UTF-16
252
+ // unit, or two that form a surrogate pair — an astral letter's partner is
253
+ // astral too, and a length check that only accepted one unit dropped it. It is
254
+ // not a multi-unit expansion such as the uppercase of the sharp s, which is
255
+ // two characters and which the engine does not fold to either.
256
+ function _addIfOneCharacter(out, candidate) {
257
+ if (candidate.length === 1) { out.add(candidate); return; }
258
+ if (candidate.length === 2 && candidate.codePointAt(0) > 0xffff) out.add(candidate);
259
+ }
260
+
261
+ function _setHas(s, ch) {
262
+ if (s.any) return true;
263
+ return s.negated ? !s.chars.has(ch) : s.chars.has(ch);
264
+ }
265
+
266
+ // Two complements always share members (the alphabet is far larger than any
267
+ // pair of exclusion lists), and `any` shares with everything.
268
+ function _setsIntersect(a, b) {
269
+ if (a.any || b.any) return true;
270
+ if (a.negated && b.negated) return true;
271
+ if (!a.negated && !b.negated) {
272
+ var small = a.chars.size <= b.chars.size ? a : b;
273
+ var large = small === a ? b : a;
274
+ var hit = false;
275
+ small.chars.forEach(function (c) { if (large.chars.has(c)) hit = true; });
276
+ return hit;
277
+ }
278
+ var pos = a.negated ? b : a;
279
+ var neg = a.negated ? a : b;
280
+ var out = false;
281
+ pos.chars.forEach(function (c) { if (!neg.chars.has(c)) out = true; });
282
+ return out;
283
+ }
284
+
285
+ // Union, with complements read the way a complement means. A complement
286
+ // covers everything except what it excludes, so a union containing one is
287
+ // itself a complement: it excludes what EVERY complement excludes and NO
288
+ // positive set supplies.
289
+ function _unionSets(sets) {
290
+ var i;
291
+ var negs = [], poss = [];
292
+ for (i = 0; i < sets.length; i += 1) {
293
+ if (sets[i].any) return _anySet();
294
+ (sets[i].negated ? negs : poss).push(sets[i]);
295
+ }
296
+ if (negs.length === 0) {
297
+ var all = new Set();
298
+ for (i = 0; i < poss.length; i += 1) {
299
+ poss[i].chars.forEach(function (c) { all.add(c); });
300
+ }
301
+ return _mkSet(all, false);
302
+ }
303
+ var excluded = new Set();
304
+ negs[0].chars.forEach(function (ch) {
305
+ for (var n = 1; n < negs.length; n += 1) if (!negs[n].chars.has(ch)) return;
306
+ for (var q = 0; q < poss.length; q += 1) if (poss[q].chars.has(ch)) return;
307
+ excluded.add(ch);
308
+ });
309
+ return _mkSet(excluded, true);
310
+ }
311
+
312
+ var WORD_CHARS = (function () {
313
+ var out = [];
314
+ var i;
315
+ for (i = 48; i <= 57; i += 1) out.push(String.fromCharCode(i));
316
+ for (i = 65; i <= 90; i += 1) out.push(String.fromCharCode(i));
317
+ for (i = 97; i <= 122; i += 1) out.push(String.fromCharCode(i));
318
+ out.push("_");
319
+ return out;
320
+ })();
321
+ var DIGIT_CHARS = "0123456789".split("");
322
+ // Source stays pure ASCII: every character these tables name is written as an
323
+ // escape, so a copy of this file cannot silently carry the character itself.
324
+ var SPACE_CHARS = [
325
+ "\u0020", "\u0009", "\u000a", "\u000b", "\u000c", "\u000d",
326
+ "\u00a0", "\u1680", "\u2000", "\u2001", "\u2002", "\u2003",
327
+ "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009",
328
+ "\u200a", "\u2028", "\u2029", "\u202f", "\u205f", "\u3000",
329
+ "\ufeff",
330
+ ];
331
+ var LINE_TERMINATORS = ["\u000a", "\u000d", "\u2028", "\u2029"];
332
+ var CONTROL_ESCAPES = {
333
+ n: "\u000a", r: "\u000d", t: "\u0009",
334
+ f: "\u000c", v: "\u000b", 0: "\u0000",
335
+ };
336
+
337
+ // The set an escape denotes, or null when it is not one this reads (a
338
+ // backreference, a property escape, an assertion).
339
+ function _escapeSet(ch) {
340
+ if (ch === "w") return _mkSet(WORD_CHARS, false);
341
+ if (ch === "W") return _mkSet(WORD_CHARS, true);
342
+ if (ch === "d") return _mkSet(DIGIT_CHARS, false);
343
+ if (ch === "D") return _mkSet(DIGIT_CHARS, true);
344
+ if (ch === "s") return _mkSet(SPACE_CHARS, false);
345
+ if (ch === "S") return _mkSet(SPACE_CHARS, true);
346
+ if (Object.prototype.hasOwnProperty.call(CONTROL_ESCAPES, ch)) {
347
+ return _mkSet([CONTROL_ESCAPES[ch]], false);
348
+ }
349
+ // backref / property / assertion / code escape — none of them a plain literal
350
+ if ("0123456789kpPbBuxc".indexOf(ch) !== -1) return null;
351
+ return _mkSet([ch], false); // an escaped literal
352
+ }
353
+
354
+ // ---- the parser ----
355
+ //
356
+ // Node shapes, all carrying the flags in force where they appear:
357
+ // { type: "alt", branches: [seq] }
358
+ // { type: "seq", terms: [term] }
359
+ // term: { node, min, max } max may be Infinity
360
+ // { type: "set", set } literal / class / escape / dot
361
+ // { type: "group", body: alt } capturing, non-capturing, named, modifier
362
+ // { type: "look" } lookaround — a different detector owns it
363
+ // { type: "anchor" } zero-width, matches no characters
364
+ // { type: "opaque" } anything not represented — never proven
365
+
366
+ // A ReDoS backstop on this parser's own recursion, set far above any
367
+ // pattern an operator writes. Nesting past it leaves the pattern unparsed,
368
+ // which is reported rather than waved through.
369
+ var MAX_PARSE_DEPTH = 200;
370
+
371
+ var FLAG_LETTERS = "dgimsuvy";
372
+
373
+ function _isFlagLetter(ch) { return FLAG_LETTERS.indexOf(ch) !== -1; }
374
+
375
+ function _isDigitChar(ch) { return ch >= "0" && ch <= "9"; }
376
+
377
+ function _isNameStart(ch) {
378
+ return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch === "_" || ch === "$";
379
+ }
380
+
381
+ // `(?=` `(?!` `(?<=` `(?<!` at `at` (which stands on the `?`), or null.
382
+ function _scanLookHead(src, at) {
383
+ if (src.charAt(at) !== "?") return null;
384
+ var next = src.charAt(at + 1);
385
+ if (next === "=" || next === "!") {
386
+ return { negated: next === "!", behind: false, end: at + 2 };
387
+ }
388
+ if (next !== "<") return null;
389
+ var third = src.charAt(at + 2);
390
+ if (third !== "=" && third !== "!") return null; // `(?<name>` — not a lookaround
391
+ return { negated: third === "!", behind: true, end: at + 3 };
392
+ }
393
+
394
+ // `(?<name>` at `at`, giving the offset past the `>`, or -1.
395
+ function _scanNamedGroupHead(src, at) {
396
+ if (src.charAt(at) !== "?" || src.charAt(at + 1) !== "<") return -1;
397
+ var i = at + 2;
398
+ if (!_isNameStart(src.charAt(i))) return -1;
399
+ i += 1;
400
+ while (i < src.length) {
401
+ var ch = src.charAt(i);
402
+ if (ch === ">") return i + 1;
403
+ if (!_isNameStart(ch) && !_isDigitChar(ch)) return -1;
404
+ i += 1;
405
+ }
406
+ return -1;
407
+ }
408
+
409
+ // `(?flags:` or `(?flags-flags:` at `at`, giving which flags it turns on and
410
+ // off, or null when the group is something else.
411
+ function _scanModifierHead(src, at) {
412
+ if (src.charAt(at) !== "?") return null;
413
+ var i = at + 1;
414
+ var on = "";
415
+ var off = "";
416
+ while (i < src.length && _isFlagLetter(src.charAt(i))) { on += src.charAt(i); i += 1; }
417
+ if (src.charAt(i) === "-") {
180
418
  i += 1;
419
+ var offStart = i;
420
+ while (i < src.length && _isFlagLetter(src.charAt(i))) { off += src.charAt(i); i += 1; }
421
+ if (i === offStart) return null; // a dash naming nothing
422
+ }
423
+ if (src.charAt(i) !== ":") return null;
424
+ return { on: on, off: off, end: i + 1 };
425
+ }
426
+
427
+ // `{n}` / `{n,}` / `{n,m}` from `at`, or null when the brace is a literal one.
428
+ // A digit run of any length is read: the pattern is already capped by
429
+ // maxPatternBytes, so a count on the digits bought nothing and its edge was a
430
+ // bypass — a bound one digit too long read as no quantifier at all.
431
+ function _scanBraces(src, at) {
432
+ var i = at + 1; // past the `{`
433
+ var loStart = i;
434
+ while (i < src.length && _isDigitChar(src.charAt(i))) i += 1;
435
+ if (i === loStart) return null;
436
+ var lo = parseInt(src.slice(loStart, i), 10); // base-10 radix
437
+ var hi = lo;
438
+ if (src.charAt(i) === ",") {
439
+ i += 1;
440
+ var hiStart = i;
441
+ while (i < src.length && _isDigitChar(src.charAt(i))) i += 1;
442
+ hi = i === hiStart ? Infinity : parseInt(src.slice(hiStart, i), 10); // base-10 radix
443
+ }
444
+ if (src.charAt(i) !== "}") return null;
445
+ return { min: lo, max: hi, end: i + 1 };
446
+ }
447
+
448
+ // Does a modifier group turn case-insensitivity ON somewhere in the pattern?
449
+ // `(?i:...)`, `(?im:...)`, `(?i-s:...)` — the `i` has to be on the enabling
450
+ // side of the dash, so `(?-i:...)` is not one.
451
+ //
452
+ // Read one character at a time. Asking this with a pattern would be the screen
453
+ // running the construct it screens over operator-supplied text, which is the
454
+ // shape this module exists to keep away from.
455
+ function _turnsFoldingOn(src) {
456
+ for (var i = 0; i + 2 < src.length; i += 1) {
457
+ if (src.charAt(i) !== "(" || src.charAt(i + 1) !== "?") continue;
458
+ var at = i + 2;
459
+ var enablesFold = false;
460
+ while (at < src.length && _isFlagLetter(src.charAt(at))) {
461
+ if (src.charAt(at) === "i") enablesFold = true;
462
+ at += 1;
463
+ }
464
+ if (src.charAt(at) === "-") { // the disabling side
465
+ at += 1;
466
+ while (at < src.length && _isFlagLetter(src.charAt(at))) at += 1;
467
+ }
468
+ if (enablesFold && src.charAt(at) === ":") return true;
469
+ }
470
+ return false;
471
+ }
472
+
473
+ function _parsePattern(src, flags, budget) {
474
+ var pos = 0;
475
+ // Folding can be switched on INSIDE the pattern, so the map cannot be
476
+ // decided from the outer flags alone: `(?i:...)` in a pattern carrying no
477
+ // `i` still needs the equivalences for its body.
478
+ var foldsAnywhere = flags.indexOf("i") !== -1 || _turnsFoldingOn(src);
479
+ var foldGroups = !foldsAnywhere ? new Map()
480
+ : _foldGroups(src, flags.indexOf("i") === -1 ? flags + "i" : flags);
481
+
482
+ function fail() { return null; }
483
+
484
+ function spend(n) { budget.left -= n; return budget.left >= 0; }
485
+
486
+ function parseAlt(depth, activeFlags) {
487
+ if (depth > MAX_PARSE_DEPTH) return fail();
488
+ var branches = [];
489
+ var branch = parseSeq(depth, activeFlags);
490
+ if (branch === null) return fail();
491
+ branches.push(branch);
492
+ while (src.charAt(pos) === "|") {
493
+ pos += 1;
494
+ branch = parseSeq(depth, activeFlags);
495
+ if (branch === null) return fail();
496
+ branches.push(branch);
497
+ }
498
+ return { type: "alt", branches: branches, flags: activeFlags };
499
+ }
500
+
501
+ function parseSeq(depth, activeFlags) {
502
+ var terms = [];
503
+ while (pos < src.length) {
504
+ var c = src.charAt(pos);
505
+ if (c === "|" || c === ")") break;
506
+ var atom = parseAtom(depth, activeFlags);
507
+ if (atom === null) return fail();
508
+ var quant = parseQuant();
509
+ if (quant === null) return fail();
510
+ if (!spend(1)) return fail();
511
+ terms.push({ node: atom, min: quant.min, max: quant.max });
512
+ }
513
+ return { type: "seq", terms: terms, flags: activeFlags };
514
+ }
515
+
516
+ // A quantifier is read here and only here, so every analysis agrees on what
517
+ // one is. A digit run of any length is read: the whole pattern is already
518
+ // capped by maxPatternBytes, so a count on the digits bought nothing and its
519
+ // edge was a bypass — a bound one digit too long read as no quantifier.
520
+ function parseQuant() {
521
+ var c = src.charAt(pos);
522
+ var min, max;
523
+ if (c === "*") { min = 0; max = Infinity; pos += 1; }
524
+ else if (c === "+") { min = 1; max = Infinity; pos += 1; }
525
+ else if (c === "?") { min = 0; max = 1; pos += 1; }
526
+ else if (c === "{") {
527
+ var braced = _scanBraces(src, pos);
528
+ if (braced === null) return { min: 1, max: 1 }; // a literal `{`
529
+ min = braced.min;
530
+ max = braced.max;
531
+ if (max < min) return null; // `{5,2}` — not a pattern this reads
532
+ pos = braced.end;
533
+ } else return { min: 1, max: 1 };
534
+ if (src.charAt(pos) === "?") pos += 1; // lazy — backtracks the same
535
+ return { min: min, max: max };
536
+ }
537
+
538
+ function parseAtom(depth, activeFlags) {
539
+ var c = src.charAt(pos);
540
+ if (c === "(") return parseGroup(depth, activeFlags);
541
+ if (c === "[") return parseClass(activeFlags);
542
+ if (c === "^" || c === "$") {
543
+ pos += 1;
544
+ // Which end it asserts matters: a trailing `$` after a run that reached
545
+ // the end of the input succeeds, while a trailing `^` cannot — it
546
+ // demands the start, so after anything non-empty it fails and sends the
547
+ // engine back to try another split.
548
+ return { type: "anchor", edge: c === "$" ? "end" : "start", flags: activeFlags };
549
+ }
550
+ if (c === ".") {
551
+ pos += 1;
552
+ var dot = activeFlags.indexOf("s") !== -1
553
+ ? _mkSet([], true) // everything
554
+ : _mkSet(LINE_TERMINATORS, true);
555
+ return { type: "set", set: dot, flags: activeFlags };
556
+ }
557
+ if (c === "\\") {
558
+ var esc = src.charAt(pos + 1);
559
+ if (esc === "") return fail();
560
+ pos += 2;
561
+ // A word boundary is an assertion that can FAIL where a start-or-end
562
+ // anchor after a run that reached the end cannot, so the two are not
563
+ // interchangeable to the analysis that asks whether a match can fail.
564
+ if (esc === "b" || esc === "B") {
565
+ return { type: "anchor", edge: "word", negated: esc === "B", flags: activeFlags };
566
+ }
567
+ var set = _escapeSet(esc);
568
+ if (set === null) return { type: "opaque", flags: activeFlags };
569
+ if (!spend(_setSize(set))) return fail();
570
+ return { type: "set", set: _applyFold(set, activeFlags, foldGroups), flags: activeFlags };
571
+ }
572
+ if (c === "*" || c === "+" || c === "?" || c === ")") return fail(); // a quantifier with nothing to quantify
573
+ // Under `u` (and `v`) a surrogate pair is ONE character to the engine, so a
574
+ // quantifier after it repeats the whole code point. Advancing a single code
575
+ // unit would read an astral literal as a fixed lead followed by a repeated
576
+ // trail — which is how `(?:<emoji>+)+` read as something other than the
577
+ // nested quantifier it is.
578
+ var literal = _codePointAt(src, pos, activeFlags);
579
+ pos += literal.length;
580
+ return { type: "set", set: _applyFold(_mkSet([literal], false), activeFlags, foldGroups), flags: activeFlags };
581
+ }
582
+
583
+ function parseClass(activeFlags) {
584
+ var start = pos;
585
+ // Under `v` a class may be a SET EXPRESSION — `[[a-z]--[x]]`,
586
+ // `[[a-z]&&[aeiou]]`, `[\q{abc}]` — and this tokenizer has no
587
+ // representation for one. Read as an ordinary class it comes apart at the
588
+ // first `]`, and the rest is tokenized as though it were pattern text: the
589
+ // repetition then appears to belong to a character that is really the tail
590
+ // of the class, and an analysis that trusted the tree called
591
+ // `[[a-z]--[x]]+b` linear where the same shape written `[a-y]+b` is
592
+ // quadratic. Nothing here can represent it, so nothing here judges it —
593
+ // the whole parse is abandoned and the caller reports what it cannot prove.
594
+ if (activeFlags.indexOf("v") !== -1 && _classUsesSetSyntax(src, pos)) return fail();
595
+ pos += 1; // the `[`
596
+ var negated = false;
597
+ if (src.charAt(pos) === "^") { negated = true; pos += 1; }
598
+ var members = new Set();
599
+ var characterised = true;
600
+ var closed = false;
601
+ while (pos < src.length) {
602
+ var c = src.charAt(pos);
603
+ // `]` ends the class wherever it appears. `[]` is the empty class and
604
+ // `[^]` is every character; treating the first `]` as a member instead
605
+ // walked past the real terminator and ate the rest of the pattern as
606
+ // class members, so every repetition after it vanished from the tree.
607
+ if (c === "]") { pos += 1; closed = true; break; }
608
+ var lo = _classMember(activeFlags);
609
+ if (lo === null) { characterised = false; if (pos <= start) return fail(); continue; }
610
+ if (src.charAt(pos) === "-" && src.charAt(pos + 1) !== "]" && pos + 1 < src.length) {
611
+ pos += 1;
612
+ var hi = _classMember(activeFlags);
613
+ if (hi === null || typeof lo !== "string" || typeof hi !== "string" ||
614
+ lo.length !== 1 || hi.length !== 1) { characterised = false; continue; }
615
+ var from = lo.charCodeAt(0);
616
+ var to = hi.charCodeAt(0);
617
+ if (to < from || to - from + 1 > MAX_CLASS_RANGE) { characterised = false; continue; }
618
+ if (!spend(to - from + 1)) return fail();
619
+ for (var code = from; code <= to; code += 1) members.add(String.fromCharCode(code));
620
+ continue;
621
+ }
622
+ if (typeof lo === "string") {
623
+ if (!spend(1)) return fail();
624
+ members.add(lo);
625
+ } else {
626
+ // Only a POSITIVE shorthand can join the member list. A negated one —
627
+ // `[^\D]`, which is the digits — is a complement, and copying its
628
+ // exclusions in as members then applying the class's own negation
629
+ // states the opposite of what it means.
630
+ if (lo.negated) { characterised = false; continue; }
631
+ lo.chars.forEach(function (m) { members.add(m); });
632
+ if (!spend(_setSize(lo))) return fail();
633
+ }
634
+ }
635
+ if (!closed) return fail(); // unterminated class
636
+ if (!characterised) return { type: "set", set: _anySet(), flags: activeFlags };
637
+ return { type: "set", set: _applyFold(_mkSet(members, negated), activeFlags, foldGroups), flags: activeFlags };
638
+ }
639
+
640
+ // One member of a class: a string for a plain character, a set for a
641
+ // shorthand escape, or null when it is not one this reads.
642
+ function _classMember(activeFlags) {
643
+ var c = src.charAt(pos);
644
+ if (c === "\\") {
645
+ var esc = src.charAt(pos + 1);
646
+ pos += 2;
647
+ var set = _escapeSet(esc);
648
+ if (set === null) return null;
649
+ if (set.chars.size === 1 && !set.negated) {
650
+ var only = null;
651
+ set.chars.forEach(function (m) { only = m; });
652
+ return only;
653
+ }
654
+ return set;
655
+ }
656
+ var member = _codePointAt(src, pos, activeFlags);
657
+ pos += member.length;
658
+ return member;
659
+ }
660
+
661
+ function parseGroup(depth, activeFlags) {
662
+ var open = pos;
663
+ pos += 1; // the `(`
664
+ var innerFlags = activeFlags;
665
+ if (src.charAt(pos) === "?") {
666
+ var look = _scanLookHead(src, pos);
667
+ if (look !== null) {
668
+ // The body is parsed and kept. A lookaround consumes nothing, so it
669
+ // never takes characters from what follows — but the engine still
670
+ // backtracks INSIDE it, so a catastrophic repetition placed there is
671
+ // catastrophic. Skipping to the closing paren left it unexamined, and
672
+ // the quantifier-in-lookaround rule that was meant to cover it reads
673
+ // the source and cannot see through nested parentheses.
674
+ var negatedLook = look.negated;
675
+ var behindLook = look.behind;
676
+ pos = look.end;
677
+ var lookBody = parseAlt(depth + 1, innerFlags);
678
+ if (lookBody === null) return fail();
679
+ if (src.charAt(pos) !== ")") return fail();
680
+ pos += 1;
681
+ return {
682
+ type: "look", body: lookBody, negated: negatedLook, behind: behindLook,
683
+ flags: activeFlags,
684
+ };
685
+ }
686
+ var namedEnd = _scanNamedGroupHead(src, pos);
687
+ if (namedEnd !== -1) pos = namedEnd;
688
+ else {
689
+ var mod = _scanModifierHead(src, pos);
690
+ if (mod === null) {
691
+ var skip = _skipToGroupEnd(open);
692
+ if (skip === -1) return fail();
693
+ pos = skip + 1;
694
+ return { type: "opaque", flags: activeFlags };
695
+ }
696
+ // A modifier group changes the flags for what it encloses, so the
697
+ // analysis inside it has to fold the way the engine will: `(?i:a|A)`
698
+ // is one branch written twice, not two disjoint ones.
699
+ // Every flag the modifier names changes what its body MEANS — `s`
700
+ // decides whether a dot covers a newline as surely as `i` decides
701
+ // which characters are one — so all of them are applied, not the one
702
+ // that happened to be fixed first.
703
+ var f;
704
+ for (f = 0; f < mod.on.length; f += 1) {
705
+ innerFlags = _withFlag(innerFlags, mod.on.charAt(f), true);
706
+ }
707
+ for (f = 0; f < mod.off.length; f += 1) {
708
+ innerFlags = _withFlag(innerFlags, mod.off.charAt(f), false);
709
+ }
710
+ pos = mod.end;
711
+ }
712
+ }
713
+ var body = parseAlt(depth + 1, innerFlags);
714
+ if (body === null) return fail();
715
+ if (src.charAt(pos) !== ")") return fail();
716
+ pos += 1;
717
+ return { type: "group", body: body, flags: activeFlags };
718
+ }
719
+
720
+ function _skipToGroupEnd(from) {
721
+ var depth = 0;
722
+ var inClass = false;
723
+ for (var i = from; i < src.length; i += 1) {
724
+ var c = src.charAt(i);
725
+ if (c === "\\") { i += 1; continue; }
726
+ if (inClass) { if (c === "]") inClass = false; continue; }
727
+ if (c === "[") { inClass = true; continue; }
728
+ if (c === "(") depth += 1;
729
+ else if (c === ")") { depth -= 1; if (depth === 0) return i; }
730
+ }
731
+ return -1;
732
+ }
733
+
734
+ var ast = parseAlt(0, typeof flags === "string" ? flags : "");
735
+ if (ast === null || pos !== src.length) return null;
736
+ return ast;
737
+ }
738
+
739
+ // One character at `at`, as the engine counts characters: a whole code point
740
+ // under `u` or `v`, a single code unit otherwise.
741
+ function _codePointAt(src, at, flags) {
742
+ var one = src.charAt(at);
743
+ if (flags.indexOf("u") === -1 && flags.indexOf("v") === -1) return one;
744
+ var code = src.charCodeAt(at);
745
+ if (code < 0xd800 || code > 0xdbff || at + 1 >= src.length) return one;
746
+ var next = src.charCodeAt(at + 1);
747
+ if (next < 0xdc00 || next > 0xdfff) return one;
748
+ return src.slice(at, at + 2);
749
+ }
750
+
751
+ // Which characters the ENGINE treats as one under these flags.
752
+ //
753
+ // A lower/upper pass does not compute the fold class: the Kelvin sign folds to
754
+ // `k`, but `k` uppercases to `K` and never back to the Kelvin sign, so a pass
755
+ // starting at `K` never reaches it and two branches that both match it were
756
+ // proven disjoint. Which characters an engine treats as equal under `i` is a
757
+ // rule the language states, so the rule is applied — it used to be discovered
758
+ // by building a RegExp per pair of characters and seeing which ones matched,
759
+ // which is the screen reaching for the construct it exists to screen.
760
+ //
761
+ // Only characters PRESENT in the pattern can create an overlap between two of
762
+ // its sets, so the comparison is made over that alphabet alone. Pairs whose
763
+ // lower/upper forms already link them are skipped; that leaves the handful of
764
+ // characters where the answer is not obvious.
765
+ var MAX_FOLD_ALPHABET = 64;
766
+
767
+ // The whole character, so a surrogate pair is canonicalized as one.
768
+ function _canonical(ch, unicodeMode) {
769
+ return codepointClass.canonicalizeForCase(ch.codePointAt(0), unicodeMode);
770
+ }
771
+
772
+ function _foldGroups(src, flags) {
773
+ var unicodeMode = flags.indexOf("u") !== -1 || flags.indexOf("v") !== -1;
774
+ var alphabet = [];
775
+ var seen = new Set();
776
+ for (var i = 0; i < src.length; i += 1) {
777
+ var ch = _codePointAt(src, i, flags);
778
+ if (ch.length === 2) i += 1; // a surrogate pair is one character
779
+ if (ch.charCodeAt(0) < 0x80) continue; // ASCII closes under lower/upper
780
+ if (seen.has(ch)) continue;
781
+ seen.add(ch);
782
+ alphabet.push(ch);
783
+ if (alphabet.length > MAX_FOLD_ALPHABET) return null; // too many to ask about — prove nothing
784
+ }
785
+ if (alphabet.length === 0) return new Map();
786
+ // Every ASCII letter is a candidate partner: the Kelvin sign's partner is an
787
+ // ordinary `k`, which need not appear in the pattern beside it.
788
+ for (var code = 0x41; code <= 0x7a; code += 1) {
789
+ if (code > 0x5a && code < 0x61) continue; // the punctuation between the two runs
790
+ var letter = String.fromCharCode(code);
791
+ if (!seen.has(letter)) { seen.add(letter); alphabet.push(letter); }
792
+ }
793
+ var groups = new Map();
794
+ for (var a = 0; a < alphabet.length; a += 1) {
795
+ for (var b = a + 1; b < alphabet.length; b += 1) {
796
+ var x = alphabet[a], y = alphabet[b];
797
+ if (_linkedByCase(x, y)) continue; // already found by folding
798
+ // Which characters an engine treats as the same under `i` is a rule, not
799
+ // something to be discovered by asking. This used to build a RegExp per
800
+ // pair and see whether one matched the other — the screen reaching for
801
+ // the very construct it screens, and a pattern's worth of them per call.
802
+ // The rule itself is exact and costs a comparison.
803
+ if (_canonical(x, unicodeMode) !== _canonical(y, unicodeMode)) continue;
804
+ _linkFold(groups, x, y);
805
+ _linkFold(groups, y, x);
806
+ }
807
+ }
808
+ return groups;
809
+ }
810
+
811
+ // Does folding one of them already reach the other? Sharing a lowercase is not
812
+ // the same question: the Kelvin sign and `K` both lower-case to `k`, yet
813
+ // folding `K` never produces the Kelvin sign — which is exactly the pair that
814
+ // has to be asked about rather than assumed.
815
+ function _linkedByCase(x, y) {
816
+ return x.toLowerCase() === y || x.toUpperCase() === y ||
817
+ y.toLowerCase() === x || y.toUpperCase() === x;
818
+ }
819
+
820
+ function _linkFold(groups, from, to) {
821
+ var list = boundedMap.getOrInsert(groups, from, function () { return []; });
822
+ if (list.indexOf(to) === -1) list.push(to);
823
+ }
824
+
825
+ function _escapeLiteral(ch) {
826
+ var out = "";
827
+ for (var i = 0; i < ch.length; i += 1) {
828
+ out += "\\u" + ("000" + ch.charCodeAt(i).toString(16)).slice(-4);
829
+ }
830
+ return out;
831
+ }
832
+
833
+ function _applyFold(set, flags, foldGroups) {
834
+ if (flags.indexOf("i") === -1) return set;
835
+ if (foldGroups === null) return _anySet(); // the fold could not be settled
836
+ var folded = _foldSet(set);
837
+ if (folded.any || foldGroups.size === 0) return folded;
838
+ var out = new Set(folded.chars);
839
+ folded.chars.forEach(function (c) {
840
+ var extra = foldGroups.get(c);
841
+ if (extra) for (var i = 0; i < extra.length; i += 1) out.add(extra[i]);
842
+ });
843
+ return _mkSet(out, folded.negated);
844
+ }
845
+
846
+ function _withFlag(flags, flag, on) {
847
+ var has = flags.indexOf(flag) !== -1;
848
+ if (on === has) return flags;
849
+ return on ? flags + flag : flags.split(flag).join("");
850
+ }
851
+
852
+ // ---- reading the tree -----------------------------------------------------
853
+
854
+ // The characters a node can begin with, or null when it can match nothing at
855
+ // all — a nullable head can start anywhere, so it is never provably disjoint.
856
+ function _firstSet(node) {
857
+ if (node.type === "set") return node.set;
858
+ // A lookaround consumes nothing, so for everything the enclosing sequence
859
+ // needs to know — where a term can start, what it can take, how long it is —
860
+ // it behaves as an anchor does. What backtracks INSIDE it is judged on its
861
+ // own, by walking its body.
862
+ if (node.type === "anchor" || node.type === "look") return null;
863
+ if (node.type === "opaque") return _anySet();
864
+ if (node.type === "group") return _firstSet(node.body);
865
+ if (node.type === "alt") {
866
+ var parts = [];
867
+ for (var b = 0; b < node.branches.length; b += 1) {
868
+ var f = _firstSet(node.branches[b]);
869
+ if (f === null) return null;
870
+ parts.push(f);
871
+ }
872
+ return _unionSets(parts);
873
+ }
874
+ for (var i = 0; i < node.terms.length; i += 1) {
875
+ var t = node.terms[i];
876
+ if (t.node.type === "anchor") continue;
877
+ var s = _firstSet(t.node);
878
+ if (s === null) return null;
879
+ if (t.min === 0) return null; // the head is optional
880
+ return s;
881
+ }
882
+ return null; // matches nothing
883
+ }
884
+
885
+ // Does this node match ANY string over `alphabet` long enough to reach the end
886
+ // of it? Not "can it match one" — the input is the attacker's to choose, so a
887
+ // suffix that works for some strings over the run's characters and not others
888
+ // is one they will pick against.
889
+ //
890
+ // This is what a run has to be able to say about whatever follows it before the
891
+ // pattern can be called a single attempt rather than a scan. `a+a` qualifies:
892
+ // every character the run eats is one the trailing `a` accepts, so wherever the
893
+ // run got to, handing one back finishes the match. `[ab]+(?=ab)` does not — the
894
+ // run eats `a` and `b` alike, and against a subject of nothing but `a` the `b`
895
+ // is never there.
896
+ //
897
+ // Knowing only what the suffix can START with is not enough, and the shape of
898
+ // the suffix does not matter: `(?:ab){2}` hides the same `b` behind a group and
899
+ // a count that a bare `ab` shows plainly.
900
+ //
901
+ // Anything unreadable — a nested assertion, an unparsed construct — answers no,
902
+ // which classes the enclosing pattern as a repeated scan rather than vouching
903
+ // for it.
904
+ function _alwaysSatisfiedBy(node, alphabet) {
905
+ return _setIsSubsetOf(alphabet, _satisfiedOn(node, alphabet));
906
+ }
907
+
908
+ // The characters a run can hand back and be certain `node` matches on, whatever
909
+ // follows them — the set the alphabet has to fit inside. Branches contribute to
910
+ // it together rather than one at a time, because `(?:a|b)` is the class `[ab]`
911
+ // written out long and the engine picks the branch the character calls for.
912
+ function _satisfiedOn(node, alphabet) {
913
+ if (node.type === "set") return node.set;
914
+ if (node.type === "group") return _satisfiedOn(node.body, alphabet);
915
+ if (node.type === "anchor" || node.type === "look" || node.type === "opaque") {
916
+ return _mkSet([], false);
917
+ }
918
+ if (node.type === "alt") {
919
+ var parts = [];
920
+ for (var b = 0; b < node.branches.length; b += 1) {
921
+ parts.push(_satisfiedOn(node.branches[b], alphabet));
922
+ }
923
+ return parts.length === 0 ? _mkSet([], false) : _unionSets(parts);
924
+ }
925
+ // A sequence is certain on whatever its first mandatory part is certain on,
926
+ // and only while everything after that part is certain across the WHOLE
927
+ // alphabet — what follows the first character is the subject's to choose, so
928
+ // `(?:a|b)` carries `[ab]+` where `(?:ab|b)` does not.
929
+ var head = null;
930
+ for (var i = 0; i < node.terms.length; i += 1) {
931
+ var t = node.terms[i];
932
+ if (t.min === 0) continue; // it can be left out
933
+ if (head === null) {
934
+ head = _satisfiedOn(t.node, alphabet);
935
+ // Its own repeats begin on characters nothing has pinned down either.
936
+ if (t.min > 1 && !_alwaysSatisfiedBy(t.node, alphabet)) return _mkSet([], false);
937
+ continue;
938
+ }
939
+ if (!_alwaysSatisfiedBy(t.node, alphabet)) return _mkSet([], false);
940
+ }
941
+ return head === null ? _anySet() : head; // nothing mandatory: it matches empty
942
+ }
943
+
944
+ // Everything a node can match anywhere inside it.
945
+ function _allSet(node) {
946
+ if (node.type === "set") return node.set;
947
+ if (node.type === "anchor" || node.type === "look") return _mkSet([], false);
948
+ if (node.type === "opaque") return _anySet();
949
+ if (node.type === "group") return _allSet(node.body);
950
+ var parts = [];
951
+ var list = node.type === "alt" ? node.branches : node.terms;
952
+ for (var i = 0; i < list.length; i += 1) {
953
+ parts.push(_allSet(node.type === "alt" ? list[i] : list[i].node));
954
+ }
955
+ return parts.length === 0 ? _mkSet([], false) : _unionSets(parts);
956
+ }
957
+
958
+ // How many ways a node can match, or Infinity. A repetition whose ways are
959
+ // few explores a fixed number of them however long the input is, which is why
960
+ // three dotted octets — twenty-seven ways — is an ordinary pattern.
961
+ var MAX_BOUNDED_PATHS = 4096;
962
+
963
+ function _waysToMatch(node) {
964
+ if (node.type === "set" || node.type === "anchor" || node.type === "look") return 1;
965
+ if (node.type === "opaque") return Infinity;
966
+ if (node.type === "group") return _waysToMatch(node.body);
967
+ if (node.type === "alt") {
968
+ var sum = 0;
969
+ for (var b = 0; b < node.branches.length; b += 1) {
970
+ sum += _waysToMatch(node.branches[b]);
971
+ if (sum > MAX_BOUNDED_PATHS) return Infinity;
972
+ }
973
+ return sum;
974
+ }
975
+ var product = 1;
976
+ for (var i = 0; i < node.terms.length; i += 1) {
977
+ var t = node.terms[i];
978
+ if (t.max === Infinity) return Infinity;
979
+ var perCopy = _waysToMatch(t.node);
980
+ if (perCopy === Infinity) return Infinity;
981
+ var spans = t.max - t.min + 1; // how many repetition counts
982
+ var ways = spans * Math.pow(perCopy, t.max);
983
+ if (!isFinite(ways) || ways > MAX_BOUNDED_PATHS) return Infinity;
984
+ product *= ways;
985
+ if (product > MAX_BOUNDED_PATHS) return Infinity;
986
+ }
987
+ return product;
988
+ }
989
+
990
+ // Can this node match runs of different lengths? That is what a repetition
991
+ // backtracks over.
992
+ function _isVariableLength(node) {
993
+ if (node.type === "set" || node.type === "anchor" || node.type === "look") return false;
994
+ if (node.type === "opaque") return true;
995
+ if (node.type === "group") return _isVariableLength(node.body);
996
+ if (node.type === "alt") {
997
+ var len = _fixedLength(node.branches[0]);
998
+ for (var b = 1; b < node.branches.length; b += 1) {
999
+ if (_fixedLength(node.branches[b]) !== len) return true;
1000
+ }
1001
+ return len === null;
1002
+ }
1003
+ for (var i = 0; i < node.terms.length; i += 1) {
1004
+ if (node.terms[i].min !== node.terms[i].max) return true;
1005
+ if (_isVariableLength(node.terms[i].node)) return true;
181
1006
  }
182
1007
  return false;
183
1008
  }
184
1009
 
1010
+ // The one length a node always matches, or null when it varies.
1011
+ function _fixedLength(node) {
1012
+ if (node.type === "anchor" || node.type === "look") return 0;
1013
+ if (node.type === "set") return 1;
1014
+ if (node.type === "opaque") return null;
1015
+ if (node.type === "group") return _fixedLength(node.body);
1016
+ if (node.type === "alt") {
1017
+ var len = _fixedLength(node.branches[0]);
1018
+ for (var b = 1; b < node.branches.length; b += 1) {
1019
+ if (_fixedLength(node.branches[b]) !== len) return null;
1020
+ }
1021
+ return len;
1022
+ }
1023
+ var total = 0;
1024
+ for (var i = 0; i < node.terms.length; i += 1) {
1025
+ var t = node.terms[i];
1026
+ if (t.min !== t.max) return null;
1027
+ var one = _fixedLength(t.node);
1028
+ if (one === null) return null;
1029
+ total += one * t.min;
1030
+ }
1031
+ return total;
1032
+ }
1033
+
1034
+ function _isNullable(node) {
1035
+ if (node.type === "anchor" || node.type === "look") return true;
1036
+ if (node.type === "set") return false;
1037
+ if (node.type === "opaque") return true;
1038
+ if (node.type === "group") return _isNullable(node.body);
1039
+ if (node.type === "alt") {
1040
+ for (var b = 0; b < node.branches.length; b += 1) {
1041
+ if (_isNullable(node.branches[b])) return true;
1042
+ }
1043
+ return false;
1044
+ }
1045
+ for (var i = 0; i < node.terms.length; i += 1) {
1046
+ if (node.terms[i].min === 0) continue;
1047
+ if (!_isNullable(node.terms[i].node)) return false;
1048
+ }
1049
+ return true;
1050
+ }
1051
+
1052
+ // ---- the proofs -----------------------------------------------------------
1053
+ //
1054
+ // A repeated body is ambiguous when one repetition could have taken characters
1055
+ // the next one takes instead. These prove the cases where it cannot.
1056
+
1057
+ // Every repetition must contain a particular character that nothing else in
1058
+ // the body can match, so the occurrences of that character pin where each
1059
+ // repetition ends: `(?:[a-z]+-)*` is decided, `(?:[a-z]+a)*` is not. The
1060
+ // delimiter may sit at either end — a separator leading each repetition pins
1061
+ // the split exactly as one trailing it does.
1062
+ function _delimiterForcesSplit(body) {
1063
+ return _splitDelimiter(body) !== null;
1064
+ }
1065
+
1066
+
1067
+ // The body varies in length, but the variation cannot be re-attributed to the
1068
+ // neighbouring repetition: every repetition must begin at a character none of
1069
+ // the varying parts can match, and the varying parts cannot trade with each
1070
+ // other either. `(?:ab?)+` is decided on those terms; `(?:a*a*-)*` is not,
1071
+ // because its two varying parts match the same characters.
1072
+ function _variationCannotMove(body) {
1073
+ if (body.type !== "alt" || body.branches.length !== 1) return false;
1074
+ var terms = body.branches[0].terms;
1075
+ var head = _firstSet(body);
1076
+ if (head === null) return false;
1077
+ var varying = [];
1078
+ for (var i = 0; i < terms.length; i += 1) {
1079
+ var t = terms[i];
1080
+ if (t.min === t.max && !_isVariableLength(t.node)) continue;
1081
+ if (t.max === Infinity) return false; // the delimiter rule owns those
1082
+ varying.push(_allSet(t.node));
1083
+ }
1084
+ if (varying.length === 0) return false;
1085
+ for (var v = 0; v < varying.length; v += 1) {
1086
+ if (_setsIntersect(varying[v], head)) return false;
1087
+ for (var w = v + 1; w < varying.length; w += 1) {
1088
+ if (_setsIntersect(varying[v], varying[w])) return false;
1089
+ }
1090
+ }
1091
+ return true;
1092
+ }
1093
+
1094
+ // Alternation branches that cannot start on the same character decide which
1095
+ // branch matches with one character. That settles the CHOICE; it settles the
1096
+ // EXTENT only if each branch matches one length, so `(?:ab?|b)+` — where the
1097
+ // short form of the first branch leaves a character that starts the second —
1098
+ // is not covered.
1099
+ function _branchesDecideThemselves(body) {
1100
+ if (body.type !== "alt" || body.branches.length < 2) return false;
1101
+ var firsts = [];
1102
+ for (var b = 0; b < body.branches.length; b += 1) {
1103
+ if (_fixedLength(body.branches[b]) === null) return false;
1104
+ var f = _firstSet(body.branches[b]);
1105
+ if (f === null) return false;
1106
+ firsts.push(f);
1107
+ }
1108
+ for (var i = 0; i < firsts.length; i += 1) {
1109
+ for (var j = i + 1; j < firsts.length; j += 1) {
1110
+ if (_setsIntersect(firsts[i], firsts[j])) return false;
1111
+ }
1112
+ }
1113
+ return true;
1114
+ }
1115
+
1116
+ // Branches can overlap on their first character and still be unambiguous,
1117
+ // because one of them REQUIRES a character the other can never match: of
1118
+ // semver's three numeric-identifier branches, the one carrying a letter is not
1119
+ // reachable by the two that are all digits, whatever prefix they share. If a
1120
+ // branch must contain a character outside everything another branch can match,
1121
+ // no string is in both, so the choice between them is decided by the input
1122
+ // rather than guessed and backtracked.
1123
+ function _mustContain(branch) {
1124
+ var parts = [];
1125
+ var terms = branch.type === "seq" ? branch.terms : [];
1126
+ for (var i = 0; i < terms.length; i += 1) {
1127
+ var t = terms[i];
1128
+ if (t.min < 1) continue; // it may match nothing
1129
+ if (t.node.type === "anchor" || t.node.type === "look") continue;
1130
+ var head = _firstSet(t.node);
1131
+ if (head === null) continue; // nullable — requires nothing
1132
+ parts.push(head);
1133
+ }
1134
+ return parts.length === 0 ? null : _unionSets(parts);
1135
+ }
1136
+
1137
+ function _branchLanguagesDisjoint(alt) {
1138
+ if (alt.type !== "alt" || alt.branches.length < 2) return false;
1139
+ var required = [], reachable = [];
1140
+ for (var b = 0; b < alt.branches.length; b += 1) {
1141
+ required.push(_mustContain(alt.branches[b]));
1142
+ reachable.push(_allSet(alt.branches[b]));
1143
+ }
1144
+ for (var i = 0; i < alt.branches.length; i += 1) {
1145
+ for (var j = i + 1; j < alt.branches.length; j += 1) {
1146
+ var iNeedsWhatJCannot = required[i] !== null && !_setsIntersect(required[i], reachable[j]);
1147
+ var jNeedsWhatICannot = required[j] !== null && !_setsIntersect(required[j], reachable[i]);
1148
+ if (!iNeedsWhatJCannot && !jNeedsWhatICannot) return false;
1149
+ }
1150
+ }
1151
+ return true;
1152
+ }
1153
+
1154
+ // An alternation nested anywhere inside a repeated body whose branches can
1155
+ // start on the same character is a choice made afresh at every repetition,
1156
+ // whatever the body's own length does — `((a|a))+` repeats a fixed-length body
1157
+ // and is still exponential, because the wrapper changes nothing about the
1158
+ // choice inside it.
1159
+ function _containsUndecidedChoice(node) {
1160
+ if (node === null || typeof node !== "object") return false;
1161
+ if (node.type === "look") return node.body ? _containsUndecidedChoice(node.body) : true;
1162
+ if (node.type === "opaque") return true;
1163
+ if (node.type === "group") return _containsUndecidedChoice(node.body);
1164
+ if (node.type === "alt") {
1165
+ if (node.branches.length > 1 && !_branchesDecideThemselves(node) &&
1166
+ !_branchLanguagesDisjoint(node)) return true;
1167
+ for (var b = 0; b < node.branches.length; b += 1) {
1168
+ if (_containsUndecidedChoice(node.branches[b])) return true;
1169
+ }
1170
+ return false;
1171
+ }
1172
+ if (node.type !== "seq") return false;
1173
+ for (var i = 0; i < node.terms.length; i += 1) {
1174
+ if (_containsUndecidedChoice(node.terms[i].node)) return true;
1175
+ }
1176
+ return false;
1177
+ }
1178
+
1179
+ // A repeated term is safe when any one of the proofs holds. Ways-to-match is
1180
+ // checked first because it is the cheapest and the most general: a repetition
1181
+ // with few ways to match explores all of them in constant time.
1182
+ // Both the repetition count and the body's own variation are finite, and the
1183
+ // ways they combine are few: the engine enumerates all of them in constant
1184
+ // time however long the input is. Three dotted octets is twenty-seven ways.
1185
+ // This holds whatever the neighbouring terms do, so it is asked first and on
1186
+ // its own.
1187
+ // How many ways a term — its node and its repetition together — can match, or
1188
+ // Infinity. Two positions side by side cost the PRODUCT of their ways, so this
1189
+ // is what composes; asking whether each is separately small does not, and let
1190
+ // four `a{0,4095}` positions through at 4.5 seconds a request.
1191
+ function _repetitionWays(term) {
1192
+ if (term.max === Infinity) return Infinity;
1193
+ var perCopy = _waysToMatch(term.node);
1194
+ if (perCopy === Infinity) return Infinity;
1195
+ var total = (term.max - term.min + 1) * Math.pow(perCopy, term.max);
1196
+ return isFinite(total) ? total : Infinity;
1197
+ }
1198
+
1199
+ function _repetitionIsEnumerable(term) {
1200
+ if (term.max === Infinity) return false;
1201
+ var perCopy = _waysToMatch(term.node);
1202
+ if (perCopy === Infinity) return false;
1203
+ var total = (term.max - term.min + 1) * Math.pow(perCopy, term.max);
1204
+ return isFinite(total) && total <= MAX_BOUNDED_PATHS;
1205
+ }
1206
+
1207
+ function _repetitionIsDecided(term) {
1208
+ var body = term.node.type === "group" ? term.node.body : term.node;
1209
+ if (_delimiterForcesSplit(body)) return true;
1210
+ if (_variationCannotMove(body)) return true;
1211
+ if (_branchesDecideThemselves(body)) return true;
1212
+ return false;
1213
+ }
1214
+
1215
+ // ---- the findings ---------------------------------------------------------
1216
+ //
1217
+ // The boundaries BETWEEN a sequence's terms matter as much as any one term:
1218
+ // five individually-decided `(?:a|b)+` groups in a row are each unambiguous
1219
+ // while the ways to divide one run of input among them are not. A term is only
1220
+ // judged in isolation when the terms around it cannot take its characters.
1221
+
1222
+ function _boundariesForced(seq) {
1223
+ var variable = [];
1224
+ for (var i = 0; i < seq.terms.length; i += 1) {
1225
+ var t = seq.terms[i];
1226
+ if (t.min !== t.max || _isVariableLength(t.node)) variable.push(i);
1227
+ }
1228
+ if (variable.length <= 1) return true;
1229
+ for (var v = 0; v < variable.length - 1; v += 1) {
1230
+ var idx = variable[v];
1231
+ var body = seq.terms[idx].node.type === "group" ? seq.terms[idx].node.body : seq.terms[idx].node;
1232
+ // The plainest reason a boundary cannot float: nothing any LATER term can
1233
+ // begin with is something the earlier one could have taken instead. That
1234
+ // is what pins `[a-z0-9]+` against `(?:-[a-z0-9]+)*` — every repetition of
1235
+ // the group starts on a hyphen, and the run before it cannot match one.
1236
+ var reach = _allSet(seq.terms[idx].node);
1237
+ var pinned = true;
1238
+ for (var k = idx + 1; k < seq.terms.length; k += 1) {
1239
+ var later = seq.terms[k];
1240
+ if (later.node.type === "anchor" || later.node.type === "look") continue;
1241
+ // A null head means the term can match nothing, so it can begin
1242
+ // anywhere — that pins nothing and must not read as "no conflict".
1243
+ var head = _firstSet(later.node);
1244
+ if (head === null || _setsIntersect(reach, head)) { pinned = false; break; }
1245
+ // A term that MUST match is a wall: the run before it has to stop where
1246
+ // the wall begins, and nothing past the wall can reach back across it.
1247
+ // That is what pins an email local part against its `@`.
1248
+ if (later.min > 0) break;
1249
+ }
1250
+ if (pinned) continue;
1251
+ var delimiter = _splitDelimiter(body);
1252
+ if (delimiter === null) return false;
1253
+ for (var beyond = idx + 1; beyond < seq.terms.length; beyond += 1) {
1254
+ if (_setsIntersect(_allSet(seq.terms[beyond].node), delimiter)) return false;
1255
+ }
1256
+ }
1257
+ return true;
1258
+ }
1259
+
1260
+ // The characters a repeated body must contain and nothing else in it can
1261
+ // match, or null. The separator sits at one END — a leading one pins the split
1262
+ // exactly as a trailing one does — and it may be more than one term long: `::`
1263
+ // is two terms, `\s+` is one that repeats. So the run is grown from the end
1264
+ // while it still overlaps the rest, and what remains has to be unambiguous on
1265
+ // its own. It is a SET rather than a character because under `i` a
1266
+ // one-character separator covers both of its cases.
1267
+ function _splitDelimiter(body) {
1268
+ if (body.type !== "alt" || body.branches.length !== 1) return null;
1269
+ var terms = body.branches[0].terms;
1270
+ if (terms.length < 2) return null;
1271
+ var trailing = _endRunDelimiter(terms, true);
1272
+ if (trailing !== null) return trailing;
1273
+ return _endRunDelimiter(terms, false);
1274
+ }
1275
+
1276
+ function _endRunDelimiter(terms, fromEnd) {
1277
+ for (var size = 1; size < terms.length; size += 1) {
1278
+ var run = fromEnd ? terms.slice(terms.length - size) : terms.slice(0, size);
1279
+ var rest = fromEnd ? terms.slice(0, terms.length - size) : terms.slice(size);
1280
+ if (rest.length === 0) return null;
1281
+ var runSets = [];
1282
+ var mandatory = false;
1283
+ var readable = true;
1284
+ for (var i = 0; i < run.length && readable; i += 1) {
1285
+ var node = run[i].node;
1286
+ // Only a positive, characterised set can be a separator: a complement or
1287
+ // an unreadable atom says nothing about what the rest cannot match.
1288
+ if (node.type !== "set" || node.set.any || node.set.negated) readable = false;
1289
+ // A separator has to be able to swallow a WHOLE run of its own
1290
+ // characters, or the boundary floats inside that run: `-+` takes every
1291
+ // dash and the next repetition must start on something else, while
1292
+ // `\d{1,3}` caps itself at three and a run of six digits divides among
1293
+ // repetitions several ways. Exact counts and open-ended repeats can;
1294
+ // a capped-but-varying one cannot.
1295
+ else if (run[i].min !== run[i].max && run[i].max !== Infinity) readable = false;
1296
+ else {
1297
+ if (run[i].min > 0) mandatory = true;
1298
+ runSets.push(node.set);
1299
+ }
1300
+ }
1301
+ if (!readable) return null; // it cannot grow past this
1302
+ if (!mandatory) continue; // every repetition must contain it
1303
+ var runSet = _unionSets(runSets);
1304
+ var restSets = [];
1305
+ for (var r = 0; r < rest.length; r += 1) restSets.push(_allSet(rest[r].node));
1306
+ // Still shared with the rest — a longer run may separate them, as the
1307
+ // second colon of `::` does.
1308
+ if (_setsIntersect(runSet, _unionSets(restSets))) continue;
1309
+ // An OPEN-ENDED separator needs something on the other side of it that
1310
+ // must match. `(?:b*a)+` is pinned because each single `a` ends exactly one
1311
+ // repetition, whatever `b*` does; `(?:b*a+)+` is not, because a run of a's
1312
+ // divides among repetitions every possible way once the rest can match
1313
+ // nothing between them — that is `(a+)+` wearing a nullable decoration.
1314
+ var runIsOpenEnded = false;
1315
+ for (var q = 0; q < run.length; q += 1) {
1316
+ if (run[q].max === Infinity) runIsOpenEnded = true;
1317
+ }
1318
+ if (runIsOpenEnded && !_someTermMustMatch(rest)) return null;
1319
+ // The run has to be unambiguous itself, not only the rest: growing it over
1320
+ // several varying terms would otherwise let THEM trade inside it.
1321
+ if (!_varyingPartsCannotTrade(run)) return null;
1322
+ if (!_varyingPartsCannotTrade(rest)) return null;
1323
+ return runSet;
1324
+ }
1325
+ return null;
1326
+ }
1327
+
1328
+ // A separator pins where each repetition ENDS. The paths through the whole
1329
+ // match are the PRODUCT of the paths through each repetition, so what is left
1330
+ // of the body has to have one parse of its own — which is a question about
1331
+ // whether its varying parts can take each other's characters, not about how
1332
+ // many of them there are. `(?:,\s*[a-z]+)*` has two and they are disjoint;
1333
+ // `(?:a*a*-)*` has two that are not, and it is exponential.
1334
+ // Far above any real pattern's fixed run of atoms; a pattern that reaches it is
1335
+ // reported rather than read further.
1336
+ var MAX_BRIDGE_STEPS = 256;
1337
+ var BRIDGE_UNREADABLE = { unreadable: true };
1338
+
1339
+ function _varyingPartsCannotTrade(terms) {
1340
+ var varying = [];
1341
+ for (var i = 0; i < terms.length; i += 1) {
1342
+ var t = terms[i];
1343
+ if (t.min === t.max && !_isVariableLength(t.node)) continue;
1344
+ varying.push({ at: i, set: _allSet(t.node) });
1345
+ }
1346
+ for (var v = 0; v < varying.length; v += 1) {
1347
+ for (var w = v + 1; w < varying.length; w += 1) {
1348
+ if (_setsIntersect(varying[v].set, varying[w].set)) return false;
1349
+ // They can also trade THROUGH the fixed-width terms between them: in
1350
+ // `a*[ab]b*` the segment "aab" parses two ways, because `[ab]` can take
1351
+ // the character either neighbour gives up. Comparing only the varying
1352
+ // parts to each other reads {a} and {b} as disjoint and misses it.
1353
+ //
1354
+ // The hand-off runs the whole way along, not one atom at a time. In
1355
+ // `a*[ab][bc]c*` no single atom touches both ends, and yet `abc` parses
1356
+ // twice — every atom takes its neighbour's character and the whole
1357
+ // segment shifts by one. So the chain is walked, and it carries only
1358
+ // while each step overlaps the one before it.
1359
+ var steps = _bridgeSteps(terms, varying[v].at + 1, varying[w].at);
1360
+ if (steps === BRIDGE_UNREADABLE) return false; // unread, so unproven
1361
+ if (steps === null) continue; // an adjacent pair covers it
1362
+ var carried = varying[v].set;
1363
+ var chained = true;
1364
+ for (var s = 0; s < steps.length; s += 1) {
1365
+ if (!_setsIntersect(carried, steps[s])) { chained = false; break; }
1366
+ carried = steps[s];
1367
+ }
1368
+ if (chained && _setsIntersect(carried, varying[w].set)) return false;
1369
+ }
1370
+ }
1371
+ return true;
1372
+ }
1373
+
1374
+ // The characters between two positions, ONE AT A TIME. A hand-off moves the
1375
+ // whole segment along by a single character, so it has to be read that way:
1376
+ // `(?:ax)` is an `a` then an `x`, and the union {a,x} would invent a step from
1377
+ // `a` straight to `b` that no shift can make. Written out or parenthesised, the
1378
+ // same characters must give the same answer.
1379
+ //
1380
+ // Null when something between them is variable-width — that pair is covered by
1381
+ // the adjacent pairs on either side of it, which are checked in their own turn.
1382
+ function _bridgeSteps(terms, from, to) {
1383
+ var steps = [];
1384
+ for (var i = from; i < to; i += 1) {
1385
+ var ok = _pushBridgeSteps(terms[i], steps);
1386
+ if (ok === false) return null; // variable-width between them
1387
+ if (ok === null) return BRIDGE_UNREADABLE; // too much to read: fail closed
1388
+ }
1389
+ return steps;
1390
+ }
1391
+
1392
+ // A count is never expanded into a step per repetition. `[ab]{1000000000}` is
1393
+ // one step: a set that overlaps its neighbour still overlaps it however many
1394
+ // times it repeats, so the extra copies say nothing the first did not — and
1395
+ // writing them out would let a short pattern spend the screen's memory, which
1396
+ // is the very thing this module exists to prevent.
1397
+ function _pushBridgeSteps(term, steps) {
1398
+ var node = term.node;
1399
+ if (node.type === "anchor" || node.type === "look") return true; // no characters
1400
+ if (term.min !== term.max) return false; // variable-width
1401
+ if (term.min === 0) return true; // no characters
1402
+ if (steps.length >= MAX_BRIDGE_STEPS) return null;
1403
+ if (node.type === "set") {
1404
+ steps.push(node.set);
1405
+ return true;
1406
+ }
1407
+ if (node.type === "group" && node.body && node.body.type === "alt" &&
1408
+ node.body.branches.length === 1 && node.body.branches[0].type === "seq") {
1409
+ // Two rounds show every hand-off there is, including the one across the
1410
+ // join between repetitions; a third only repeats what the second showed.
1411
+ var inner = node.body.branches[0].terms;
1412
+ var rounds = term.min > 1 ? 2 : 1;
1413
+ for (var r = 0; r < rounds; r += 1) {
1414
+ for (var j = 0; j < inner.length; j += 1) {
1415
+ var ok = _pushBridgeSteps(inner[j], steps);
1416
+ if (ok !== true) return ok;
1417
+ }
1418
+ }
1419
+ return true;
1420
+ }
1421
+ // A choice, or something unread: carry everything it can match, which can
1422
+ // only make the chain easier to complete and the pattern harder to vouch for.
1423
+ steps.push(_allSet(node));
1424
+ return true;
1425
+ }
1426
+
1427
+ // Does at least one term here have to match a character? A body of nothing but
1428
+ // optional parts can match empty, which is what makes a separator beside it no
1429
+ // separator at all.
1430
+ function _someTermMustMatch(terms) {
1431
+ for (var i = 0; i < terms.length; i += 1) {
1432
+ var t = terms[i];
1433
+ if (t.min < 1) continue;
1434
+ if (t.node.type === "anchor" || t.node.type === "look") continue;
1435
+ if (!_isNullable(t.node)) return true;
1436
+ }
1437
+ return false;
1438
+ }
1439
+
1440
+ // Two positions in one sequence that repeat over characters they share divide
1441
+ // a run of those characters between them every possible way — quadratic for
1442
+ // two, degree k for k. Nullable terms between them do not separate them: they
1443
+ // can match nothing, which is exactly what makes the two neighbours. It only
1444
+ // costs anything when something after the pair can FAIL and send the engine
1445
+ // back to try another split, so a later term that must match and cannot match
1446
+ // what the pair matches is what turns the ambiguity into work. Without one —
1447
+ // `^\s*.*$` — the first attempt succeeds and there is nothing to explore.
1448
+ function _adjacentAmbiguity(seq, outerCanFail) {
1449
+ for (var i = 0; i < seq.terms.length; i += 1) {
1450
+ var left = seq.terms[i];
1451
+ if (!_repeatsVariably(left)) continue;
1452
+ var leftSet = _allSet(left.node);
1453
+ var leftWays = _repetitionWays(left);
1454
+ for (var j = i + 1; j < seq.terms.length; j += 1) {
1455
+ var right = seq.terms[j];
1456
+ // The two positions can only trade characters at the boundary between
1457
+ // them, so what matters is what the later one can BEGIN with — a group
1458
+ // that must start on a hyphen takes nothing from a run of digits, however
1459
+ // many digits it can match further in.
1460
+ var rightHead = _firstSet(right.node);
1461
+ if (rightHead === null) rightHead = _allSet(right.node);
1462
+ if (_repeatsVariably(right) && _setsIntersect(leftSet, rightHead)) {
1463
+ // Together the two positions divide a run of shared characters
1464
+ // leftWays x rightWays ways. Few enough of those and the engine
1465
+ // enumerates them in constant time however long the run is.
1466
+ var pairWays = leftWays * _repetitionWays(right);
1467
+ if (!(isFinite(pairWays) && pairWays <= MAX_BOUNDED_PATHS) &&
1468
+ _canFailAfter(seq, j + 1, _unionSets([leftSet, _allSet(right.node)]),
1469
+ outerCanFail)) return true;
1470
+ break;
1471
+ }
1472
+ if (!_termIsNullable(right)) break; // a mandatory term separates them
1473
+ }
1474
+ }
1475
+ return false;
1476
+ }
1477
+
1478
+ function _repeatsVariably(term) {
1479
+ if (term.min !== term.max) return true;
1480
+ return term.min > 0 && _isVariableLength(term.node);
1481
+ }
1482
+
1483
+ function _termIsNullable(term) {
1484
+ return term.min === 0 || _isNullable(term.node);
1485
+ }
1486
+
1487
+ // Can the match still fail once the pair has been passed? Only then does the
1488
+ // engine come back to try another split. Anything that MUST match can be
1489
+ // absent from the input. If everything left is optional, the match fails only
1490
+ // on a character nothing here accepts — so it cannot fail at all when the pair
1491
+ // and the optional remainder between them cover every character, which is why
1492
+ // `^\s*.*$` is linear while `^a*a*$` is quadratic.
1493
+ // Whether anything at or after `from` can refuse, ignoring what the pair
1494
+ // covers. Used to tell a nested sequence that its enclosing context has a
1495
+ // failure point of its own — a group hides the `!` that follows it, and
1496
+ // without this the inside of `^(?:\s*.*)!$` reads as a match that cannot fail.
1497
+ function _tailCanFail(seq, from) {
1498
+ for (var i = from; i < seq.terms.length; i += 1) {
1499
+ var t = seq.terms[i];
1500
+ if (t.node.type === "look") return true;
1501
+ if (t.node.type === "anchor") {
1502
+ if (t.node.edge === "end") continue;
1503
+ return true;
1504
+ }
1505
+ if (t.min > 0) return true;
1506
+ }
1507
+ return false;
1508
+ }
1509
+
1510
+ function _canFailAfter(seq, from, covered, outerCanFail) {
1511
+ if (outerCanFail) return true;
1512
+ var reach = [covered];
1513
+ for (var i = from; i < seq.terms.length; i += 1) {
1514
+ var t = seq.terms[i];
1515
+ // An assertion consumes nothing but can still refuse, and a refusal is
1516
+ // what sends the engine back for another split. Only a start-or-end
1517
+ // anchor is safe here, and only because the pair covering every character
1518
+ // means the greedy first attempt already reached the end. A word boundary
1519
+ // or a lookaround can fail wherever it sits.
1520
+ if (t.node.type === "look") return true;
1521
+ if (t.node.type === "anchor") {
1522
+ if (t.node.edge === "end") continue;
1523
+ return true;
1524
+ }
1525
+ if (t.min > 0) return true; // must match, so it can be missing
1526
+ reach.push(_allSet(t.node));
1527
+ }
1528
+ var all = _unionSets(reach);
1529
+ return !(all.any || (all.negated && all.chars.size === 0));
1530
+ }
1531
+
1532
+ // Walk every sequence in the tree, judging each repeated term and each
1533
+ // sequence's own boundaries.
1534
+ function _findAmbiguity(node, out, outerCanFail) {
1535
+ if (node === null || typeof node !== "object") return;
1536
+ outerCanFail = outerCanFail === true;
1537
+ if (node.type === "alt") {
1538
+ for (var b = 0; b < node.branches.length; b += 1) {
1539
+ _findAmbiguity(node.branches[b], out, outerCanFail);
1540
+ }
1541
+ return;
1542
+ }
1543
+ if (node.type === "group") { _findAmbiguity(node.body, out, outerCanFail); return; }
1544
+ if (node.type === "look") {
1545
+ // A lookaround consumes nothing, so it never takes characters from what
1546
+ // follows — but the engine still backtracks INSIDE it, and a catastrophic
1547
+ // repetition placed there is catastrophic. What it contains is judged the
1548
+ // same way, and reported under the lookaround rule as well so an operator
1549
+ // who tightened that one specifically still gets the finding.
1550
+ if (node.body) {
1551
+ var inner = { nested: false, alternation: false, lookaround: false };
1552
+ _findAmbiguity(node.body, inner, outerCanFail);
1553
+ if (inner.nested) out.nested = true;
1554
+ if (inner.alternation) out.alternation = true;
1555
+ if (inner.nested || inner.alternation || inner.lookaround) out.lookaround = true;
1556
+ }
1557
+ return;
1558
+ }
1559
+ if (node.type !== "seq") return;
1560
+
1561
+ // Boundaries proven forced are boundaries that cannot float, which is the
1562
+ // whole of what the adjacency check looks for — asking it again would only
1563
+ // re-derive, less precisely, what the proof already settled.
1564
+ var forced = _boundariesForced(node);
1565
+ if (!forced && _adjacentAmbiguity(node, outerCanFail)) out.nested = true;
1566
+ for (var i = 0; i < node.terms.length; i += 1) {
1567
+ var term = node.terms[i];
1568
+ // What follows THIS term inside the sequence, or failing that whatever the
1569
+ // enclosing context can refuse with, is the continuation its contents face.
1570
+ _findAmbiguity(term.node, out, _tailCanFail(node, i + 1) || outerCanFail);
1571
+ if (term.max <= 1) continue; // taken at most once
1572
+ var body = term.node.type === "group" ? term.node.body : term.node;
1573
+ var isAlternation = body.type === "alt" && body.branches.length > 1;
1574
+ if (!_isVariableLength(term.node) && !isAlternation &&
1575
+ !_containsUndecidedChoice(body)) continue;
1576
+ if (_repetitionIsEnumerable(term)) continue;
1577
+ if (forced && _repetitionIsDecided(term) && !_containsUndecidedChoice(body)) continue;
1578
+ if (isAlternation) out.alternation = true;
1579
+ else out.nested = true;
1580
+ }
1581
+ }
1582
+
1583
+ // The most characters a node can match, or Infinity.
1584
+ function _maxLength(node) {
1585
+ if (node.type === "anchor" || node.type === "look") return 0;
1586
+ if (node.type === "set") return 1;
1587
+ if (node.type === "opaque") return Infinity;
1588
+ if (node.type === "group") return _maxLength(node.body);
1589
+ if (node.type === "alt") {
1590
+ var widest = 0;
1591
+ for (var b = 0; b < node.branches.length; b += 1) {
1592
+ var one = _maxLength(node.branches[b]);
1593
+ if (one === Infinity) return Infinity;
1594
+ if (one > widest) widest = one;
1595
+ }
1596
+ return widest;
1597
+ }
1598
+ var total = 0;
1599
+ for (var i = 0; i < node.terms.length; i += 1) {
1600
+ var t = node.terms[i];
1601
+ var per = _maxLength(t.node);
1602
+ if (per === 0) continue;
1603
+ if (per === Infinity || t.max === Infinity) return Infinity;
1604
+ total += per * t.max;
1605
+ }
1606
+ return total;
1607
+ }
1608
+
1609
+ // The fewest characters this node can consume. A body that can consume none
1610
+ // matches wherever it is asked, including where there is nothing behind it —
1611
+ // which is what makes a NEGATIVE lookbehind over it fail everywhere: `(?<!)`
1612
+ // and `(?<!a?)` are refusals at every position, not assertions that hold at the
1613
+ // start of the subject.
1614
+ function _minLength(node) {
1615
+ if (node.type === "anchor" || node.type === "look") return 0;
1616
+ if (node.type === "set") return 1;
1617
+ if (node.type === "opaque") return 0; // never proven to need one
1618
+ if (node.type === "group") return _minLength(node.body);
1619
+ if (node.type === "alt") {
1620
+ var shortest = Infinity;
1621
+ for (var b = 0; b < node.branches.length; b += 1) {
1622
+ var one = _minLength(node.branches[b]);
1623
+ if (one < shortest) shortest = one;
1624
+ }
1625
+ return shortest === Infinity ? 0 : shortest;
1626
+ }
1627
+ var total = 0;
1628
+ for (var i = 0; i < node.terms.length; i += 1) {
1629
+ var t = node.terms[i];
1630
+ if (t.min < 1) continue;
1631
+ total += _minLength(t.node) * t.min;
1632
+ }
1633
+ return total;
1634
+ }
1635
+
1636
+ // A pattern that is not anchored at the start is retried at EVERY position in
1637
+ // the subject. That costs nothing when an attempt fails at once — a leading
1638
+ // literal is checked and rejected in constant time — but when the pattern can
1639
+ // consume an unbounded amount BEFORE reaching something that must match, each
1640
+ // attempt walks the rest of the input before discovering the failure, and the
1641
+ // whole scan is quadratic in the subject length.
1642
+ //
1643
+ // No ambiguity is involved, so none of the backtracking rules see it: `/a+b/`
1644
+ // and `/(\w+)\s+(\d+)/` are each unambiguous on one attempt and each cost
1645
+ // seconds on a few tens of kilobytes of attacker-controlled input. The remedy
1646
+ // is to anchor the pattern, make it sticky, or bound the subject — so this is
1647
+ // its own finding under its own policy, and an operator who bounds the subject
1648
+ // can turn it off without giving up the backtracking classes.
1649
+ function _unanchoredScanIsQuadratic(ast, flags) {
1650
+ var text = typeof flags === "string" ? flags : "";
1651
+ if (text.indexOf("y") !== -1) return false; // sticky — one position, not every one
1652
+ var multiline = text.indexOf("m") !== -1;
1653
+ if (ast.type !== "alt") return false;
1654
+ for (var b = 0; b < ast.branches.length; b += 1) {
1655
+ if (_branchScanIsQuadratic(ast.branches[b], multiline)) return true;
1656
+ }
1657
+ return false;
1658
+ }
1659
+
1660
+ // A group that neither repeats nor offers a choice changes nothing about how
1661
+ // many positions the engine tries the pattern at, so its contents are part of
1662
+ // the same scan. Reading only the outermost term list let one pair of
1663
+ // parentheses hide the cost: `(a+b)` is `a+b`.
1664
+ function _inlineForScan(terms) {
1665
+ var out = [];
1666
+ for (var i = 0; i < terms.length; i += 1) {
1667
+ var t = terms[i];
1668
+ if (t.min === 1 && t.max === 1 && t.node.type === "group" &&
1669
+ t.node.body.type === "alt" && t.node.body.branches.length === 1) {
1670
+ out = out.concat(_inlineForScan(t.node.body.branches[0].terms));
1671
+ continue;
1672
+ }
1673
+ out.push(t);
1674
+ }
1675
+ return out;
1676
+ }
1677
+
1678
+ // Expanding a choice re-reads everything around it, so a pattern made of them
1679
+ // could cost more to screen than to run. Far above any real pattern, and a
1680
+ // pattern that reaches it is reported rather than waved through.
1681
+ var MAX_SCAN_EXPANSIONS = 2048;
1682
+
1683
+ // Stands in for an assertion carried past the one being read: it consumes
1684
+ // nothing and it can refuse, which is all the scan analysis needs of it.
1685
+ var ASSERTION_STOP = { node: { type: "anchor", edge: "assertion" }, min: 1, max: 1 };
1686
+
1687
+ function _branchScanIsQuadratic(seq, multiline, budget) {
1688
+ if (seq.type !== "seq") return false;
1689
+ return _termsScanIsQuadratic(_inlineForScan(seq.terms), multiline,
1690
+ budget || { left: MAX_SCAN_EXPANSIONS });
1691
+ }
1692
+
1693
+ function _termsScanIsQuadratic(terms, multiline, budget) {
1694
+ // Anchored to one position, so nothing inside it is repeated per character.
1695
+ if (_pinnedToOnePosition(terms, multiline)) return false;
1696
+
1697
+ // A lookaround consumes nothing, which is not the same as costing nothing.
1698
+ // `(?=a+b)` re-runs its body at every position in the subject and each run
1699
+ // walks what is left, so the assertion is the scan. Its body is a pattern in
1700
+ // its own right and is read as one — including its own anchors, so `(?=^a+b)`
1701
+ // is one attempt like any other anchored pattern.
1702
+ for (var k = 0; k < terms.length; k += 1) {
1703
+ var look = terms[k];
1704
+ if (look.node.type !== "look" || !look.node.body) continue;
1705
+ // A lookBEHIND matches its body backwards from the position, so the same
1706
+ // reading applies to it reversed: what it tests first is what stands
1707
+ // immediately before. It is the difference between `(?<=a+b)`, which fails
1708
+ // on the neighbouring `b` at nearly every position, and `(?<=ba+)`, which
1709
+ // walks back through everything before it at every one.
1710
+ var body = look.node.behind ? _reversedForLookbehind(look.node.body) : look.node.body;
1711
+ if (body.type !== "alt") continue;
1712
+ if (!_lookIsReachableEverywhere(terms, k, body)) continue;
1713
+ // What follows the assertion is part of the same attempt, so it is where
1714
+ // the attempt can fail. An assertion that always SUCCEEDS still costs what
1715
+ // its run costs, and something failing after it makes the engine pay that
1716
+ // again from the next position: `(?=a+)[^a]` walks the whole subject at
1717
+ // every position, though `(?=a+)` alone matches at the first.
1718
+ // The body consumes nothing, so what follows the assertion is tested at the
1719
+ // same position the body started from, not after it. Reading a following
1720
+ // assertion as though it stood past the body would judge it against the
1721
+ // body's run — so it is carried as what it is here: something that can
1722
+ // refuse without consuming.
1723
+ var continuation = [];
1724
+ if (look.node.negated) {
1725
+ // For a NEGATED assertion the body succeeding IS the failure, so nothing
1726
+ // after it matters: `(?!a+)` refuses at every position, each time having
1727
+ // walked the rest of the subject to find the `a+` it forbids.
1728
+ continuation.push(ASSERTION_STOP);
1729
+ } else {
1730
+ for (var c = k + 1; c < terms.length; c += 1) {
1731
+ continuation.push(terms[c].node.type === "look" ? ASSERTION_STOP : terms[c]);
1732
+ }
1733
+ }
1734
+ for (var lb = 0; lb < body.branches.length; lb += 1) {
1735
+ var branch = body.branches[lb];
1736
+ if (branch.type !== "seq") continue;
1737
+ var withRest = _inlineForScan(branch.terms).concat(continuation);
1738
+ if (_termsScanIsQuadratic(withRest, multiline, budget)) return true;
1739
+ }
1740
+ }
1741
+
1742
+ // A choice is several scans, not one: only one branch of `(?:x|a+b)` runs
1743
+ // away, and that is enough. It need not be the FIRST thing in the pattern —
1744
+ // `a(?:x|a+b)` enters the same branch from every starting position, so the
1745
+ // search continues past whatever fixed atoms precede it.
1746
+ for (var g = 0; g < terms.length; g += 1) {
1747
+ var term = terms[g];
1748
+ if (term.min === 1 && term.max === 1 && term.node.type === "group" &&
1749
+ term.node.body.type === "alt" && term.node.body.branches.length > 1) {
1750
+ var after = terms.slice(g + 1);
1751
+ var before = terms.slice(0, g);
1752
+ for (var br = 0; br < term.node.body.branches.length; br += 1) {
1753
+ budget.left -= 1;
1754
+ if (budget.left <= 0) return true; // unread, so reported
1755
+ var spliced = _inlineForScan(term.node.body.branches[br].terms).concat(after);
1756
+ if (_termsScanIsQuadratic(before.concat(spliced), multiline, budget)) return true;
1757
+ }
1758
+ return false; // the branches cover every path
1759
+ }
1760
+ // Stop at the first term that runs away. Past it a choice belongs to the
1761
+ // suffix, where the engine tries every branch of it at each step back
1762
+ // through the run — `[ab]+(?:a|b)` is not `[ab]+a` or `[ab]+b` but both at
1763
+ // once, which is what makes it the class `[ab]`. Reading the suffix is the
1764
+ // suffix rule's job, and it reads a choice as the cover it is.
1765
+ if (_isRunawayTerm(term)) break;
1766
+ }
1767
+ return _headRunsAway(terms, multiline, budget);
1768
+ }
1769
+
1770
+ // A term that can consume an unbounded amount, which is what makes an attempt
1771
+ // cost the length of what remains rather than a constant.
1772
+ function _isRunawayTerm(term) {
1773
+ if (term.node.type === "anchor" || term.node.type === "look") return false;
1774
+ var span = _maxLength(term.node);
1775
+ return span === Infinity || (term.max === Infinity && span > 0);
1776
+ }
1777
+
1778
+ // A `^` before anything is consumed means one attempt, whatever follows it.
1779
+ // Under `m` it matches at every line start instead, which is fewer positions
1780
+ // than characters but still grows with the input.
1781
+ function _pinnedToOnePosition(terms, multiline) {
1782
+ for (var i = 0; i < terms.length; i += 1) {
1783
+ // An assertion standing BEFORE the anchor is evaluated before the anchor
1784
+ // can refuse, so the anchor does not save it from being tried everywhere.
1785
+ // Whether it costs anything is the assertion rule's question, not this
1786
+ // one's — this one only stops answering.
1787
+ if (terms[i].node.type === "look") return false;
1788
+ if (terms[i].node.type === "anchor") {
1789
+ if (terms[i].node.edge === "start" && !_multilineAt(terms[i].node, multiline)) return true;
1790
+ continue;
1791
+ }
1792
+ return false;
1793
+ }
1794
+ return false;
1795
+ }
1796
+
1797
+ // Whether `^` means the start of the SUBJECT or the start of a line, where this
1798
+ // anchor stands. A modifier group turns it on for part of a pattern — inside
1799
+ // `(?m: ... )` the anchor matches at every line start whatever the pattern's own
1800
+ // flags say — so the answer comes from the flags in force at the anchor.
1801
+ function _multilineAt(node, fallback) {
1802
+ return typeof node.flags === "string" ? node.flags.indexOf("m") !== -1 : fallback;
1803
+ }
1804
+
1805
+ // The characters a negative lookahead rules out at its own position, or null
1806
+ // when it rules out no single character. Forbidding `a` forbids the character;
1807
+ // forbidding `ab` forbids only the pair, and leaves every `a` not followed by
1808
+ // a `b` exactly where it was.
1809
+ function _forbiddenHeadSet(body) {
1810
+ return body ? _soleSetOf(body) : null;
1811
+ }
1812
+
1813
+ // One mandatory character and nothing else asked for.
1814
+ function _soleSetOf(node) {
1815
+ if (node.type === "set") return node.set;
1816
+ if (node.type === "group") return _soleSetOf(node.body);
1817
+ // A group's body is an alternation, so parentheses alone must not change the
1818
+ // answer: `(?!(?:a))` forbids what `(?!a)` forbids, and a choice between
1819
+ // single characters forbids all of them at once.
1820
+ if (node.type === "alt") {
1821
+ var parts = [];
1822
+ for (var b = 0; b < node.branches.length; b += 1) {
1823
+ var branchSet = _soleSetOf(node.branches[b]);
1824
+ if (branchSet === null) return null;
1825
+ parts.push(branchSet);
1826
+ }
1827
+ return parts.length === 0 ? null : _unionSets(parts);
1828
+ }
1829
+ if (node.type !== "seq") return null;
1830
+ var found = null;
1831
+ for (var i = 0; i < node.terms.length; i += 1) {
1832
+ var t = node.terms[i];
1833
+ if (t.min !== 1) return null; // a prefix, not a character
1834
+ if (found !== null) return null; // more than one part is required
1835
+ found = _soleSetOf(t.node);
1836
+ if (found === null) return null;
1837
+ }
1838
+ return found;
1839
+ }
1840
+
1841
+ // A lookbehind's body, written the way it is matched — last part first. Only
1842
+ // the ORDER changes: a set reads the same from either side, and an anchor is
1843
+ // left alone because every anchor is already treated as somewhere an attempt
1844
+ // can fail.
1845
+ function _reversedForLookbehind(node) {
1846
+ if (!node) return node;
1847
+ if (node.type === "alt") {
1848
+ var branches = [];
1849
+ for (var b = 0; b < node.branches.length; b += 1) {
1850
+ branches.push(_reversedForLookbehind(node.branches[b]));
1851
+ }
1852
+ return { type: "alt", branches: branches };
1853
+ }
1854
+ if (node.type === "seq") {
1855
+ var terms = [];
1856
+ for (var i = node.terms.length - 1; i >= 0; i -= 1) {
1857
+ var t = node.terms[i];
1858
+ terms.push({ node: _reversedForLookbehind(t.node), min: t.min, max: t.max });
1859
+ }
1860
+ return { type: "seq", terms: terms };
1861
+ }
1862
+ if (node.type === "group") return { type: "group", body: _reversedForLookbehind(node.body) };
1863
+ return node;
1864
+ }
1865
+
1866
+ // Is the assertion reached from most starting positions? The same question the
1867
+ // run asks, and the same answer: only when one input can both match everything
1868
+ // mandatory before it and supply what its body needs to get going. `x(?=a+b)`
1869
+ // cannot — a subject of `x` reaches the assertion everywhere and gives the
1870
+ // `a+` nothing to eat, and a subject of `a` feeds the run but matches the `x`
1871
+ // nowhere — so its body runs a bounded number of times and the scan is linear.
1872
+ function _lookIsReachableEverywhere(terms, at, body) {
1873
+ var head = _firstSet(body);
1874
+ if (head === null) head = _allSet(body);
1875
+ // What holds a scan is judged on what the scan WALKS, which is neither the
1876
+ // character it starts on nor everything the assertion can match.
1877
+ var walks = _scanSetOfBody(body);
1878
+ for (var i = 0; i < at; i += 1) {
1879
+ var t = terms[i];
1880
+ if (t.node.type === "look") {
1881
+ // A positive lookAHEAD in front of this one tests the same position, so
1882
+ // it decides where this one is reached at all. `(?=x)(?=a+b)` runs its
1883
+ // `a+` only where an `x` stands, and there it stops at once. A negative
1884
+ // one forbids rather than requires, and a lookBEHIND speaks about the
1885
+ // text before the position, so neither narrows this.
1886
+ if (t.node.behind) {
1887
+ if (_lookbehindSeparates(t.node, walks)) return false;
1888
+ continue;
1889
+ }
1890
+ if (!t.node.body) continue;
1891
+ if (t.node.negated) {
1892
+ // A negative one narrows too, when what it forbids is a character
1893
+ // rather than a sequence: `(?!a)(?=a+b)` reaches the `a+` only where
1894
+ // there is no `a` for it. `(?!ab)` forbids the pair and leaves every
1895
+ // `a` that is not followed by `b`, so it rules nothing out here.
1896
+ var forbidden = _forbiddenHeadSet(t.node.body);
1897
+ if (forbidden !== null && _setIsSubsetOf(head, forbidden)) return false;
1898
+ continue;
1899
+ }
1900
+ var required = _firstSet(t.node.body);
1901
+ if (required !== null && !_setsIntersect(required, head)) return false;
1902
+ continue;
1903
+ }
1904
+ if (t.node.type === "anchor") {
1905
+ // What the assertion STARTS by consuming, the same set the direct form
1906
+ // is judged on. It is the run that reaches the next separator or reads
1907
+ // past it; what the assertion goes on to ask for afterwards is bounded
1908
+ // by wherever that run stopped.
1909
+ if (_anchorBoundsTheScan(t.node, walks)) return false;
1910
+ continue;
1911
+ }
1912
+ if (t.min < 1) continue; // optional — costs nothing
1913
+ if (!_setsIntersect(_allSet(t.node), head)) return false;
1914
+ }
1915
+ return true;
1916
+ }
1917
+
1918
+ // The characters an assertion's scan actually WALKS: those of its first
1919
+ // runaway part. Not the character it starts on — `(?=a[ax]*z)` starts on an `a`
1920
+ // and then walks over `x` as well, so an `x` in front of it separates nothing.
1921
+ // And not everything it can match — `(?=\w+\s+\d+)` walks only over `\w`, and
1922
+ // stops at the space, which is exactly why a word boundary holds it.
1923
+ function _scanSetOfBody(body) {
1924
+ var parts = [];
1925
+ if (body && body.type === "alt") {
1926
+ for (var b = 0; b < body.branches.length; b += 1) {
1927
+ var branch = body.branches[b];
1928
+ if (branch.type !== "seq") continue;
1929
+ var flat = _inlineForScan(branch.terms);
1930
+ for (var i = 0; i < flat.length; i += 1) {
1931
+ if (_isRunawayTerm(flat[i])) { parts.push(_allSet(flat[i].node)); break; }
1932
+ }
1933
+ }
1934
+ }
1935
+ // Nothing found is not the same as nothing there — a scan nested inside
1936
+ // another assertion consumes nothing that `_allSet` can see. So the answer is
1937
+ // everything, and no separator gets to claim it holds a scan this could not
1938
+ // read.
1939
+ return parts.length === 0 ? _anySet() : _unionSets(parts);
1940
+ }
1941
+
1942
+ // A positive lookBEHIND in front of a scan says what stands immediately before
1943
+ // every viable start. When the scan cannot eat that character, the starts are
1944
+ // separated by something it has to stop at, so the runs from them do not
1945
+ // overlap and their lengths add up to the subject rather than multiplying by
1946
+ // it: `(?<=x)a+b` is linear where `(?<=a)a+b` is quadratic.
1947
+ function _lookbehindSeparates(node, scanSet) {
1948
+ if (!node.behind || node.negated || !node.body) return false;
1949
+ // ANY position it insists on will do, not only the one nearest the start.
1950
+ // `(?<=xa)a+b` puts the `x` two characters back, and a scan of `a`s stops at
1951
+ // it just the same — every viable start still has one in front of it, so the
1952
+ // runs do not overlap.
1953
+ var positions = [];
1954
+ if (_flatSets(_reversedForLookbehind(node.body), positions, MAX_BEHIND_POSITIONS)) {
1955
+ for (var i = 0; i < positions.length; i += 1) {
1956
+ if (!_setsIntersect(positions[i], scanSet)) return true;
1957
+ }
1958
+ return false;
1959
+ }
1960
+ var before = _firstSet(_reversedForLookbehind(node.body));
1961
+ return before !== null && !_setsIntersect(before, scanSet);
1962
+ }
1963
+
1964
+ // Does an anchor standing in front of a scan hold it to a bounded number of
1965
+ // runs? Only two do, and for different reasons.
1966
+ //
1967
+ // `$` outside multiline succeeds at one place in the subject, so whatever
1968
+ // follows it runs once: `$(?=a+b)` is linear where `(?=a+b)` is quadratic.
1969
+ //
1970
+ // The others succeed far more often, and whether that matters depends on the
1971
+ // scan. Their firings are separated by a character of a particular kind — a
1972
+ // newline for a line anchor, a character of the other class for `\b` — so a
1973
+ // scan that cannot match across that separator gets no further than the next
1974
+ // one, and the number of firings and the distance between them trade off
1975
+ // exactly: `(?m)^a+b` and `\b\w+\s+\d+` are linear. A scan that CAN cross
1976
+ // reaches the end of the subject from every firing and stays quadratic:
1977
+ // `\b.*z` is not saved by its `\b`, nor `(?m)^[\s\S]*z` by its `^`.
1978
+ function _anchorBoundsTheScan(anchor, scanSet) {
1979
+ if (anchor.edge === "word") {
1980
+ // `\B` is the opposite: it succeeds everywhere EXCEPT the transitions, so
1981
+ // it fires all the way through a run instead of separating one from the
1982
+ // next and bounds nothing. `\B\w+z` scans from nearly every position.
1983
+ if (anchor.negated) return false;
1984
+ return _setIsSubsetOf(scanSet, _escapeSet("w")) ||
1985
+ _setIsSubsetOf(scanSet, _escapeSet("W"));
1986
+ }
1987
+ if (!_multilineAt(anchor, false)) return anchor.edge === "end";
1988
+ // Every line terminator, not just the newline. `^` under `m` fires after a
1989
+ // carriage return and after the two Unicode separators as well, so a scan
1990
+ // that stops only at `\n` reads straight past them: `/^[^\n]*z/m` is
1991
+ // quadratic on a subject of carriage returns.
1992
+ return !_setsIntersect(scanSet, _mkSet(LINE_TERMINATORS, false));
1993
+ }
1994
+
1995
+ function _headRunsAway(terms, multiline, budget) {
1996
+ var i = 0;
1997
+ for (; i < terms.length; i += 1) {
1998
+ if (terms[i].node.type === "look") continue;
1999
+ if (terms[i].node.type === "anchor") {
2000
+ if (terms[i].node.edge === "start" && !_multilineAt(terms[i].node, multiline)) return false;
2001
+ continue;
2002
+ }
2003
+ break;
2004
+ }
2005
+ // The runaway term need not be the FIRST one. A fixed atom in front costs an
2006
+ // attempt nothing — `aa+b` and `a.*b` pass their leading `a` in constant time
2007
+ // and then scan the whole remaining suffix before failing, exactly as `a+b`
2008
+ // does. Any position from here on can be the one that runs away.
2009
+ for (var r = i; r < terms.length; r += 1) {
2010
+ var term = terms[r];
2011
+ if (term.node.type === "anchor" || term.node.type === "look") continue;
2012
+ var reach = _maxLength(term.node);
2013
+ var runsAway = reach === Infinity || (term.max === Infinity && reach > 0);
2014
+ if (!runsAway) continue;
2015
+ if (!_runIsReachableEverywhere(terms, i, r)) continue;
2016
+ if (_canFailAfterRun(terms, r)) return true;
2017
+ // The failure can be INSIDE the term that runs away. `(?:a+b)+` has nothing
2018
+ // after it to fail on, and fails on its own `b` at every position all the
2019
+ // same, so the body is read as the pattern it is.
2020
+ //
2021
+ // Only while the term is MANDATORY. One that can be left out matches empty
2022
+ // and the attempt succeeds there and then, whatever its body would have
2023
+ // failed on: `(?:[a-z]+-)*` is linear.
2024
+ if (term.min >= 1 && term.node.type === "group" && term.node.body &&
2025
+ term.node.body.type === "alt") {
2026
+ for (var gb = 0; gb < term.node.body.branches.length; gb += 1) {
2027
+ if (_branchScanIsQuadratic(term.node.body.branches[gb], multiline, budget)) return true;
2028
+ }
2029
+ }
2030
+ }
2031
+ return false;
2032
+ }
2033
+
2034
+ // Can one input both match everything before the run AND feed the run? Only
2035
+ // then does the run get to walk the input from most starting positions.
2036
+ //
2037
+ // `aa+b` qualifies: a string of `a`s matches the leading `a` at every position
2038
+ // and the `a+` then eats the rest. `\.[a-f0-9]{8,}\.` does not: an all-dots
2039
+ // input matches the leading dot everywhere but the run cannot eat a dot, and a
2040
+ // hex input feeds the run but matches the leading dot nowhere. A mandatory
2041
+ // prefix over characters the run cannot consume bounds the scan.
2042
+ function _runIsReachableEverywhere(terms, from, runAt) {
2043
+ // What the run can BEGIN with, not everything it can match. The optional
2044
+ // build-metadata group of a version string can match letters, but it has to
2045
+ // start on `-` or `+`; a stream of version prefixes never supplies one where
2046
+ // the group begins, so the run never gets going and the scan stays linear.
2047
+ var runHead = _firstSet(terms[runAt].node);
2048
+ if (runHead === null) runHead = _allSet(terms[runAt].node);
2049
+ // From the start of the pattern, not from the first consuming term: an
2050
+ // anchor in front of the run is one of the things that can bound it.
2051
+ for (var i = 0; i < runAt; i += 1) {
2052
+ var t = terms[i];
2053
+ if (t.node.type === "look") {
2054
+ if (_lookbehindSeparates(t.node, _allSet(terms[runAt].node))) return false;
2055
+ continue;
2056
+ }
2057
+ if (t.node.type === "anchor") {
2058
+ if (_anchorBoundsTheScan(t.node, _allSet(terms[runAt].node))) return false;
2059
+ continue;
2060
+ }
2061
+ if (t.min < 1) continue; // optional — costs an attempt nothing
2062
+ if (!_setsIntersect(_allSet(t.node), runHead)) return false;
2063
+ }
2064
+ return true;
2065
+ }
2066
+
2067
+ // Is there something after the runaway term that an attempt can fail on?
2068
+ //
2069
+ // Not every mandatory suffix qualifies. `a+a` and `\w+\w` always succeed on the
2070
+ // first attempt wherever the run is long enough, and where it is not, there is
2071
+ // nothing for the run to scan — the suffix asks only for a character the run
2072
+ // itself has been eating, so it can always hand one back. What makes the scan
2073
+ // quadratic is a suffix the run CANNOT satisfy out of its own characters, so
2074
+ // every attempt walks the run to its end and then fails.
2075
+ //
2076
+ // An assertion counts too. `a+$` and `a+(?=b)` consume nothing, but `$` fails
2077
+ // on any input with a trailing character the run did not eat, and the engine
2078
+ // then repeats that walk from every start position.
2079
+ function _canFailAfterRun(terms, from) {
2080
+ var reach = _allSet(terms[from].node);
2081
+ // The fewest characters the run can leave behind an endpoint. `a*` can leave
2082
+ // none, `a+` one, `(?:ab)+` two — which is what a negative lookbehind has to
2083
+ // outreach before it can be settled by failing to match. Nothing may stand in
2084
+ // front of the run for this to hold, not even something zero-width: a `\B`
2085
+ // there refuses position zero, so the first attempt to reach the run begins
2086
+ // somewhere with characters behind it, and `\Ba*(?<!a)` walks the rest of the
2087
+ // subject from every one of them.
2088
+ var shortestRun = _minLength(terms[from].node) * terms[from].min;
2089
+ for (var j = from + 1; j < terms.length; j += 1) {
2090
+ var later = terms[j];
2091
+ if (later.node.type === "look") {
2092
+ // A lookaround the run can settle out of its own characters is not a
2093
+ // failure point. A POSITIVE one has to be satisfiable in full from what
2094
+ // the run eats: `a+(?=a)` hands one `a` back and the first viable attempt
2095
+ // completes, exactly as `a+a` does, while `a+(?=b)` never can. Its first
2096
+ // character alone does not answer that — `a+(?=a[^a])` starts on ground
2097
+ // the run covers and then asks for something it never supplies.
2098
+ //
2099
+ // A NEGATIVE one is settled by the assertion failing, so what matters is
2100
+ // whether it can even begin — and that answer depends on WHICH WAY it
2101
+ // looks, because the two directions read opposite ground.
2102
+ //
2103
+ // Looking AHEAD, it reads what the run did not eat. When everything it
2104
+ // forbids is something the run eats — `a+(?!a)` — a greedy run stops at a
2105
+ // character it could not eat, so the assertion holds on the first try.
2106
+ // When it forbids nothing the run eats — `a+(?!b)` — one handed-back
2107
+ // character is enough. Only the partial overlap fails repeatedly:
2108
+ // `a+(?![ab])` meets the forbidden `b` past the run and then walks back
2109
+ // through a run of forbidden `a`s, refusing at every step.
2110
+ //
2111
+ // Looking BEHIND, it reads the characters the run just ate, so forbidding
2112
+ // them is the WORST case rather than the safe one: `a+(?<!a)` refuses at
2113
+ // the end of the greedy run, refuses again at every character it hands
2114
+ // back, and does the whole walk again from every later start — quadratic,
2115
+ // and it was being waved through by the lookahead's own argument. Only
2116
+ // forbidding something the run never eats — `a+(?<!b)` — settles at once.
2117
+ var body = later.node.body;
2118
+ if (body) {
2119
+ if (later.node.negated && later.node.behind) {
2120
+ // A negative lookbehind is settled by FAILING to match, and it reads
2121
+ // the characters the run just ate — so it refuses at every endpoint
2122
+ // exactly when the run's own characters can spell the whole of it.
2123
+ // `a+(?<!a)` is that case, and quadratic. `a+(?<!ab)` and `a+(?<!ba)`
2124
+ // are not: neither can be spelled out of `a`s, so the assertion holds
2125
+ // where the greedy run stops and the first attempt completes. The
2126
+ // whole body has to be weighed and not just the character beside the
2127
+ // position — `a+(?<!.a)` and `a+(?<![ab]a)` can be spelled from the
2128
+ // run as well, and both walk the subject again from every start.
2129
+ // Where both the assertion and the run are fixed shapes, they are
2130
+ // read against each other position by position, nearest the endpoint
2131
+ // first. That is what distinguishes `(?:ab)+(?<!ab)`, which matches at
2132
+ // every endpoint and is quadratic, from `(?:ab)+(?<!bb)`, which needs
2133
+ // a `b` where the run always leaves an `a` and so can never match at
2134
+ // one — although both are spelled entirely out of characters the run
2135
+ // eats, which is all a set-membership test can see.
2136
+ var behindSeq = [];
2137
+ if (_flatSets(_reversedForLookbehind(body), behindSeq, MAX_BEHIND_POSITIONS) &&
2138
+ behindSeq.length > 0) {
2139
+ var runPositions = _positionsBehindRun(terms[from].node, behindSeq.length);
2140
+ if (runPositions !== null) {
2141
+ var canMatchThere = true;
2142
+ for (var k = 0; k < behindSeq.length; k += 1) {
2143
+ if (!_setsIntersect(behindSeq[k], runPositions[k])) canMatchThere = false;
2144
+ }
2145
+ if (!canMatchThere) continue; // holds where the run stops
2146
+ // The run also hands characters back, down to its shortest, and
2147
+ // reaching past THAT the assertion meets the character in front of
2148
+ // the run — which the run demonstrably did not eat, or it would
2149
+ // have started there. An assertion needing a run character in that
2150
+ // position can never match at the short endpoint, so it holds and
2151
+ // the search ends after one walk: `a+(?<!aa)` settles on the single
2152
+ // character the run owes and `(?:ab)+(?<!abab)` on its one
2153
+ // repetition, while `a+(?<!.a)` asks for anything at all there and
2154
+ // gets it. Only where nothing stands in front of the run and the
2155
+ // assertion follows it immediately — the `(?!a)` in
2156
+ // `a*(?!a)(?<!a)` refuses the short endpoint and the walk repeats
2157
+ // from every position.
2158
+ //
2159
+ // That argument holds only for a run that eats ONE character at a
2160
+ // time, because only such a run would have started one position
2161
+ // earlier. `(?:ab)+` advances two at a time and begins at each
2162
+ // `a`, so the character in front of it can perfectly well be a `b`
2163
+ // it also eats — and `(?:ab)+(?<!bab)` finds exactly that and
2164
+ // fails at every repetition.
2165
+ if (from === 0 && j === from + 1 &&
2166
+ _minLength(terms[from].node) === 1 && _maxLength(terms[from].node) === 1 &&
2167
+ behindSeq.length > shortestRun &&
2168
+ _setIsSubsetOf(behindSeq[shortestRun], reach)) continue;
2169
+ }
2170
+ }
2171
+ if (!_spellableFrom(body, reach)) continue;
2172
+ } else if (later.node.negated) {
2173
+ var starts = _firstSet(body);
2174
+ if (starts !== null &&
2175
+ (_setIsSubsetOf(starts, reach) || !_setsIntersect(reach, starts))) continue;
2176
+ } else if (_alwaysSatisfiedBy(body, reach)) continue;
2177
+ }
2178
+ return true;
2179
+ }
2180
+ if (later.node.type === "anchor") return true; // `$` fails on a trailing extra
2181
+ if (later.min < 1) continue; // optional — never the failure
2182
+ // Everything the run can eat would also satisfy this, so wherever the run
2183
+ // matched enough characters the suffix is already met and the first
2184
+ // attempt succeeds: `a+a` and `\w+\w` are linear. The direction matters —
2185
+ // `.*b` has a suffix INSIDE the run's set and is still quadratic, because
2186
+ // an input of nothing but non-`b` characters feeds the run and then fails.
2187
+ if (_alwaysSatisfiedBy(later.node, reach)) continue;
2188
+ return true;
2189
+ }
2190
+ return false;
2191
+ }
2192
+
2193
+ // A cap on how far back a lookbehind is read position by position. Far above
2194
+ // any assertion an operator writes; past it the coarser test takes over.
2195
+ var MAX_BEHIND_POSITIONS = 64;
2196
+
2197
+ // The node read backwards as a flat run of single-character sets, appended to
2198
+ // `out`. Only a fixed shape can be read this way — a plain sequence of
2199
+ // characters, classes and groups of them. An alternation of more than one
2200
+ // branch, a variable count, or anything zero-width returns false, and the
2201
+ // caller falls back to what it can prove from the run's characters alone.
2202
+ function _flatSets(node, out, limit) {
2203
+ if (out.length >= limit) return true;
2204
+ if (node.type === "set") { out.push(node.set); return true; }
2205
+ if (node.type === "group") return _flatSets(node.body, out, limit);
2206
+ if (node.type === "alt") {
2207
+ if (node.branches.length !== 1) return false;
2208
+ return _flatSets(node.branches[0], out, limit);
2209
+ }
2210
+ if (node.type !== "seq") return false;
2211
+ for (var i = 0; i < node.terms.length; i += 1) {
2212
+ var t = node.terms[i];
2213
+ if (t.node.type === "anchor" || t.node.type === "look") return false;
2214
+ if (t.min !== t.max) return false; // not a fixed shape
2215
+ for (var rep = 0; rep < t.min; rep += 1) {
2216
+ if (!_flatSets(t.node, out, limit)) return false;
2217
+ if (out.length >= limit) return true;
2218
+ }
2219
+ }
2220
+ return true;
2221
+ }
2222
+
2223
+ // The characters standing behind an endpoint of a run, nearest first. A run
2224
+ // repeats whole copies of its body, so they are the body read backwards over
2225
+ // and over: `(?:ab)+` leaves a `b`, then an `a`, then a `b`, behind every one of
2226
+ // its endpoints — which is why `(?<!bb)` can never match at one, however many
2227
+ // `b`s the run has eaten in total.
2228
+ function _positionsBehindRun(runNode, count) {
2229
+ var reversed = _reversedForLookbehind(runNode);
2230
+ var out = [];
2231
+ while (out.length < count) {
2232
+ var before = out.length;
2233
+ if (!_flatSets(reversed, out, count)) return null;
2234
+ if (out.length === before) return null; // consumes nothing to repeat
2235
+ }
2236
+ return out;
2237
+ }
2238
+
2239
+ // Can this node be spelled out of characters the run eats? Not whether it MUST
2240
+ // be — whether it CAN. A negative lookbehind the run's own characters can spell
2241
+ // refuses at every endpoint of the run, so the attempt walks the run and fails,
2242
+ // and does it again from every later start. One that needs a character the run
2243
+ // never eats cannot match where the run stops, so the assertion holds there and
2244
+ // the first attempt completes.
2245
+ //
2246
+ // Asking whether EVERY character of the run satisfies the body is the wrong
2247
+ // question and answers it backwards: `[ab]+(?<!a)` has a `b` in the run that
2248
+ // does not satisfy the `a`, and is quadratic all the same, because the run can
2249
+ // end on an `a`.
2250
+ function _spellableFrom(node, reach) {
2251
+ if (!node) return true;
2252
+ if (node.type === "alt") {
2253
+ for (var brIndex = 0; brIndex < node.branches.length; brIndex += 1) {
2254
+ if (_spellableFrom(node.branches[brIndex], reach)) return true;
2255
+ }
2256
+ return false;
2257
+ }
2258
+ if (node.type === "seq") {
2259
+ for (var termIndex = 0; termIndex < node.terms.length; termIndex += 1) {
2260
+ var term = node.terms[termIndex];
2261
+ if (term.min < 1) continue; // can be left out entirely
2262
+ if (!_spellableFrom(term.node, reach)) return false;
2263
+ }
2264
+ return true;
2265
+ }
2266
+ if (node.type === "group") return _spellableFrom(node.body, reach);
2267
+ if (node.type === "set") return _setsIntersect(node.set, reach);
2268
+ if (node.type === "anchor") {
2269
+ // A `^` or `$` INSIDE the assertion pins it to one end of the subject, and
2270
+ // the endpoints of a run are neither: `a+(?<!a$)` can only match where the
2271
+ // run ends at the end of the input, so one backtrack settles it and the
2272
+ // scan stays linear. A word boundary is not so easily placed — `a+(?<!a\B)`
2273
+ // holds between two word characters, which is every endpoint inside a run
2274
+ // of them, and is quadratic — so it stays unproven.
2275
+ return node.edge === "word" || node.edge === "assertion";
2276
+ }
2277
+ if (node.type === "look") return true; // consumes nothing, unproven
2278
+ return true; // opaque — never proven away
2279
+ }
2280
+
2281
+ // Every member of `inner` is also a member of `outer`.
2282
+ function _setIsSubsetOf(inner, outer) {
2283
+ if (inner.any) return !!outer.any;
2284
+ if (outer.any) return true;
2285
+ if (!inner.negated && !outer.negated) {
2286
+ var missing = false;
2287
+ inner.chars.forEach(function (c) { if (!outer.chars.has(c)) missing = true; });
2288
+ return !missing;
2289
+ }
2290
+ if (inner.negated && outer.negated) {
2291
+ // ¬A ⊆ ¬B iff B ⊆ A.
2292
+ var uncovered = false;
2293
+ outer.chars.forEach(function (c) { if (!inner.chars.has(c)) uncovered = true; });
2294
+ return !uncovered;
2295
+ }
2296
+ if (!inner.negated && outer.negated) {
2297
+ var excluded = false;
2298
+ inner.chars.forEach(function (c) { if (outer.chars.has(c)) excluded = true; });
2299
+ return !excluded;
2300
+ }
2301
+ return false; // ¬A ⊆ B — never, for any real alphabet
2302
+ }
2303
+
2304
+ // The two ambiguity findings, from one parse of the pattern.
2305
+ //
2306
+ // A pattern that fails to parse HERE but compiles as a RegExp is a gap in this
2307
+ // parser, not a safe pattern, so it is reported rather than waved through — the
2308
+ // alternative is a construct nobody thought of becoming a way past the guard.
2309
+ // Input that is not a regex at all (a glob fragment, which this same gate
2310
+ // screens) has no repetition structure to judge and is left to the detectors
2311
+ // that do read it.
2312
+ function _ambiguityFindings(src, flags) {
2313
+ var out = { nested: false, alternation: false, lookaround: false, unanchored: false };
2314
+ var text = String(src);
2315
+ var ast = _parsePattern(text, typeof flags === "string" ? flags : "",
2316
+ { left: ANALYSIS_BUDGET });
2317
+ if (ast === null) {
2318
+ // Asked WITH the flags it was given, because some syntax exists only under
2319
+ // one of them: `[[a-z]--[x]]` is a class under `v` and a syntax error
2320
+ // without it. Asking without the flags called such a pattern "not a regex
2321
+ // at all" and returned every finding false, so a quadratic pattern using
2322
+ // any of that syntax walked straight past this gate.
2323
+ var compiles = true;
2324
+ try { RegExp(text, typeof flags === "string" ? flags : ""); }
2325
+ catch (_e) { compiles = false; }
2326
+ if (compiles) {
2327
+ out.nested = true;
2328
+ out.lookaround = true;
2329
+ out.unanchored = true; // unread, so unproven
2330
+ }
2331
+ return out;
2332
+ }
2333
+ if (_declinesOnFlags(flags)) {
2334
+ // Suppressions are off, so any repetition of something that varies counts.
2335
+ _findAmbiguityUnproven(ast, out);
2336
+ out.unanchored = _unanchoredScanIsQuadratic(ast, flags);
2337
+ return out;
2338
+ }
2339
+ _findAmbiguity(ast, out);
2340
+ out.unanchored = _unanchoredScanIsQuadratic(ast, flags);
2341
+ return out;
2342
+ }
2343
+
2344
+ // The same walk with every proof withheld.
2345
+ function _findAmbiguityUnproven(node, out) {
2346
+ if (node === null || typeof node !== "object") return;
2347
+ if (node.type === "alt") {
2348
+ for (var b = 0; b < node.branches.length; b += 1) _findAmbiguityUnproven(node.branches[b], out);
2349
+ return;
2350
+ }
2351
+ if (node.type === "group") { _findAmbiguityUnproven(node.body, out); return; }
2352
+ if (node.type === "look") {
2353
+ if (node.body) {
2354
+ var innerUnproven = { nested: false, alternation: false, lookaround: false };
2355
+ _findAmbiguityUnproven(node.body, innerUnproven);
2356
+ if (innerUnproven.nested) out.nested = true;
2357
+ if (innerUnproven.alternation) out.alternation = true;
2358
+ if (innerUnproven.nested || innerUnproven.alternation) out.lookaround = true;
2359
+ }
2360
+ return;
2361
+ }
2362
+ if (node.type !== "seq") return;
2363
+ for (var i = 0; i < node.terms.length; i += 1) {
2364
+ var term = node.terms[i];
2365
+ _findAmbiguityUnproven(term.node, out);
2366
+ if (term.max <= 1) continue;
2367
+ var body = term.node.type === "group" ? term.node.body : term.node;
2368
+ var isAlternation = body.type === "alt" && body.branches.length > 1;
2369
+ if (!_isVariableLength(term.node) && !isAlternation) continue;
2370
+ if (isAlternation) out.alternation = true;
2371
+ else out.nested = true;
2372
+ }
2373
+ }
185
2374
 
186
2375
  function _detectIssues(input, opts) {
187
2376
  var pre = gateContract.detectStringInput(input, opts, { name: "regex", noun: "regex pattern", cap: { bytes: opts.maxPatternBytes, kind: "pattern-cap", snippet: "regex pattern exceeds maxPatternBytes " + opts.maxPatternBytes } });
188
2377
  if (pre.done) return pre.issues;
189
2378
  var issues = pre.issues;
190
2379
 
191
- if (opts.nestedQuantPolicy !== "allow" && _hasNestedQuantifier(input)) {
2380
+ var ambiguity = (opts.nestedQuantPolicy !== "allow" ||
2381
+ opts.alternationQuantPolicy !== "allow" ||
2382
+ opts.lookaroundQuantPolicy !== "allow" ||
2383
+ opts.unanchoredScanPolicy !== "allow")
2384
+ ? _ambiguityFindings(input, opts.regexFlags)
2385
+ : { nested: false, alternation: false, lookaround: false, unanchored: false };
2386
+
2387
+ if (opts.nestedQuantPolicy !== "allow" && ambiguity.nested) {
192
2388
  issues.push({
193
2389
  kind: "nested-quantifier", severity: "critical",
194
2390
  ruleId: "regex.nested-quantifier",
@@ -198,43 +2394,61 @@ function _detectIssues(input, opts) {
198
2394
  });
199
2395
  }
200
2396
 
201
- if (opts.alternationQuantPolicy !== "allow" &&
202
- ALTERNATION_QUANT_RE.test(input)) { // allow:regex-no-length-cap input bounded by maxPatternBytes
2397
+
2398
+ if (opts.alternationQuantPolicy !== "allow" && ambiguity.alternation) {
203
2399
  issues.push({
204
2400
  kind: "alternation-quantifier",
205
2401
  severity: opts.alternationQuantPolicy === "reject" ? "high" : "warn",
206
2402
  ruleId: "regex.alternation-quantifier",
207
- snippet: "pattern contains alternation-with-quantifier shape " +
208
- "(e.g. `(a|b)+`) — alternation overlap may amplify " +
209
- "search paths",
2403
+ snippet: "pattern contains alternation-with-quantifier shape whose " +
2404
+ "branches can match at the same position (e.g. `(a|a)*`, " +
2405
+ "`(\\d|\\d{2})*`) — the overlap amplifies search paths. " +
2406
+ "Branches that cannot start on the same character are " +
2407
+ "accepted; give each one a distinct leading character",
2408
+ });
2409
+ }
2410
+
2411
+ if (opts.unanchoredScanPolicy !== "allow" && ambiguity.unanchored) {
2412
+ issues.push({
2413
+ kind: "unanchored-scan",
2414
+ severity: opts.unanchoredScanPolicy === "reject" ? "high" : "warn",
2415
+ ruleId: "regex.unanchored-scan",
2416
+ snippet: "pattern is not anchored at the start and can consume an " +
2417
+ "unbounded amount before something that must match (e.g. " +
2418
+ "`a+b`, `(\\w+)\\s+(\\d+)`) — it is retried at every position " +
2419
+ "in the subject and each attempt walks the rest of it, which " +
2420
+ "is quadratic in the input; anchor it with `^`, make it " +
2421
+ "sticky, or bound the subject length",
210
2422
  });
211
2423
  }
212
2424
 
213
- if (opts.lookaroundQuantPolicy !== "allow" &&
214
- LOOKAROUND_QUANT_RE.test(input)) { // allow:regex-no-length-cap — input bounded by maxPatternBytes
2425
+ if (opts.lookaroundQuantPolicy !== "allow" && ambiguity.lookaround) {
215
2426
  issues.push({
216
2427
  kind: "lookaround-quantifier",
217
2428
  severity: opts.lookaroundQuantPolicy === "reject" ? "high" : "warn",
218
2429
  ruleId: "regex.lookaround-quantifier",
219
- snippet: "pattern contains quantifier inside lookaround " +
220
- "(`(?=.*+)`) — catastrophic in some engines",
2430
+ snippet: "pattern contains a repetition inside a lookaround whose parts " +
2431
+ "compete for the same input (e.g. `(?=(a|a)+)`) — the engine " +
2432
+ "backtracks inside an assertion exactly as it does outside one",
221
2433
  });
222
2434
  }
223
2435
 
224
2436
  if (opts.boundedRepeatPolicy !== "allow") {
225
- BOUNDED_REPEAT_RE.lastIndex = 0;
226
- var match;
227
- while ((match = BOUNDED_REPEAT_RE.exec(input)) !== null) { // allow:regex-no-length-cap — input bounded by maxPatternBytes
228
- var lower = parseInt(match[1], 10); // base-10 radix
229
- var upper = match[2] === undefined ? lower :
230
- match[2] === "" ? Infinity : parseInt(match[2], 10); // base-10 radix
2437
+ for (var bi = 0; bi < input.length; bi += 1) {
2438
+ if (input.charAt(bi) !== "{") continue;
2439
+ var braces = _scanBraces(input, bi);
2440
+ if (braces === null) continue;
2441
+ var lower = braces.min;
2442
+ var upper = braces.max;
2443
+ var written = input.slice(bi, braces.end);
2444
+ bi = braces.end - 1; // resume past what was read
231
2445
  var ceiling = (upper === Infinity || upper > lower) ? upper : lower;
232
2446
  if (ceiling > opts.maxBoundedRepeat) {
233
2447
  issues.push({
234
2448
  kind: "bounded-repeat-cap",
235
2449
  severity: opts.boundedRepeatPolicy === "reject" ? "high" : "warn",
236
2450
  ruleId: "regex.bounded-repeat-cap",
237
- snippet: "bounded-repeat `" + match[0] + "` upper bound " +
2451
+ snippet: "bounded-repeat `" + written + "` upper bound " +
238
2452
  (ceiling === Infinity ? "unbounded" : ceiling) +
239
2453
  " exceeds maxBoundedRepeat " + opts.maxBoundedRepeat,
240
2454
  });
@@ -300,25 +2514,17 @@ function _detectNestedExtglob(input, opts, issues) {
300
2514
  // patterns like `a*(b+(c))` where the heads are quantifier
301
2515
  // groupings, not extglob.
302
2516
  if (opts.inputKind !== "glob") return;
303
- // Collect extglob head positions via match() read-only scan.
2517
+ // Where each extglob head stands, read straight off the input. Matching for
2518
+ // them and then hunting for the offsets of what was matched did the same walk
2519
+ // twice, and did the first half of it with a pattern.
304
2520
  var heads = [];
305
- var allHeads = input.match(EXTGLOB_HEAD_RE); // allow:regex-no-length-cap input bounded by maxPatternBytes
306
- if (allHeads === null || allHeads.length < 2) return;
307
- // Locate each head index manually (match returns substrings, not idx).
308
- var scanFrom = 0;
309
- for (var hh = 0; hh < allHeads.length; hh += 1) {
310
- var ch0 = allHeads[hh].charAt(0);
311
- var idx = scanFrom;
312
- while (idx < input.length - 1) {
313
- var c0 = input.charAt(idx);
314
- var c1 = input.charAt(idx + 1);
315
- if (c1 === "(" && c0 === ch0) break;
316
- idx += 1;
317
- }
318
- heads.push(idx);
319
- scanFrom = idx + 1;
2521
+ for (var hh = 0; hh + 1 < input.length; hh += 1) {
2522
+ if (input.charAt(hh + 1) !== "(") continue;
2523
+ if (EXTGLOB_HEADS.indexOf(input.charAt(hh)) === -1) continue;
2524
+ heads.push(hh);
320
2525
  if (heads.length > 1024) break; // head-count safety cap
321
2526
  }
2527
+ if (heads.length < 2) return;
322
2528
  var nested = false;
323
2529
  for (var hi = 0; hi < heads.length && !nested; hi += 1) {
324
2530
  var headStart = heads[hi];
@@ -522,10 +2728,21 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("^[a-z]+$", "(a+)+b")
522
2728
  * @signature b.guardRegex.assertSafe(input, label?, ErrorClass?, code?, opts?)
523
2729
  * @since 0.15.39
524
2730
  * @status stable
525
- * @related b.guardRegex.sanitize, b.guardRegex.validate
2731
+ * @related b.guardRegex.sanitize, b.guardRegex.validate, b.regexLinear.compile
526
2732
  *
527
2733
  * Screen an already-compiled <code>RegExp</code> (or a raw pattern string) for
528
2734
  * catastrophic-backtracking (ReDoS) shapes, throwing if the pattern is unsafe.
2735
+ *
2736
+ * Screening asks whether a pattern LOOKS dangerous, which is a different
2737
+ * question from running it safely. If what you need is to match an operator's
2738
+ * pattern against request data, <code>b.regexLinear.compile</code> runs it in
2739
+ * time proportional to the subject whatever the pattern is, and needs no
2740
+ * screening at all — no shape it accepts can be made to backtrack. Screening is
2741
+ * for the cases where the platform engine must do the matching: a pattern handed
2742
+ * to a library, to <code>String.prototype.replace</code>, or to anything else
2743
+ * that takes a <code>RegExp</code>. The two are complements, and the runner
2744
+ * names the constructs it cannot take (backreferences, lookaround) so the choice
2745
+ * between them is visible rather than implied.
529
2746
  * This is the config-time guard for request-lifecycle code that matches an
530
2747
  * operator-supplied regex against attacker-controlled input (User-Agent,
531
2748
  * Origin, request path, form field, HELO) — an accidentally-catastrophic
@@ -543,9 +2760,38 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("^[a-z]+$", "(a+)+b")
543
2760
  * linear, not exponential, and legitimate patterns (e.g. a hex hash of 8+
544
2761
  * digits) use them. Pass an explicit <code>opts</code> to override.
545
2762
  *
2763
+ * <b>What it can and cannot tell you.</b> Two costs decide what a match against
2764
+ * hostile input is worth, and the analysis reaches both, but by different
2765
+ * means and with different confidence.
2766
+ *
2767
+ * The first is what one match attempt costs — whether a repetition's parts
2768
+ * compete for the same characters, so the engine explores many ways to divide
2769
+ * the input between them. That is the backtracking analysis, and it is
2770
+ * conservative by construction: a pattern it cannot characterise is refused
2771
+ * rather than waved through. It is not a decision procedure, though. It proves
2772
+ * unambiguity for the shapes it knows and refuses the rest, so a pattern that
2773
+ * is in fact linear can still be turned away — the refusal names the shape, and
2774
+ * rewriting to a form it can prove (a distinct leading character per branch, a
2775
+ * separator no other part matches) is usually a small edit.
2776
+ *
2777
+ * The second is how many attempts there are. An unanchored pattern is retried
2778
+ * at every position in the subject, and when it can consume an unbounded amount
2779
+ * before reaching something that must match, each attempt walks the rest of the
2780
+ * input — quadratic overall, with no ambiguity anywhere for the first analysis
2781
+ * to find. That is reported separately as <code>regex.unanchored-scan</code>,
2782
+ * under <code>unanchoredScanPolicy</code>, so an operator who bounds the subject
2783
+ * length instead can turn it off without giving up the backtracking classes.
2784
+ * Anchoring the pattern, or compiling it sticky, removes the cost outright.
2785
+ *
2786
+ * Neither answers the question a running system actually asks, which is how
2787
+ * long THIS match will take on THIS input. Screening the pattern removes the
2788
+ * shapes whose cost explodes; it does not make an unbounded subject safe. Where
2789
+ * the input is attacker-controlled, cap its length as well.
2790
+ *
546
2791
  * @opts
547
- * profile: string, // guardRegex profile (default: "strict")
548
- * boundedRepeatPolicy: string, // default: "allow" (large bounded repeats are linear)
2792
+ * profile: string, // guardRegex profile (default: "strict")
2793
+ * boundedRepeatPolicy: string, // default: "allow" (large bounded repeats are linear)
2794
+ * unanchoredScanPolicy: string, // "reject" at strict, "audit" at balanced, "allow" at permissive
549
2795
  *
550
2796
  * @example
551
2797
  * b.guardRegex.assertSafe(/^[a-z]+$/); // ok — returns the RegExp
@@ -555,6 +2801,16 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("^[a-z]+$", "(a+)+b")
555
2801
  */
556
2802
  function assertSafe(input, label, ErrorClass, code, opts) {
557
2803
  var source = (input instanceof RegExp) ? input.source : input;
2804
+ // The flags decide what the source means. Screening `.source` alone reads
2805
+ // `(a|A)+` as two disjoint branches when under `i` the engine sees one
2806
+ // branch twice — the exact overlap the alternation rule exists to catch. A
2807
+ // RegExp carries its flags, so they travel with it; a caller screening a
2808
+ // raw string that they will later compile case-insensitively passes
2809
+ // `regexFlags` themselves.
2810
+ if (input instanceof RegExp && (!opts || opts.regexFlags === undefined)) {
2811
+ opts = Object.assign({ profile: "strict", boundedRepeatPolicy: "allow" },
2812
+ opts || {}, { regexFlags: input.flags });
2813
+ }
558
2814
  try {
559
2815
  // Screen the catastrophic-backtracking classes (nested / alternation /
560
2816
  // lookaround quantifiers — held at every profile) but allow large bounded