vinext 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/build/client-build-config.d.ts +64 -3
- package/dist/build/client-build-config.js +131 -4
- package/dist/build/client-reference-groups.d.ts +163 -0
- package/dist/build/client-reference-groups.js +534 -0
- package/dist/build/module-dependency-cache.d.ts +31 -0
- package/dist/build/module-dependency-cache.js +51 -1
- package/dist/build/next-client-runtime-manifests.d.ts +10 -1
- package/dist/build/next-client-runtime-manifests.js +19 -4
- package/dist/build/nitro-public-files.d.ts +24 -0
- package/dist/build/nitro-public-files.js +60 -0
- package/dist/build/pages-client-assets-module.js +1 -1
- package/dist/build/prerender-paths.js +5 -2
- package/dist/build/server-actions-encryption-key.d.ts +23 -0
- package/dist/build/server-actions-encryption-key.js +101 -0
- package/dist/build/ssr-manifest.d.ts +3 -0
- package/dist/build/ssr-manifest.js +10 -1
- package/dist/client/client-rewrite-params.d.ts +9 -0
- package/dist/client/client-rewrite-params.js +20 -0
- package/dist/client/dev-error-overlay.js +2 -1
- package/dist/client/navigation-runtime.d.ts +25 -2
- package/dist/client/navigation-runtime.js +42 -11
- package/dist/client/vinext-next-data.d.ts +0 -1
- package/dist/config/config-matchers.d.ts +15 -40
- package/dist/config/config-matchers.js +183 -255
- package/dist/config/config-source.d.ts +34 -0
- package/dist/config/config-source.js +141 -0
- package/dist/config/header-params.d.ts +29 -0
- package/dist/config/header-params.js +106 -0
- package/dist/config/next-config.d.ts +7 -5
- package/dist/config/next-config.js +120 -7
- package/dist/entries/app-browser-entry.d.ts +14 -1
- package/dist/entries/app-browser-entry.js +26 -36
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +92 -57
- package/dist/entries/app-rsc-manifest.d.ts +10 -0
- package/dist/entries/app-rsc-manifest.js +20 -1
- package/dist/entries/app-ssr-entry.d.ts +4 -1
- package/dist/entries/app-ssr-entry.js +9 -2
- package/dist/entries/pages-client-entry.d.ts +1 -0
- package/dist/entries/pages-client-entry.js +6 -6
- package/dist/entries/pages-server-entry.js +17 -14
- package/dist/index.d.ts +11 -0
- package/dist/index.js +239 -83
- package/dist/init.js +17 -0
- package/dist/plugins/optimize-imports.d.ts +25 -2
- package/dist/plugins/optimize-imports.js +106 -20
- package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
- package/dist/plugins/rsc-client-reference-loaders.js +336 -18
- package/dist/plugins/use-cache-callable.d.ts +5 -0
- package/dist/plugins/use-cache-callable.js +30 -8
- package/dist/plugins/worker-image-imports.d.ts +1 -0
- package/dist/plugins/worker-image-imports.js +1 -1
- package/dist/routing/app-route-graph.d.ts +1 -1
- package/dist/routing/browser-route-manifest.d.ts +43 -0
- package/dist/routing/browser-route-manifest.js +195 -0
- package/dist/routing/hashed-route-patterns.d.ts +4 -0
- package/dist/routing/hashed-route-patterns.js +22 -0
- package/dist/routing/optimistic-route-trie.d.ts +27 -0
- package/dist/routing/optimistic-route-trie.js +102 -0
- package/dist/routing/route-validation.d.ts +6 -1
- package/dist/routing/route-validation.js +26 -10
- package/dist/server/app-action-forwarding.d.ts +8 -0
- package/dist/server/app-action-forwarding.js +15 -1
- package/dist/server/app-browser-entry.js +147 -60
- package/dist/server/app-browser-error.d.ts +3 -1
- package/dist/server/app-browser-error.js +7 -2
- package/dist/server/app-browser-interception-context.d.ts +2 -2
- package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
- package/dist/server/app-browser-mpa-navigation.js +6 -5
- package/dist/server/app-browser-navigation-controller.d.ts +17 -6
- package/dist/server/app-browser-navigation-controller.js +88 -26
- package/dist/server/app-browser-state.d.ts +2 -2
- package/dist/server/app-browser-visible-commit.d.ts +4 -4
- package/dist/server/app-client-reference-preloader.d.ts +23 -0
- package/dist/server/app-client-reference-preloader.js +30 -7
- package/dist/server/app-fallback-renderer.js +2 -1
- package/dist/server/app-middleware.d.ts +6 -0
- package/dist/server/app-middleware.js +8 -10
- package/dist/server/app-optimistic-routing.d.ts +8 -10
- package/dist/server/app-optimistic-routing.js +4 -95
- package/dist/server/app-page-boundary-render.js +8 -1
- package/dist/server/app-page-cache-render.js +1 -0
- package/dist/server/app-page-dispatch.js +2 -0
- package/dist/server/app-page-render.js +3 -1
- package/dist/server/app-page-rsc-render-status.d.ts +1 -0
- package/dist/server/app-page-rsc-render-status.js +1 -0
- package/dist/server/app-page-search-params-observation.d.ts +1 -4
- package/dist/server/app-page-search-params-observation.js +8 -10
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-pages-bridge.d.ts +2 -1
- package/dist/server/app-pages-bridge.js +11 -7
- package/dist/server/app-prerender-static-params.js +3 -2
- package/dist/server/app-render-dependency.js +42 -2
- package/dist/server/app-request-stage-independent-entry.js +2 -2
- package/dist/server/app-response-stage-entry.js +3 -3
- package/dist/server/app-route-handler-dispatch.js +3 -1
- package/dist/server/app-route-handler-execution.d.ts +2 -0
- package/dist/server/app-route-handler-execution.js +9 -2
- package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
- package/dist/server/app-route-handler-middleware-context.js +3 -3
- package/dist/server/app-route-handler-response.d.ts +2 -1
- package/dist/server/app-route-handler-response.js +2 -2
- package/dist/server/app-route-handler-runtime.d.ts +2 -0
- package/dist/server/app-route-handler-runtime.js +20 -3
- package/dist/server/app-route-module-loader.js +7 -6
- package/dist/server/app-route-tree.d.ts +20 -0
- package/dist/server/app-route-tree.js +18 -0
- package/dist/server/app-router-entry.js +2 -2
- package/dist/server/app-rsc-combined-handler.js +8 -1
- package/dist/server/app-rsc-errors.d.ts +7 -0
- package/dist/server/app-rsc-errors.js +9 -2
- package/dist/server/app-rsc-handler.d.ts +4 -1
- package/dist/server/app-rsc-handler.js +146 -130
- package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.js +3 -1
- package/dist/server/app-rsc-route-matching.d.ts +1 -0
- package/dist/server/app-rsc-route-matching.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +23 -6
- package/dist/server/app-server-action-execution.js +50 -56
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +16 -7
- package/dist/server/app-ssr-stream.d.ts +7 -0
- package/dist/server/app-ssr-stream.js +13 -1
- package/dist/server/config-headers.js +12 -3
- package/dist/server/isolate-shared-promise.d.ts +10 -0
- package/dist/server/isolate-shared-promise.js +33 -0
- package/dist/server/isr-cache.js +3 -2
- package/dist/server/middleware-matcher-pattern.d.ts +0 -1
- package/dist/server/middleware-matcher-pattern.js +11 -140
- package/dist/server/middleware-matcher.js +5 -6
- package/dist/server/middleware-response-headers.d.ts +31 -0
- package/dist/server/middleware-response-headers.js +57 -1
- package/dist/server/middleware-runtime.js +26 -13
- package/dist/server/navigation-planner.d.ts +2 -2
- package/dist/server/nitro-public-files-plugin.d.ts +38 -0
- package/dist/server/nitro-public-files-plugin.js +106 -0
- package/dist/server/nitro-public-files.d.ts +21 -0
- package/dist/server/nitro-public-files.js +39 -0
- package/dist/server/pages-asset-tags.d.ts +5 -0
- package/dist/server/pages-asset-tags.js +9 -2
- package/dist/server/pages-client-assets.d.ts +4 -1
- package/dist/server/pages-i18n.d.ts +18 -0
- package/dist/server/pages-i18n.js +18 -2
- package/dist/server/pages-node-compat.d.ts +5 -3
- package/dist/server/pages-node-compat.js +35 -16
- package/dist/server/pages-request-pipeline.d.ts +6 -1
- package/dist/server/pages-request-pipeline.js +31 -35
- package/dist/server/pages-request-stage-entry.js +16 -6
- package/dist/server/pages-response-stage-entry.js +1 -1
- package/dist/server/pages-revalidate.js +1 -0
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +71 -33
- package/dist/server/proxy-trust.d.ts +6 -0
- package/dist/server/proxy-trust.js +12 -6
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +12 -1
- package/dist/server/server-reference-loader.js +10 -1
- package/dist/server/static-file-cache.js +15 -3
- package/dist/server/worker-utils.js +1 -1
- package/dist/shims/cache-handler.js +3 -2
- package/dist/shims/cache-request-state.d.ts +40 -3
- package/dist/shims/cache-request-state.js +70 -5
- package/dist/shims/cache-runtime.js +47 -19
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +17 -8
- package/dist/shims/cdn-purge-routes.d.ts +24 -0
- package/dist/shims/cdn-purge-routes.js +77 -0
- package/dist/shims/dynamic.js +17 -4
- package/dist/shims/form.js +8 -3
- package/dist/shims/image-props.d.ts +93 -0
- package/dist/shims/image-props.js +323 -0
- package/dist/shims/image.d.ts +4 -18
- package/dist/shims/image.js +4 -319
- package/dist/shims/internal/headers-state.d.ts +7 -0
- package/dist/shims/internal/headers-state.js +14 -2
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +31 -14
- package/dist/shims/internal/hybrid-client-route-owner.js +8 -8
- package/dist/shims/link.js +5 -3
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.js +17 -5
- package/dist/shims/og.js +35 -10
- package/dist/shims/request-context.d.ts +13 -0
- package/dist/shims/router.js +19 -40
- package/dist/shims/server.d.ts +31 -4
- package/dist/shims/server.js +58 -10
- package/dist/shims/thenable-params.d.ts +6 -1
- package/dist/shims/thenable-params.js +11 -2
- package/dist/shims/unified-request-context.d.ts +5 -0
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/utils/asset-prefix.d.ts +7 -0
- package/dist/utils/asset-prefix.js +10 -1
- package/dist/utils/client-build-manifest.js +12 -5
- package/dist/utils/client-runtime-metadata.d.ts +1 -0
- package/dist/utils/client-runtime-metadata.js +15 -2
- package/dist/utils/lazy-chunks.d.ts +4 -0
- package/dist/utils/manifest-paths.d.ts +14 -0
- package/dist/utils/manifest-paths.js +22 -1
- package/dist/utils/path-to-regexp.d.ts +21 -0
- package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
- package/dist/utils/plugin-react-version.d.ts +15 -0
- package/dist/utils/plugin-react-version.js +64 -0
- package/dist/utils/public-routes.d.ts +2 -0
- package/dist/utils/public-routes.js +18 -7
- package/package.json +1 -1
- package/dist/server/middleware-path-to-regexp.d.ts +0 -13
- package/dist/utils/regex-safety.d.ts +0 -17
- package/dist/utils/regex-safety.js +0 -789
|
@@ -1,60 +1,32 @@
|
|
|
1
1
|
import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
2
2
|
import { buildRequestHeadersFromMiddlewareResponse, getUnconsumedMiddlewareRequestHeaders } from "../utils/middleware-request-headers.js";
|
|
3
|
-
import {
|
|
3
|
+
import { compileConfigSource, configSourceError, execConfigSource, matchConfigPattern } from "./config-source.js";
|
|
4
4
|
import { normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
|
|
5
5
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
6
6
|
import { attachRequestCfMetadata } from "../server/request-pipeline.js";
|
|
7
7
|
import { substituteDestinationParams, substituteRedirectDestinationQuery } from "./destination-params.js";
|
|
8
|
+
import { compileHeaderParamTemplate, isValidHeaderPair } from "./header-params.js";
|
|
8
9
|
//#region src/config/config-matchers.ts
|
|
9
10
|
/**
|
|
10
|
-
* Cache for compiled regex patterns in matchConfigPattern.
|
|
11
|
-
*
|
|
12
|
-
* Redirect/rewrite patterns are static — they come from next.config.js and
|
|
13
|
-
* never change at runtime. Without caching, every request that hits the regex
|
|
14
|
-
* branch re-runs the full tokeniser walk + isSafeRegex + new RegExp() for
|
|
15
|
-
* every rule in the array. On apps with many locale-prefixed rules (which all
|
|
16
|
-
* contain `(` and therefore enter the regex branch) this dominated profiling
|
|
17
|
-
* at ~2.4 seconds of CPU self-time.
|
|
18
|
-
*
|
|
19
|
-
* Value is `null` when safeRegExp rejected the pattern (ReDoS risk), so we
|
|
20
|
-
* skip it on subsequent requests too without re-running the scanner.
|
|
21
|
-
*/
|
|
22
|
-
const _compiledPatternCache = /* @__PURE__ */ new Map();
|
|
23
|
-
/**
|
|
24
|
-
* Cache for compiled header source regexes in matchHeaders.
|
|
25
|
-
*
|
|
26
|
-
* Each NextHeader rule has a `source` that is run through escapeHeaderSource()
|
|
27
|
-
* then safeRegExp() to produce a RegExp. Both are pure functions of the source
|
|
28
|
-
* string and the result never changes. Without caching, every request
|
|
29
|
-
* re-runs the full escapeHeaderSource tokeniser + isSafeRegex scan + new RegExp()
|
|
30
|
-
* for every header rule.
|
|
31
|
-
*
|
|
32
|
-
* Value is `null` when safeRegExp rejected the pattern (ReDoS risk).
|
|
33
|
-
*/
|
|
34
|
-
const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
|
|
35
|
-
/**
|
|
36
11
|
* Cache for compiled has/missing condition value regexes in checkSingleCondition.
|
|
37
12
|
*
|
|
38
|
-
* Each has/missing condition may carry a `value` string that is
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
13
|
+
* Each has/missing condition may carry a `value` string that is compiled as a
|
|
14
|
+
* regex for matching against header/cookie/query/host values. The condition
|
|
15
|
+
* objects are static (from next.config.js) so the compiled RegExp never
|
|
16
|
+
* changes. Without caching, the value is recompiled on every request for every
|
|
17
|
+
* condition on every rule.
|
|
43
18
|
*
|
|
44
|
-
* Value is `null` when
|
|
45
|
-
*
|
|
19
|
+
* Value is `null` when the value is not a valid regex (use exact string
|
|
20
|
+
* comparison instead).
|
|
46
21
|
*/
|
|
47
22
|
const _compiledConditionCache = /* @__PURE__ */ new Map();
|
|
48
23
|
/**
|
|
49
|
-
* Generic helper for the regex compilation
|
|
24
|
+
* Generic helper for the regex compilation cache above.
|
|
50
25
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
26
|
+
* The cache stores the compiled artifact (or `null` when the pattern could
|
|
27
|
+
* not be compiled) the first time a key is seen, and reuses it forever. The
|
|
53
28
|
* `undefined` sentinel distinguishes "not yet seen" from "seen and rejected"
|
|
54
|
-
* so we never
|
|
55
|
-
*
|
|
56
|
-
* Keep the security path intact: `compile()` is responsible for calling
|
|
57
|
-
* safeRegExp(); this helper only handles caching.
|
|
29
|
+
* so we never recompile the same input.
|
|
58
30
|
*/
|
|
59
31
|
function getCachedRegex(cache, key, compile) {
|
|
60
32
|
let value = cache.get(key);
|
|
@@ -106,8 +78,36 @@ function getCachedRegex(cache, key, compile) {
|
|
|
106
78
|
* by `applyLocaleToRoutes` for the locale-capture variant)
|
|
107
79
|
* Both forms benefit from O(1) suffix lookup; the optionality is recorded
|
|
108
80
|
* on the entry so we know whether to try the no-locale-prefix bucket.
|
|
81
|
+
*
|
|
82
|
+
* The index must match exactly what the compiled source would. So the param
|
|
83
|
+
* name follows path-to-regexp's `\w` name lexer, the alternatives are plain
|
|
84
|
+
* words, possibly empty (a `.*` alternative could span several segments), and
|
|
85
|
+
* the suffix class only admits characters path-to-regexp treats as literals:
|
|
86
|
+
* `:` (param), `*` and `+` (modifiers), `(`, `{`, `?` and `\` are excluded.
|
|
109
87
|
*/
|
|
110
|
-
const _LOCALE_STATIC_RE =
|
|
88
|
+
const _LOCALE_STATIC_RE = /^\/:\w+\(([\w-]*(?:\|[\w-]*)*)\)(\??)\/([a-zA-Z0-9_~.%@!$&',;=/-]+)$/;
|
|
89
|
+
/**
|
|
90
|
+
* ECMAScript's non-Unicode Canonicalize operation, which a RegExp with `i` and
|
|
91
|
+
* without `u` compares characters by: it keeps the original UTF-16 code unit
|
|
92
|
+
* when uppercasing expands it or maps a non-ASCII character to ASCII.
|
|
93
|
+
*/
|
|
94
|
+
function canonicalizeIgnoreCase(character) {
|
|
95
|
+
const upper = character.toUpperCase();
|
|
96
|
+
if (upper.length !== 1) return character;
|
|
97
|
+
if (character.charCodeAt(0) >= 128 && upper.charCodeAt(0) < 128) return character;
|
|
98
|
+
return upper;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Folds case as a RegExp with `i` and without `u` compares characters, as
|
|
102
|
+
* the compiled source does: `toLowerCase()` would also read the Kelvin sign
|
|
103
|
+
* as `k`.
|
|
104
|
+
*/
|
|
105
|
+
function foldCase(value) {
|
|
106
|
+
if (!/[^ -~]/.test(value)) return value.toUpperCase();
|
|
107
|
+
let folded = "";
|
|
108
|
+
for (let i = 0; i < value.length; i++) folded += canonicalizeIgnoreCase(value[i]);
|
|
109
|
+
return folded;
|
|
110
|
+
}
|
|
111
111
|
const _redirectIndexCache = /* @__PURE__ */ new WeakMap();
|
|
112
112
|
/**
|
|
113
113
|
* Build (or retrieve from cache) the redirect index for a given redirects array.
|
|
@@ -124,12 +124,12 @@ function _getRedirectIndex(redirects) {
|
|
|
124
124
|
for (let i = 0; i < redirects.length; i++) {
|
|
125
125
|
const redirect = redirects[i];
|
|
126
126
|
const m = _LOCALE_STATIC_RE.exec(redirect.source);
|
|
127
|
-
if (m) {
|
|
127
|
+
if (m && m[1] !== "" && !m[3].endsWith("/")) {
|
|
128
128
|
const paramName = redirect.source.slice(2, redirect.source.indexOf("("));
|
|
129
129
|
const alternation = m[1];
|
|
130
130
|
const optional = m[2] === "?";
|
|
131
131
|
const suffix = "/" + m[3];
|
|
132
|
-
const altRe =
|
|
132
|
+
const altRe = compileRegExp("^(?:" + alternation + ")$", "i");
|
|
133
133
|
if (!altRe) {
|
|
134
134
|
linear.push([i, redirect]);
|
|
135
135
|
continue;
|
|
@@ -141,7 +141,7 @@ function _getRedirectIndex(redirects) {
|
|
|
141
141
|
redirect,
|
|
142
142
|
originalIndex: i
|
|
143
143
|
};
|
|
144
|
-
const bucketKey = suffix
|
|
144
|
+
const bucketKey = foldCase(suffix);
|
|
145
145
|
const bucket = localeStatic.get(bucketKey);
|
|
146
146
|
if (bucket) bucket.push(entry);
|
|
147
147
|
else localeStatic.set(bucketKey, [entry]);
|
|
@@ -185,77 +185,18 @@ function stripHopByHopRequestHeaders(headers) {
|
|
|
185
185
|
for (const token of connectionTokens) headers.delete(token);
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* Uses the same deterministic structural analysis as middleware matcher
|
|
191
|
-
* validation. Nested bounded repetition is accepted only when its repeated
|
|
192
|
-
* language has fixed width and unambiguous branches; a fixed outer count can
|
|
193
|
-
* otherwise still cause polynomially catastrophic backtracking on long near
|
|
194
|
-
* misses.
|
|
188
|
+
* Compile a regex from next.config, or return null when it is invalid.
|
|
195
189
|
*
|
|
196
|
-
*
|
|
190
|
+
* Like Next.js, patterns are not checked for catastrophic backtracking: they
|
|
191
|
+
* run as written.
|
|
197
192
|
*/
|
|
198
|
-
function
|
|
199
|
-
return analyzeRegexSafety(pattern, { ignoreCase: flags?.includes("i") }) === null;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Compile a regex pattern safely. Returns the compiled RegExp or null if the
|
|
203
|
-
* pattern is invalid or vulnerable to ReDoS.
|
|
204
|
-
*
|
|
205
|
-
* Logs a warning when a pattern is rejected so developers can fix their config.
|
|
206
|
-
*/
|
|
207
|
-
function safeRegExp(pattern, flags) {
|
|
208
|
-
if (!isSafeRegex(pattern, flags)) {
|
|
209
|
-
console.warn(`[vinext] Rejecting potentially unsafe regex pattern (ReDoS risk): ${pattern}\n Nested or ambiguous repetition can cause catastrophic backtracking.\n Simplify the pattern to make repeated matches fixed and unambiguous.`);
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
193
|
+
function compileRegExp(pattern, flags) {
|
|
212
194
|
try {
|
|
213
195
|
return new RegExp(pattern, flags);
|
|
214
196
|
} catch {
|
|
215
197
|
return null;
|
|
216
198
|
}
|
|
217
199
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Convert a Next.js header/rewrite/redirect source pattern into a regex string.
|
|
220
|
-
*
|
|
221
|
-
* Regex groups in the source (e.g. `(\d+)`) are extracted first, the remaining
|
|
222
|
-
* text is escaped/converted in a **single pass** (avoiding chained `.replace()`
|
|
223
|
-
* which CodeQL flags as incomplete sanitization), then groups are restored.
|
|
224
|
-
*/
|
|
225
|
-
function escapeHeaderSource(source) {
|
|
226
|
-
const S = "";
|
|
227
|
-
const groups = [];
|
|
228
|
-
const withPlaceholders = source.replace(/\(([^)]+)\)/g, (_m, inner) => {
|
|
229
|
-
groups.push(inner);
|
|
230
|
-
return `${S}G${groups.length - 1}${S}`;
|
|
231
|
-
});
|
|
232
|
-
let result = "";
|
|
233
|
-
const re = new RegExp(`${S}G(\\d+)${S}|:[\\w-]+|[.+?*]|[^.+?*:\\uE000]+`, "g");
|
|
234
|
-
let m;
|
|
235
|
-
while ((m = re.exec(withPlaceholders)) !== null) if (m[1] !== void 0) result += `(${groups[Number(m[1])]})`;
|
|
236
|
-
else if (m[0].startsWith(":")) {
|
|
237
|
-
const constraintMatch = withPlaceholders.slice(re.lastIndex).match(new RegExp(`^${S}G(\\d+)${S}`));
|
|
238
|
-
if (constraintMatch) {
|
|
239
|
-
re.lastIndex += constraintMatch[0].length;
|
|
240
|
-
result += `(${groups[Number(constraintMatch[1])]})`;
|
|
241
|
-
} else result += "[^/]+";
|
|
242
|
-
} else switch (m[0]) {
|
|
243
|
-
case ".":
|
|
244
|
-
result += "\\.";
|
|
245
|
-
break;
|
|
246
|
-
case "+":
|
|
247
|
-
result += "\\+";
|
|
248
|
-
break;
|
|
249
|
-
case "?":
|
|
250
|
-
result += "\\?";
|
|
251
|
-
break;
|
|
252
|
-
case "*":
|
|
253
|
-
result += ".*";
|
|
254
|
-
break;
|
|
255
|
-
default: result += m[0];
|
|
256
|
-
}
|
|
257
|
-
return result;
|
|
258
|
-
}
|
|
259
200
|
const _BASEPATH_DEFAULT = {
|
|
260
201
|
basePath: "",
|
|
261
202
|
hadBasePath: true
|
|
@@ -360,11 +301,11 @@ function matchSingleCondition(condition, ctx) {
|
|
|
360
301
|
}
|
|
361
302
|
/**
|
|
362
303
|
* Return a cached RegExp for a has/missing condition value string, compiling
|
|
363
|
-
* on first use. Returns null if
|
|
364
|
-
*
|
|
304
|
+
* on first use. Returns null if the value is not a valid regex (fall back to
|
|
305
|
+
* exact string comparison).
|
|
365
306
|
*/
|
|
366
307
|
function _cachedConditionRegex(value) {
|
|
367
|
-
return getCachedRegex(_compiledConditionCache, value, () =>
|
|
308
|
+
return getCachedRegex(_compiledConditionCache, value, () => compileRegExp(`^${value}$`));
|
|
368
309
|
}
|
|
369
310
|
/**
|
|
370
311
|
* Check all has/missing conditions for a config rule.
|
|
@@ -389,125 +330,13 @@ function checkHasConditions(has, missing, ctx) {
|
|
|
389
330
|
return collectConditionParams(has, missing, ctx) !== null;
|
|
390
331
|
}
|
|
391
332
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*/
|
|
396
|
-
function extractConstraint(str, re) {
|
|
397
|
-
if (str[re.lastIndex] !== "(") return null;
|
|
398
|
-
const start = re.lastIndex + 1;
|
|
399
|
-
let depth = 1;
|
|
400
|
-
let i = start;
|
|
401
|
-
while (i < str.length && depth > 0) {
|
|
402
|
-
if (str[i] === "(") depth++;
|
|
403
|
-
else if (str[i] === ")") depth--;
|
|
404
|
-
i++;
|
|
405
|
-
}
|
|
406
|
-
if (depth !== 0) return null;
|
|
407
|
-
re.lastIndex = i;
|
|
408
|
-
return str.slice(start, i - 1);
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Match a Next.js config pattern (from redirects/rewrites sources) against a pathname.
|
|
412
|
-
* Returns matched params or null.
|
|
413
|
-
*
|
|
414
|
-
* Supports:
|
|
415
|
-
* :param - matches a single path segment
|
|
416
|
-
* :param* - matches zero or more segments (catch-all)
|
|
417
|
-
* :param+ - matches one or more segments
|
|
418
|
-
* (regex) - inline regex patterns in the source
|
|
419
|
-
* :param(constraint) - named param with inline regex constraint
|
|
420
|
-
*/
|
|
421
|
-
/**
|
|
422
|
-
* Strip a single trailing slash from a pathname for config-source matching.
|
|
423
|
-
*
|
|
424
|
-
* Next.js conditionally appends `(/)?` to rewrite/redirect/header source
|
|
425
|
-
* regexes when `trailingSlash: true` (see Next.js
|
|
426
|
-
* `resolve-rewrites.ts` and `server-utils.ts:checkRewrite`). Rather than
|
|
427
|
-
* threading the trailingSlash flag through every matcher, we unconditionally
|
|
428
|
-
* strip a trailing slash from the incoming pathname. When `trailingSlash: false`
|
|
429
|
-
* the request pipeline emits a normalizing redirect (step 3) before config
|
|
430
|
-
* rewrites/redirects (step 6) ever run, so the pathname is already slash-free;
|
|
431
|
-
* the unconditional strip is defense-in-depth for that ordering. When
|
|
432
|
-
* `trailingSlash: true` it bridges the gap between the canonicalized request
|
|
433
|
-
* path (`/rewrite-1/`) and source patterns written without a trailing slash
|
|
434
|
-
* (`/rewrite-1`).
|
|
435
|
-
*
|
|
436
|
-
* The root path `"/"` is preserved as-is.
|
|
333
|
+
* Strip one trailing slash, preserving the root path `"/"`. Compiled sources
|
|
334
|
+
* accept an optional trailing slash, so the redirect index looks up the
|
|
335
|
+
* slash-free form.
|
|
437
336
|
*/
|
|
438
337
|
function stripTrailingSlashForConfigMatch(value) {
|
|
439
338
|
return value.length > 1 && value.endsWith("/") ? value.slice(0, -1) : value;
|
|
440
339
|
}
|
|
441
|
-
function configPathEquals(a, b) {
|
|
442
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
443
|
-
}
|
|
444
|
-
function configPathStartsWith(pathname, prefix) {
|
|
445
|
-
return pathname.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase();
|
|
446
|
-
}
|
|
447
|
-
function matchConfigPattern(pathname, pattern) {
|
|
448
|
-
const pathnameHadTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
|
|
449
|
-
pathname = stripTrailingSlashForConfigMatch(pathname);
|
|
450
|
-
if (pathnameHadTrailingSlash) pattern = stripTrailingSlashForConfigMatch(pattern);
|
|
451
|
-
const catchAllAnchor = /:[\w-]+[*+]/.test(pattern);
|
|
452
|
-
const namedParamCount = (pattern.match(/:[\w-]+/g) || []).length;
|
|
453
|
-
if (pattern.includes("(") || pattern.includes("\\") || /:[\w-]+[*+][^/]/.test(pattern) || /:[\w-]+\./.test(pattern) || /[^/]:[\w-]+/.test(pattern) || catchAllAnchor && namedParamCount > 1) try {
|
|
454
|
-
const compiled = getCachedRegex(_compiledPatternCache, pattern, () => {
|
|
455
|
-
const paramNames = [];
|
|
456
|
-
let regexStr = "";
|
|
457
|
-
const tokenRe = /:([\w-]+)|[.]|[^:.]+/g;
|
|
458
|
-
let tok;
|
|
459
|
-
while ((tok = tokenRe.exec(pattern)) !== null) if (tok[1] !== void 0) {
|
|
460
|
-
const name = tok[1];
|
|
461
|
-
const rest = pattern.slice(tokenRe.lastIndex);
|
|
462
|
-
if (rest.startsWith("*") || rest.startsWith("+")) {
|
|
463
|
-
const quantifier = rest[0];
|
|
464
|
-
tokenRe.lastIndex += 1;
|
|
465
|
-
const constraint = extractConstraint(pattern, tokenRe);
|
|
466
|
-
paramNames.push(name);
|
|
467
|
-
if (constraint !== null) regexStr += `(${constraint})`;
|
|
468
|
-
else regexStr += quantifier === "*" ? "(.*)" : "(.+)";
|
|
469
|
-
} else {
|
|
470
|
-
const constraint = extractConstraint(pattern, tokenRe);
|
|
471
|
-
paramNames.push(name);
|
|
472
|
-
regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
|
|
473
|
-
}
|
|
474
|
-
} else if (tok[0] === ".") regexStr += "\\.";
|
|
475
|
-
else regexStr += tok[0];
|
|
476
|
-
const re = safeRegExp("^" + regexStr + "$", "i");
|
|
477
|
-
return re ? {
|
|
478
|
-
re,
|
|
479
|
-
paramNames
|
|
480
|
-
} : null;
|
|
481
|
-
});
|
|
482
|
-
if (!compiled) return null;
|
|
483
|
-
const match = compiled.re.exec(pathname);
|
|
484
|
-
if (!match) return null;
|
|
485
|
-
const params = Object.create(null);
|
|
486
|
-
for (let i = 0; i < compiled.paramNames.length; i++) params[compiled.paramNames[i]] = match[i + 1] ?? "";
|
|
487
|
-
return params;
|
|
488
|
-
} catch {}
|
|
489
|
-
const catchAllMatch = pattern.match(/:([\w-]+)(\*|\+)$/);
|
|
490
|
-
if (catchAllMatch) {
|
|
491
|
-
const prefix = pattern.slice(0, pattern.lastIndexOf(":"));
|
|
492
|
-
const paramName = catchAllMatch[1];
|
|
493
|
-
const isPlus = catchAllMatch[2] === "+";
|
|
494
|
-
const prefixNoSlash = prefix.replace(/\/$/, "");
|
|
495
|
-
if (!configPathStartsWith(pathname, prefixNoSlash)) return null;
|
|
496
|
-
const charAfter = pathname[prefixNoSlash.length];
|
|
497
|
-
if (charAfter !== void 0 && charAfter !== "/") return null;
|
|
498
|
-
const rest = pathname.slice(prefixNoSlash.length);
|
|
499
|
-
if (isPlus && (!rest || rest === "/")) return null;
|
|
500
|
-
let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
|
|
501
|
-
return { [paramName]: restValue };
|
|
502
|
-
}
|
|
503
|
-
const parts = pattern.split("/");
|
|
504
|
-
const pathParts = pathname.split("/");
|
|
505
|
-
if (parts.length !== pathParts.length) return null;
|
|
506
|
-
const params = Object.create(null);
|
|
507
|
-
for (let i = 0; i < parts.length; i++) if (parts[i].startsWith(":")) params[parts[i].slice(1)] = pathParts[i];
|
|
508
|
-
else if (!configPathEquals(parts[i], pathParts[i])) return null;
|
|
509
|
-
return params;
|
|
510
|
-
}
|
|
511
340
|
/**
|
|
512
341
|
* Apply redirect rules from next.config.js.
|
|
513
342
|
* Returns the redirect info if a redirect was matched, or null.
|
|
@@ -550,7 +379,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
550
379
|
let localeMatch = null;
|
|
551
380
|
let localeMatchIndex = Infinity;
|
|
552
381
|
if (index.localeStatic.size > 0) {
|
|
553
|
-
const noLocaleBucket = index.localeStatic.get(normalizedPathname
|
|
382
|
+
const noLocaleBucket = index.localeStatic.get(foldCase(normalizedPathname));
|
|
554
383
|
if (noLocaleBucket) for (const entry of noLocaleBucket) {
|
|
555
384
|
if (!entry.optional) continue;
|
|
556
385
|
if (entry.originalIndex >= localeMatchIndex) continue;
|
|
@@ -570,7 +399,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
570
399
|
if (slashTwo !== -1) {
|
|
571
400
|
const suffix = normalizedPathname.slice(slashTwo);
|
|
572
401
|
const localePart = normalizedPathname.slice(1, slashTwo);
|
|
573
|
-
const localeBucket = index.localeStatic.get(suffix
|
|
402
|
+
const localeBucket = index.localeStatic.get(foldCase(suffix));
|
|
574
403
|
if (localeBucket) for (const entry of localeBucket) {
|
|
575
404
|
if (entry.originalIndex >= localeMatchIndex) continue;
|
|
576
405
|
if (!entry.altRe.test(localePart)) continue;
|
|
@@ -615,17 +444,17 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
615
444
|
function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT, paramsPathname = pathname, onRuleSourceMatch) {
|
|
616
445
|
for (const rewrite of rewrites) {
|
|
617
446
|
if (!shouldEvaluateRule(rewrite.basePath, basePathState)) continue;
|
|
618
|
-
const
|
|
619
|
-
if (
|
|
447
|
+
const matched = execConfigSource(pathname, rewrite.source);
|
|
448
|
+
if (matched) {
|
|
620
449
|
onRuleSourceMatch?.(rewrite);
|
|
621
|
-
const params = paramsPathname === pathname ?
|
|
450
|
+
const { params, unmatched } = paramsPathname === pathname ? matched : execConfigSource(paramsPathname, rewrite.source) ?? matched;
|
|
622
451
|
const conditionParams = rewrite.has || rewrite.missing ? collectConditionParams(rewrite.has, rewrite.missing, ctx) : _emptyParams();
|
|
623
452
|
if (!conditionParams) continue;
|
|
624
453
|
const rewriteParams = {
|
|
625
454
|
...params,
|
|
626
455
|
...conditionParams
|
|
627
456
|
};
|
|
628
|
-
return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams);
|
|
457
|
+
return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams, unmatched.filter((key) => !(key in conditionParams)));
|
|
629
458
|
}
|
|
630
459
|
}
|
|
631
460
|
return null;
|
|
@@ -673,16 +502,18 @@ function resolveRedirectMatch(redirect, params) {
|
|
|
673
502
|
/**
|
|
674
503
|
* Match Next.js's rewrite-specific prepareDestination behavior: source params
|
|
675
504
|
* that are not consumed by the destination path/host are exposed to the target
|
|
676
|
-
* page through query.
|
|
505
|
+
* page through query. Optional params that did not match are not params at
|
|
506
|
+
* all in Next.js, so they are neither appended nor count as consumed.
|
|
677
507
|
*
|
|
678
508
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
679
509
|
*/
|
|
680
|
-
function substituteAndSanitizeRewriteDestination(destination, params) {
|
|
510
|
+
function substituteAndSanitizeRewriteDestination(destination, params, unmatchedParams) {
|
|
681
511
|
const rewritten = substituteAndSanitizeDestination(destination, params, "rewrite");
|
|
682
|
-
|
|
512
|
+
const matchedParams = unmatchedParams.length === 0 ? params : Object.fromEntries(Object.entries(params).filter(([key]) => !unmatchedParams.includes(key)));
|
|
513
|
+
if (!shouldAppendRewriteParamsToQuery(destination, matchedParams)) return rewritten;
|
|
683
514
|
const existingQueryKeys = getDestinationQueryKeys(destination);
|
|
684
515
|
const paramsToAppend = [];
|
|
685
|
-
for (const [key, value] of Object.entries(
|
|
516
|
+
for (const [key, value] of Object.entries(matchedParams)) {
|
|
686
517
|
if (key === "nextInternalLocale" || existingQueryKeys.has(key)) continue;
|
|
687
518
|
paramsToAppend.push([key, value]);
|
|
688
519
|
}
|
|
@@ -898,23 +729,118 @@ async function proxyExternalRequest(request, externalUrl) {
|
|
|
898
729
|
* when evaluating headers, so this parameter is required.
|
|
899
730
|
*/
|
|
900
731
|
function matchHeaders(pathname, headers, ctx, basePathState = _BASEPATH_DEFAULT, onRuleSourceMatch) {
|
|
901
|
-
const pathnameHadTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
|
|
902
|
-
pathname = stripTrailingSlashForConfigMatch(pathname);
|
|
903
732
|
const result = [];
|
|
904
733
|
for (const rule of headers) {
|
|
905
734
|
if (!shouldEvaluateRule(rule.basePath, basePathState)) continue;
|
|
906
|
-
const
|
|
907
|
-
const
|
|
908
|
-
if (
|
|
735
|
+
const compiledSource = compileConfigSource(rule.source);
|
|
736
|
+
const sourceMatch = compiledSource?.re.exec(pathname);
|
|
737
|
+
if (compiledSource && sourceMatch) {
|
|
909
738
|
onRuleSourceMatch?.(rule);
|
|
910
739
|
if (rule.has || rule.missing) {
|
|
911
740
|
if (!checkHasConditions(rule.has, rule.missing, ctx)) continue;
|
|
912
741
|
}
|
|
913
|
-
|
|
742
|
+
if (!rule.headers.some((header) => header.key.includes(":") || header.value.includes(":"))) {
|
|
743
|
+
result.push(...rule.headers);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const params = collectHeaderSourceParams(sourceMatch, compiledSource.keys);
|
|
747
|
+
if (rule.has) collectHeaderHasParams(rule.has, ctx, params);
|
|
748
|
+
result.push(...substituteHeaderParams(rule.headers, params));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return result;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Collect source params like Next.js's `getPathMatch` with
|
|
755
|
+
* `removeUnnamedParams`: path-to-regexp's `regexpToFunction` keeps captures
|
|
756
|
+
* raw, splits `*`/`+` captures on the key's prefix and suffix, and omits
|
|
757
|
+
* groups that did not participate.
|
|
758
|
+
*/
|
|
759
|
+
function collectHeaderSourceParams(match, keys) {
|
|
760
|
+
const params = _emptyParams();
|
|
761
|
+
for (let index = 1; index < match.length; index++) {
|
|
762
|
+
const capture = match[index];
|
|
763
|
+
const key = keys[index - 1];
|
|
764
|
+
if (capture === void 0 || !key || typeof key.name === "number") continue;
|
|
765
|
+
params[key.name] = key.modifier === "*" || key.modifier === "+" ? capture.split(key.prefix + key.suffix) : capture;
|
|
766
|
+
}
|
|
767
|
+
return params;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Add `has` captures like Next.js's `matchHas`, after checkHasConditions has
|
|
771
|
+
* accepted the rule: a condition without a value exposes the matched value
|
|
772
|
+
* under its key (letters only), a value regex exposes its named groups, and a
|
|
773
|
+
* host regex without named groups exposes the matched host as `host`.
|
|
774
|
+
*
|
|
775
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
776
|
+
*/
|
|
777
|
+
function collectHeaderHasParams(has, ctx, params) {
|
|
778
|
+
for (const condition of has) {
|
|
779
|
+
let value;
|
|
780
|
+
switch (condition.type) {
|
|
781
|
+
case "header":
|
|
782
|
+
value = ctx.headers.get(condition.key) ?? void 0;
|
|
783
|
+
break;
|
|
784
|
+
case "cookie":
|
|
785
|
+
value = Object.hasOwn(ctx.cookies, condition.key) ? ctx.cookies[condition.key] : void 0;
|
|
786
|
+
break;
|
|
787
|
+
case "query": {
|
|
788
|
+
const values = ctx.query.getAll(condition.key);
|
|
789
|
+
value = values.length > 1 ? values : values[0];
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "host": value = ctx.host;
|
|
914
793
|
}
|
|
794
|
+
if (!value) continue;
|
|
795
|
+
if (!condition.value) {
|
|
796
|
+
if (condition.type !== "host") params[condition.key.replace(/[^A-Za-z]/g, "")] = value;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const match = _cachedConditionRegex(condition.value)?.exec(Array.isArray(value) ? value[value.length - 1] : value);
|
|
800
|
+
if (!match) continue;
|
|
801
|
+
if (match.groups) Object.assign(params, match.groups);
|
|
802
|
+
else if (condition.type === "host" && match[0]) params.host = match[0];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const _warnedHeaderParamTemplates = /* @__PURE__ */ new WeakSet();
|
|
806
|
+
/**
|
|
807
|
+
* Substitute params into header keys and values like Next.js's
|
|
808
|
+
* `compileNonPath`, with params inserted verbatim. Source captures are the raw
|
|
809
|
+
* percent-encoded path text, as in Next.js, but `has` captures from decoded
|
|
810
|
+
* cookies or query values can carry characters a header cannot.
|
|
811
|
+
*
|
|
812
|
+
* Next.js fails the whole request when compiling throws (a repeated param
|
|
813
|
+
* used without `*`/`+`, or a has group that did not participate) or when the
|
|
814
|
+
* result is not a valid header. Requests can trigger both, so vinext instead
|
|
815
|
+
* omits only the affected header and logs once per header template.
|
|
816
|
+
*/
|
|
817
|
+
function substituteHeaderParams(headers, params) {
|
|
818
|
+
if (Object.keys(params).length === 0) return headers;
|
|
819
|
+
const result = [];
|
|
820
|
+
for (const header of headers) {
|
|
821
|
+
let key;
|
|
822
|
+
let value;
|
|
823
|
+
try {
|
|
824
|
+
key = compileHeaderParamTemplate(header.key, params);
|
|
825
|
+
value = compileHeaderParamTemplate(header.value, params);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
warnHeaderParamTemplate(header, error instanceof Error ? error.message : String(error));
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (key === header.key && value === header.value) result.push(header);
|
|
831
|
+
else if (isValidHeaderPair(key, value)) result.push({
|
|
832
|
+
key,
|
|
833
|
+
value
|
|
834
|
+
});
|
|
835
|
+
else warnHeaderParamTemplate(header, "the substituted header is not a valid HTTP header");
|
|
915
836
|
}
|
|
916
837
|
return result;
|
|
917
838
|
}
|
|
839
|
+
function warnHeaderParamTemplate(header, reason) {
|
|
840
|
+
if (_warnedHeaderParamTemplates.has(header)) return;
|
|
841
|
+
_warnedHeaderParamTemplates.add(header);
|
|
842
|
+
console.warn(`[vinext] Omitting headers() header "${header.key}: ${header.value}" from a response: ${reason}.`);
|
|
843
|
+
}
|
|
918
844
|
/**
|
|
919
845
|
* Escape a string for inclusion in a regex character class / alternation.
|
|
920
846
|
* Mirrors `escape-string-regexp` semantics used by Next.js's processRoutes.
|
|
@@ -936,17 +862,19 @@ function _escapeRegexString(value) {
|
|
|
936
862
|
* starts with `/:nextInternalLocale(en|sv|nl)` so that locale-prefixed
|
|
937
863
|
* URLs match. For redirects only, a second variant prefixed with
|
|
938
864
|
* `/${defaultLocale}` is also emitted, matching Next.js exactly.
|
|
939
|
-
* -
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
865
|
+
* - Headers get only that variant, exactly like Next.js. Every header
|
|
866
|
+
* matcher caller splices the (domain-aware) default locale into
|
|
867
|
+
* unprefixed request paths first (`normalizeDefaultLocalePathname`,
|
|
868
|
+
* mirroring `resolve-routes.ts`), so `/about` is matched as `/en/about`.
|
|
869
|
+
* `/_next/*` paths are left unprefixed and therefore only match
|
|
870
|
+
* `locale: false` rules, as in Next.js.
|
|
871
|
+
* - **Vinext divergence (redirects/rewrites only)**: we ALSO retain the
|
|
872
|
+
* original (unprefixed) source, appended LAST so the locale-aware
|
|
873
|
+
* variants win when both forms could match. Their callers normalise
|
|
874
|
+
* default-locale paths too, so this is a legacy fallback. Headers never
|
|
875
|
+
* get it: it would apply rules Next.js skips (`/((?!api).*)` would match
|
|
876
|
+
* `/fr/api/x` as `fr/api/x`), and default-locale requests would match
|
|
877
|
+
* both variants, appending additive headers such as `Set-Cookie` twice.
|
|
950
878
|
*
|
|
951
879
|
* Destinations that are local (start with `/`) are similarly rewritten with
|
|
952
880
|
* `/:nextInternalLocale` for the locale-capture variant so the locale
|
|
@@ -985,9 +913,9 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
|
|
|
985
913
|
};
|
|
986
914
|
if ("destination" in internalRoute && internalDestination !== void 0) internalRoute.destination = internalDestination;
|
|
987
915
|
out.push(internalRoute);
|
|
988
|
-
out.push(r);
|
|
916
|
+
if (type !== "header") out.push(r);
|
|
989
917
|
}
|
|
990
918
|
return out;
|
|
991
919
|
}
|
|
992
920
|
//#endregion
|
|
993
|
-
export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions,
|
|
921
|
+
export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, configSourceError, isExternalUrl, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, sanitizeDestination };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PathKey } from "../utils/path-to-regexp.js";
|
|
2
|
+
//#region src/config/config-source.d.ts
|
|
3
|
+
export type CompiledConfigSource = {
|
|
4
|
+
re: RegExp;
|
|
5
|
+
/** Keys in capture-group order; unnamed groups have numeric names. */
|
|
6
|
+
keys: PathKey[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Why `source` is not a valid redirects/rewrites/headers source, or null.
|
|
10
|
+
* Mirrors the source checks in Next.js's checkCustomRoutes, which fail the
|
|
11
|
+
* build; config loading calls this to do the same.
|
|
12
|
+
*
|
|
13
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/lib/load-custom-routes.ts
|
|
14
|
+
*/
|
|
15
|
+
export declare function configSourceError(source: unknown): string | null;
|
|
16
|
+
export declare function compileConfigSource(source: string): CompiledConfigSource | null;
|
|
17
|
+
/**
|
|
18
|
+
* Match a Next.js config source (from redirects/rewrites/headers) against a
|
|
19
|
+
* pathname. Returns the named params or null.
|
|
20
|
+
*
|
|
21
|
+
* Unnamed groups are dropped, as Next.js does with `removeUnnamedParams`.
|
|
22
|
+
* Repeated params (`:path*`, `:path+`) keep the captured string (`a/b`), and
|
|
23
|
+
* an optional param that did not match is `""`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function matchConfigPattern(pathname: string, pattern: string): Record<string, string> | null;
|
|
26
|
+
/**
|
|
27
|
+
* matchConfigPattern, also reporting the optional params that did not match.
|
|
28
|
+
* Next.js leaves those out of its params entirely.
|
|
29
|
+
*/
|
|
30
|
+
export declare function execConfigSource(pathname: string, source: string): {
|
|
31
|
+
params: Record<string, string>;
|
|
32
|
+
unmatched: string[];
|
|
33
|
+
} | null;
|
|
34
|
+
//#endregion
|