@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.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +3 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/referrals.js +10 -6
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +21 -23
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +137 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- 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|
|
|
25
|
-
* amplifies search paths
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
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
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 === "
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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|
|
|
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
|
|
220
|
-
"(`(
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
var
|
|
230
|
-
|
|
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 `" +
|
|
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
|
-
//
|
|
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
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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:
|
|
548
|
-
* boundedRepeatPolicy:
|
|
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
|