vinext 1.2.0 → 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 +13 -10
- 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/output-root-externals.d.ts +21 -0
- package/dist/build/output-root-externals.js +40 -0
- package/dist/build/pages-client-assets-module.js +1 -1
- package/dist/build/prerender-paths.js +6 -3
- package/dist/build/prerender.js +1 -1
- 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/check.js +5 -6
- package/dist/cli-dev-config.js +3 -3
- package/dist/cli.js +1 -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 +195 -262
- 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 +350 -175
- package/dist/init.d.ts +1 -1
- package/dist/init.js +103 -6
- package/dist/plugins/ast-utils.d.ts +53 -2
- package/dist/plugins/ast-utils.js +90 -2
- package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
- package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
- package/dist/plugins/commonjs-esm-facade.js +2 -2
- package/dist/plugins/css-module-imports.js +13 -20
- package/dist/plugins/dynamic-preload-metadata.js +2 -3
- package/dist/plugins/extensionless-dynamic-import.js +2 -3
- package/dist/plugins/fonts.d.ts +1 -1
- package/dist/plugins/fonts.js +3 -3
- package/dist/plugins/ignore-dynamic-requests.js +2 -3
- package/dist/plugins/import-meta-url.js +3 -4
- package/dist/plugins/instrumentation-client.js +3 -2
- package/dist/plugins/middleware-export-validation.js +2 -3
- package/dist/plugins/og-harfbuzz.js +2 -3
- package/dist/plugins/optimize-imports.d.ts +25 -2
- package/dist/plugins/optimize-imports.js +109 -24
- package/dist/plugins/pages-node-externals.js +2 -3
- package/dist/plugins/remove-console.js +2 -18
- package/dist/plugins/require-condition-resolution.js +3 -3
- package/dist/plugins/require-context.js +2 -3
- package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
- package/dist/plugins/rsc-client-reference-loaders.js +336 -18
- package/dist/plugins/strip-server-exports.js +3 -4
- package/dist/plugins/styled-jsx.js +2 -3
- package/dist/plugins/use-cache-callable.d.ts +26 -2
- package/dist/plugins/use-cache-callable.js +101 -36
- package/dist/plugins/worker-image-imports.d.ts +1 -0
- package/dist/plugins/worker-image-imports.js +31 -34
- package/dist/plugins/worker-use-server.js +38 -14
- 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/api-handler.js +1 -1
- package/dist/server/app-action-forwarding.d.ts +8 -0
- package/dist/server/app-action-forwarding.js +17 -3
- package/dist/server/app-browser-entry.js +149 -62
- 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-interception-context.js +1 -1
- 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-prefetch-response.js +2 -2
- package/dist/server/app-browser-state.d.ts +2 -2
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-supplemental-refresh.js +1 -1
- 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 +3 -1
- package/dist/server/app-page-render.js +4 -2
- 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 +12 -8
- package/dist/server/app-prerender-endpoints.js +1 -1
- package/dist/server/app-prerender-static-params.js +3 -2
- package/dist/server/app-render-abort-error.d.ts +19 -0
- package/dist/server/app-render-abort-error.js +31 -1
- package/dist/server/app-render-dependency.js +42 -2
- package/dist/server/app-request-stage-independent-entry.js +7 -7
- package/dist/server/app-response-stage-entry.js +5 -5
- 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 +21 -4
- 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 +7 -7
- package/dist/server/app-rsc-cache-busting.d.ts +16 -0
- package/dist/server/app-rsc-cache-busting.js +29 -12
- 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 +151 -135
- package/dist/server/app-rsc-request-normalization.js +3 -3
- 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 +5 -3
- 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 +51 -57
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +21 -11
- package/dist/server/app-ssr-stream.d.ts +7 -0
- package/dist/server/app-ssr-stream.js +13 -1
- package/dist/server/app-visited-response-cache.js +2 -2
- package/dist/server/config-headers.js +13 -4
- package/dist/server/defer-until-stream-consumed.d.ts +2 -1
- package/dist/server/defer-until-stream-consumed.js +3 -1
- package/dist/server/dev-server.js +1 -1
- package/dist/server/image-optimization.js +1 -0
- 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/metadata-route-response.js +2 -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 +30 -17
- package/dist/server/navigation-planner.d.ts +2 -2
- package/dist/server/navigation-planner.js +1 -1
- 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/open-redirect.d.ts +11 -4
- package/dist/server/open-redirect.js +11 -4
- package/dist/server/pages-api-route.js +2 -2
- 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-data-route.js +2 -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-page-data.js +1 -1
- package/dist/server/pages-page-handler.js +1 -1
- package/dist/server/pages-page-response.js +1 -1
- package/dist/server/pages-request-pipeline.d.ts +6 -1
- package/dist/server/pages-request-pipeline.js +35 -39
- package/dist/server/pages-request-stage-entry.js +20 -10
- package/dist/server/pages-response-stage-entry.js +1 -1
- package/dist/server/pages-revalidate.js +3 -2
- package/dist/server/pages-router-entry.js +2 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +74 -36
- 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/rsc-stream-hints.d.ts +1 -1
- package/dist/server/rsc-stream-hints.js +21 -3
- package/dist/server/server-reference-loader.js +10 -1
- package/dist/server/static-file-cache.js +15 -3
- package/dist/server/worker-image-optimization.js +19 -6
- 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/head.js +1 -1
- package/dist/shims/headers.js +2 -2
- 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 +32 -15
- package/dist/shims/internal/hybrid-client-route-owner.js +9 -8
- package/dist/shims/link-prefetch.js +5 -4
- package/dist/shims/link.js +7 -5
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.js +21 -10
- package/dist/shims/og.js +35 -10
- package/dist/shims/request-context.d.ts +13 -0
- package/dist/shims/router.js +28 -52
- package/dist/shims/server.d.ts +42 -8
- package/dist/shims/server.js +99 -15
- 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/shims/url-utils.d.ts +7 -0
- package/dist/shims/url-utils.js +12 -3
- 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/domain-locale.js +1 -1
- 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/plugin-rsc-version.d.ts +2 -0
- package/dist/utils/plugin-rsc-version.js +28 -2
- package/dist/utils/public-routes.d.ts +2 -0
- package/dist/utils/public-routes.js +18 -7
- package/dist/utils/react-version.d.ts +7 -0
- package/dist/utils/react-version.js +39 -16
- package/package.json +6 -6
- package/dist/build/nitro-route-rules.d.ts +0 -47
- package/dist/build/nitro-route-rules.js +0 -79
- package/dist/plugins/cache-flight-codec.d.ts +0 -19
- package/dist/plugins/cache-flight-codec.js +0 -68
- 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,59 +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
|
+
import { attachRequestCfMetadata } from "../server/request-pipeline.js";
|
|
6
7
|
import { substituteDestinationParams, substituteRedirectDestinationQuery } from "./destination-params.js";
|
|
8
|
+
import { compileHeaderParamTemplate, isValidHeaderPair } from "./header-params.js";
|
|
7
9
|
//#region src/config/config-matchers.ts
|
|
8
10
|
/**
|
|
9
|
-
* Cache for compiled regex patterns in matchConfigPattern.
|
|
10
|
-
*
|
|
11
|
-
* Redirect/rewrite patterns are static — they come from next.config.js and
|
|
12
|
-
* never change at runtime. Without caching, every request that hits the regex
|
|
13
|
-
* branch re-runs the full tokeniser walk + isSafeRegex + new RegExp() for
|
|
14
|
-
* every rule in the array. On apps with many locale-prefixed rules (which all
|
|
15
|
-
* contain `(` and therefore enter the regex branch) this dominated profiling
|
|
16
|
-
* at ~2.4 seconds of CPU self-time.
|
|
17
|
-
*
|
|
18
|
-
* Value is `null` when safeRegExp rejected the pattern (ReDoS risk), so we
|
|
19
|
-
* skip it on subsequent requests too without re-running the scanner.
|
|
20
|
-
*/
|
|
21
|
-
const _compiledPatternCache = /* @__PURE__ */ new Map();
|
|
22
|
-
/**
|
|
23
|
-
* Cache for compiled header source regexes in matchHeaders.
|
|
24
|
-
*
|
|
25
|
-
* Each NextHeader rule has a `source` that is run through escapeHeaderSource()
|
|
26
|
-
* then safeRegExp() to produce a RegExp. Both are pure functions of the source
|
|
27
|
-
* string and the result never changes. Without caching, every request
|
|
28
|
-
* re-runs the full escapeHeaderSource tokeniser + isSafeRegex scan + new RegExp()
|
|
29
|
-
* for every header rule.
|
|
30
|
-
*
|
|
31
|
-
* Value is `null` when safeRegExp rejected the pattern (ReDoS risk).
|
|
32
|
-
*/
|
|
33
|
-
const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
|
|
34
|
-
/**
|
|
35
11
|
* Cache for compiled has/missing condition value regexes in checkSingleCondition.
|
|
36
12
|
*
|
|
37
|
-
* Each has/missing condition may carry a `value` string that is
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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.
|
|
42
18
|
*
|
|
43
|
-
* Value is `null` when
|
|
44
|
-
*
|
|
19
|
+
* Value is `null` when the value is not a valid regex (use exact string
|
|
20
|
+
* comparison instead).
|
|
45
21
|
*/
|
|
46
22
|
const _compiledConditionCache = /* @__PURE__ */ new Map();
|
|
47
23
|
/**
|
|
48
|
-
* Generic helper for the regex compilation
|
|
24
|
+
* Generic helper for the regex compilation cache above.
|
|
49
25
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
|
52
28
|
* `undefined` sentinel distinguishes "not yet seen" from "seen and rejected"
|
|
53
|
-
* so we never
|
|
54
|
-
*
|
|
55
|
-
* Keep the security path intact: `compile()` is responsible for calling
|
|
56
|
-
* safeRegExp(); this helper only handles caching.
|
|
29
|
+
* so we never recompile the same input.
|
|
57
30
|
*/
|
|
58
31
|
function getCachedRegex(cache, key, compile) {
|
|
59
32
|
let value = cache.get(key);
|
|
@@ -105,8 +78,36 @@ function getCachedRegex(cache, key, compile) {
|
|
|
105
78
|
* by `applyLocaleToRoutes` for the locale-capture variant)
|
|
106
79
|
* Both forms benefit from O(1) suffix lookup; the optionality is recorded
|
|
107
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.
|
|
108
87
|
*/
|
|
109
|
-
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
|
+
}
|
|
110
111
|
const _redirectIndexCache = /* @__PURE__ */ new WeakMap();
|
|
111
112
|
/**
|
|
112
113
|
* Build (or retrieve from cache) the redirect index for a given redirects array.
|
|
@@ -123,12 +124,12 @@ function _getRedirectIndex(redirects) {
|
|
|
123
124
|
for (let i = 0; i < redirects.length; i++) {
|
|
124
125
|
const redirect = redirects[i];
|
|
125
126
|
const m = _LOCALE_STATIC_RE.exec(redirect.source);
|
|
126
|
-
if (m) {
|
|
127
|
+
if (m && m[1] !== "" && !m[3].endsWith("/")) {
|
|
127
128
|
const paramName = redirect.source.slice(2, redirect.source.indexOf("("));
|
|
128
129
|
const alternation = m[1];
|
|
129
130
|
const optional = m[2] === "?";
|
|
130
131
|
const suffix = "/" + m[3];
|
|
131
|
-
const altRe =
|
|
132
|
+
const altRe = compileRegExp("^(?:" + alternation + ")$", "i");
|
|
132
133
|
if (!altRe) {
|
|
133
134
|
linear.push([i, redirect]);
|
|
134
135
|
continue;
|
|
@@ -140,7 +141,7 @@ function _getRedirectIndex(redirects) {
|
|
|
140
141
|
redirect,
|
|
141
142
|
originalIndex: i
|
|
142
143
|
};
|
|
143
|
-
const bucketKey = suffix
|
|
144
|
+
const bucketKey = foldCase(suffix);
|
|
144
145
|
const bucket = localeStatic.get(bucketKey);
|
|
145
146
|
if (bucket) bucket.push(entry);
|
|
146
147
|
else localeStatic.set(bucketKey, [entry]);
|
|
@@ -184,77 +185,18 @@ function stripHopByHopRequestHeaders(headers) {
|
|
|
184
185
|
for (const token of connectionTokens) headers.delete(token);
|
|
185
186
|
}
|
|
186
187
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* Uses the same deterministic structural analysis as middleware matcher
|
|
190
|
-
* validation. Nested bounded repetition is accepted only when its repeated
|
|
191
|
-
* language has fixed width and unambiguous branches; a fixed outer count can
|
|
192
|
-
* otherwise still cause polynomially catastrophic backtracking on long near
|
|
193
|
-
* misses.
|
|
194
|
-
*
|
|
195
|
-
* Returns true if the pattern appears safe, false if it's potentially dangerous.
|
|
196
|
-
*/
|
|
197
|
-
function isSafeRegex(pattern, flags) {
|
|
198
|
-
return analyzeRegexSafety(pattern, { ignoreCase: flags?.includes("i") }) === null;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Compile a regex pattern safely. Returns the compiled RegExp or null if the
|
|
202
|
-
* pattern is invalid or vulnerable to ReDoS.
|
|
188
|
+
* Compile a regex from next.config, or return null when it is invalid.
|
|
203
189
|
*
|
|
204
|
-
*
|
|
190
|
+
* Like Next.js, patterns are not checked for catastrophic backtracking: they
|
|
191
|
+
* run as written.
|
|
205
192
|
*/
|
|
206
|
-
function
|
|
207
|
-
if (!isSafeRegex(pattern, flags)) {
|
|
208
|
-
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.`);
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
193
|
+
function compileRegExp(pattern, flags) {
|
|
211
194
|
try {
|
|
212
195
|
return new RegExp(pattern, flags);
|
|
213
196
|
} catch {
|
|
214
197
|
return null;
|
|
215
198
|
}
|
|
216
199
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Convert a Next.js header/rewrite/redirect source pattern into a regex string.
|
|
219
|
-
*
|
|
220
|
-
* Regex groups in the source (e.g. `(\d+)`) are extracted first, the remaining
|
|
221
|
-
* text is escaped/converted in a **single pass** (avoiding chained `.replace()`
|
|
222
|
-
* which CodeQL flags as incomplete sanitization), then groups are restored.
|
|
223
|
-
*/
|
|
224
|
-
function escapeHeaderSource(source) {
|
|
225
|
-
const S = "";
|
|
226
|
-
const groups = [];
|
|
227
|
-
const withPlaceholders = source.replace(/\(([^)]+)\)/g, (_m, inner) => {
|
|
228
|
-
groups.push(inner);
|
|
229
|
-
return `${S}G${groups.length - 1}${S}`;
|
|
230
|
-
});
|
|
231
|
-
let result = "";
|
|
232
|
-
const re = new RegExp(`${S}G(\\d+)${S}|:[\\w-]+|[.+?*]|[^.+?*:\\uE000]+`, "g");
|
|
233
|
-
let m;
|
|
234
|
-
while ((m = re.exec(withPlaceholders)) !== null) if (m[1] !== void 0) result += `(${groups[Number(m[1])]})`;
|
|
235
|
-
else if (m[0].startsWith(":")) {
|
|
236
|
-
const constraintMatch = withPlaceholders.slice(re.lastIndex).match(new RegExp(`^${S}G(\\d+)${S}`));
|
|
237
|
-
if (constraintMatch) {
|
|
238
|
-
re.lastIndex += constraintMatch[0].length;
|
|
239
|
-
result += `(${groups[Number(constraintMatch[1])]})`;
|
|
240
|
-
} else result += "[^/]+";
|
|
241
|
-
} else switch (m[0]) {
|
|
242
|
-
case ".":
|
|
243
|
-
result += "\\.";
|
|
244
|
-
break;
|
|
245
|
-
case "+":
|
|
246
|
-
result += "\\+";
|
|
247
|
-
break;
|
|
248
|
-
case "?":
|
|
249
|
-
result += "\\?";
|
|
250
|
-
break;
|
|
251
|
-
case "*":
|
|
252
|
-
result += ".*";
|
|
253
|
-
break;
|
|
254
|
-
default: result += m[0];
|
|
255
|
-
}
|
|
256
|
-
return result;
|
|
257
|
-
}
|
|
258
200
|
const _BASEPATH_DEFAULT = {
|
|
259
201
|
basePath: "",
|
|
260
202
|
hadBasePath: true
|
|
@@ -296,13 +238,17 @@ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
|
|
|
296
238
|
const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders);
|
|
297
239
|
const unconsumedRequestHeaders = getUnconsumedMiddlewareRequestHeaders(middlewareHeaders);
|
|
298
240
|
for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-") && key !== "x-middleware-cache" && !unconsumedRequestHeaders.has(key)) delete middlewareHeaders[key];
|
|
299
|
-
if (nextHeaders)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
241
|
+
if (nextHeaders) {
|
|
242
|
+
const source = request;
|
|
243
|
+
request = new Request(request.url, {
|
|
244
|
+
method: request.method,
|
|
245
|
+
headers: nextHeaders,
|
|
246
|
+
body: request.body,
|
|
247
|
+
signal: request.signal,
|
|
248
|
+
duplex: request.body ? "half" : void 0
|
|
249
|
+
});
|
|
250
|
+
attachRequestCfMetadata(request, source);
|
|
251
|
+
}
|
|
306
252
|
return {
|
|
307
253
|
request,
|
|
308
254
|
postMwReqCtx: requestContextFromRequest(request)
|
|
@@ -355,11 +301,11 @@ function matchSingleCondition(condition, ctx) {
|
|
|
355
301
|
}
|
|
356
302
|
/**
|
|
357
303
|
* Return a cached RegExp for a has/missing condition value string, compiling
|
|
358
|
-
* on first use. Returns null if
|
|
359
|
-
*
|
|
304
|
+
* on first use. Returns null if the value is not a valid regex (fall back to
|
|
305
|
+
* exact string comparison).
|
|
360
306
|
*/
|
|
361
307
|
function _cachedConditionRegex(value) {
|
|
362
|
-
return getCachedRegex(_compiledConditionCache, value, () =>
|
|
308
|
+
return getCachedRegex(_compiledConditionCache, value, () => compileRegExp(`^${value}$`));
|
|
363
309
|
}
|
|
364
310
|
/**
|
|
365
311
|
* Check all has/missing conditions for a config rule.
|
|
@@ -384,125 +330,13 @@ function checkHasConditions(has, missing, ctx) {
|
|
|
384
330
|
return collectConditionParams(has, missing, ctx) !== null;
|
|
385
331
|
}
|
|
386
332
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*/
|
|
391
|
-
function extractConstraint(str, re) {
|
|
392
|
-
if (str[re.lastIndex] !== "(") return null;
|
|
393
|
-
const start = re.lastIndex + 1;
|
|
394
|
-
let depth = 1;
|
|
395
|
-
let i = start;
|
|
396
|
-
while (i < str.length && depth > 0) {
|
|
397
|
-
if (str[i] === "(") depth++;
|
|
398
|
-
else if (str[i] === ")") depth--;
|
|
399
|
-
i++;
|
|
400
|
-
}
|
|
401
|
-
if (depth !== 0) return null;
|
|
402
|
-
re.lastIndex = i;
|
|
403
|
-
return str.slice(start, i - 1);
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Match a Next.js config pattern (from redirects/rewrites sources) against a pathname.
|
|
407
|
-
* Returns matched params or null.
|
|
408
|
-
*
|
|
409
|
-
* Supports:
|
|
410
|
-
* :param - matches a single path segment
|
|
411
|
-
* :param* - matches zero or more segments (catch-all)
|
|
412
|
-
* :param+ - matches one or more segments
|
|
413
|
-
* (regex) - inline regex patterns in the source
|
|
414
|
-
* :param(constraint) - named param with inline regex constraint
|
|
415
|
-
*/
|
|
416
|
-
/**
|
|
417
|
-
* Strip a single trailing slash from a pathname for config-source matching.
|
|
418
|
-
*
|
|
419
|
-
* Next.js conditionally appends `(/)?` to rewrite/redirect/header source
|
|
420
|
-
* regexes when `trailingSlash: true` (see Next.js
|
|
421
|
-
* `resolve-rewrites.ts` and `server-utils.ts:checkRewrite`). Rather than
|
|
422
|
-
* threading the trailingSlash flag through every matcher, we unconditionally
|
|
423
|
-
* strip a trailing slash from the incoming pathname. When `trailingSlash: false`
|
|
424
|
-
* the request pipeline emits a normalizing redirect (step 3) before config
|
|
425
|
-
* rewrites/redirects (step 6) ever run, so the pathname is already slash-free;
|
|
426
|
-
* the unconditional strip is defense-in-depth for that ordering. When
|
|
427
|
-
* `trailingSlash: true` it bridges the gap between the canonicalized request
|
|
428
|
-
* path (`/rewrite-1/`) and source patterns written without a trailing slash
|
|
429
|
-
* (`/rewrite-1`).
|
|
430
|
-
*
|
|
431
|
-
* 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.
|
|
432
336
|
*/
|
|
433
337
|
function stripTrailingSlashForConfigMatch(value) {
|
|
434
338
|
return value.length > 1 && value.endsWith("/") ? value.slice(0, -1) : value;
|
|
435
339
|
}
|
|
436
|
-
function configPathEquals(a, b) {
|
|
437
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
438
|
-
}
|
|
439
|
-
function configPathStartsWith(pathname, prefix) {
|
|
440
|
-
return pathname.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase();
|
|
441
|
-
}
|
|
442
|
-
function matchConfigPattern(pathname, pattern) {
|
|
443
|
-
const pathnameHadTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
|
|
444
|
-
pathname = stripTrailingSlashForConfigMatch(pathname);
|
|
445
|
-
if (pathnameHadTrailingSlash) pattern = stripTrailingSlashForConfigMatch(pattern);
|
|
446
|
-
const catchAllAnchor = /:[\w-]+[*+]/.test(pattern);
|
|
447
|
-
const namedParamCount = (pattern.match(/:[\w-]+/g) || []).length;
|
|
448
|
-
if (pattern.includes("(") || pattern.includes("\\") || /:[\w-]+[*+][^/]/.test(pattern) || /:[\w-]+\./.test(pattern) || /[^/]:[\w-]+/.test(pattern) || catchAllAnchor && namedParamCount > 1) try {
|
|
449
|
-
const compiled = getCachedRegex(_compiledPatternCache, pattern, () => {
|
|
450
|
-
const paramNames = [];
|
|
451
|
-
let regexStr = "";
|
|
452
|
-
const tokenRe = /:([\w-]+)|[.]|[^:.]+/g;
|
|
453
|
-
let tok;
|
|
454
|
-
while ((tok = tokenRe.exec(pattern)) !== null) if (tok[1] !== void 0) {
|
|
455
|
-
const name = tok[1];
|
|
456
|
-
const rest = pattern.slice(tokenRe.lastIndex);
|
|
457
|
-
if (rest.startsWith("*") || rest.startsWith("+")) {
|
|
458
|
-
const quantifier = rest[0];
|
|
459
|
-
tokenRe.lastIndex += 1;
|
|
460
|
-
const constraint = extractConstraint(pattern, tokenRe);
|
|
461
|
-
paramNames.push(name);
|
|
462
|
-
if (constraint !== null) regexStr += `(${constraint})`;
|
|
463
|
-
else regexStr += quantifier === "*" ? "(.*)" : "(.+)";
|
|
464
|
-
} else {
|
|
465
|
-
const constraint = extractConstraint(pattern, tokenRe);
|
|
466
|
-
paramNames.push(name);
|
|
467
|
-
regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
|
|
468
|
-
}
|
|
469
|
-
} else if (tok[0] === ".") regexStr += "\\.";
|
|
470
|
-
else regexStr += tok[0];
|
|
471
|
-
const re = safeRegExp("^" + regexStr + "$", "i");
|
|
472
|
-
return re ? {
|
|
473
|
-
re,
|
|
474
|
-
paramNames
|
|
475
|
-
} : null;
|
|
476
|
-
});
|
|
477
|
-
if (!compiled) return null;
|
|
478
|
-
const match = compiled.re.exec(pathname);
|
|
479
|
-
if (!match) return null;
|
|
480
|
-
const params = Object.create(null);
|
|
481
|
-
for (let i = 0; i < compiled.paramNames.length; i++) params[compiled.paramNames[i]] = match[i + 1] ?? "";
|
|
482
|
-
return params;
|
|
483
|
-
} catch {}
|
|
484
|
-
const catchAllMatch = pattern.match(/:([\w-]+)(\*|\+)$/);
|
|
485
|
-
if (catchAllMatch) {
|
|
486
|
-
const prefix = pattern.slice(0, pattern.lastIndexOf(":"));
|
|
487
|
-
const paramName = catchAllMatch[1];
|
|
488
|
-
const isPlus = catchAllMatch[2] === "+";
|
|
489
|
-
const prefixNoSlash = prefix.replace(/\/$/, "");
|
|
490
|
-
if (!configPathStartsWith(pathname, prefixNoSlash)) return null;
|
|
491
|
-
const charAfter = pathname[prefixNoSlash.length];
|
|
492
|
-
if (charAfter !== void 0 && charAfter !== "/") return null;
|
|
493
|
-
const rest = pathname.slice(prefixNoSlash.length);
|
|
494
|
-
if (isPlus && (!rest || rest === "/")) return null;
|
|
495
|
-
let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
|
|
496
|
-
return { [paramName]: restValue };
|
|
497
|
-
}
|
|
498
|
-
const parts = pattern.split("/");
|
|
499
|
-
const pathParts = pathname.split("/");
|
|
500
|
-
if (parts.length !== pathParts.length) return null;
|
|
501
|
-
const params = Object.create(null);
|
|
502
|
-
for (let i = 0; i < parts.length; i++) if (parts[i].startsWith(":")) params[parts[i].slice(1)] = pathParts[i];
|
|
503
|
-
else if (!configPathEquals(parts[i], pathParts[i])) return null;
|
|
504
|
-
return params;
|
|
505
|
-
}
|
|
506
340
|
/**
|
|
507
341
|
* Apply redirect rules from next.config.js.
|
|
508
342
|
* Returns the redirect info if a redirect was matched, or null.
|
|
@@ -545,7 +379,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
545
379
|
let localeMatch = null;
|
|
546
380
|
let localeMatchIndex = Infinity;
|
|
547
381
|
if (index.localeStatic.size > 0) {
|
|
548
|
-
const noLocaleBucket = index.localeStatic.get(normalizedPathname
|
|
382
|
+
const noLocaleBucket = index.localeStatic.get(foldCase(normalizedPathname));
|
|
549
383
|
if (noLocaleBucket) for (const entry of noLocaleBucket) {
|
|
550
384
|
if (!entry.optional) continue;
|
|
551
385
|
if (entry.originalIndex >= localeMatchIndex) continue;
|
|
@@ -565,7 +399,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
565
399
|
if (slashTwo !== -1) {
|
|
566
400
|
const suffix = normalizedPathname.slice(slashTwo);
|
|
567
401
|
const localePart = normalizedPathname.slice(1, slashTwo);
|
|
568
|
-
const localeBucket = index.localeStatic.get(suffix
|
|
402
|
+
const localeBucket = index.localeStatic.get(foldCase(suffix));
|
|
569
403
|
if (localeBucket) for (const entry of localeBucket) {
|
|
570
404
|
if (entry.originalIndex >= localeMatchIndex) continue;
|
|
571
405
|
if (!entry.altRe.test(localePart)) continue;
|
|
@@ -610,17 +444,17 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
610
444
|
function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT, paramsPathname = pathname, onRuleSourceMatch) {
|
|
611
445
|
for (const rewrite of rewrites) {
|
|
612
446
|
if (!shouldEvaluateRule(rewrite.basePath, basePathState)) continue;
|
|
613
|
-
const
|
|
614
|
-
if (
|
|
447
|
+
const matched = execConfigSource(pathname, rewrite.source);
|
|
448
|
+
if (matched) {
|
|
615
449
|
onRuleSourceMatch?.(rewrite);
|
|
616
|
-
const params = paramsPathname === pathname ?
|
|
450
|
+
const { params, unmatched } = paramsPathname === pathname ? matched : execConfigSource(paramsPathname, rewrite.source) ?? matched;
|
|
617
451
|
const conditionParams = rewrite.has || rewrite.missing ? collectConditionParams(rewrite.has, rewrite.missing, ctx) : _emptyParams();
|
|
618
452
|
if (!conditionParams) continue;
|
|
619
453
|
const rewriteParams = {
|
|
620
454
|
...params,
|
|
621
455
|
...conditionParams
|
|
622
456
|
};
|
|
623
|
-
return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams);
|
|
457
|
+
return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams, unmatched.filter((key) => !(key in conditionParams)));
|
|
624
458
|
}
|
|
625
459
|
}
|
|
626
460
|
return null;
|
|
@@ -668,16 +502,18 @@ function resolveRedirectMatch(redirect, params) {
|
|
|
668
502
|
/**
|
|
669
503
|
* Match Next.js's rewrite-specific prepareDestination behavior: source params
|
|
670
504
|
* that are not consumed by the destination path/host are exposed to the target
|
|
671
|
-
* 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.
|
|
672
507
|
*
|
|
673
508
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
674
509
|
*/
|
|
675
|
-
function substituteAndSanitizeRewriteDestination(destination, params) {
|
|
510
|
+
function substituteAndSanitizeRewriteDestination(destination, params, unmatchedParams) {
|
|
676
511
|
const rewritten = substituteAndSanitizeDestination(destination, params, "rewrite");
|
|
677
|
-
|
|
512
|
+
const matchedParams = unmatchedParams.length === 0 ? params : Object.fromEntries(Object.entries(params).filter(([key]) => !unmatchedParams.includes(key)));
|
|
513
|
+
if (!shouldAppendRewriteParamsToQuery(destination, matchedParams)) return rewritten;
|
|
678
514
|
const existingQueryKeys = getDestinationQueryKeys(destination);
|
|
679
515
|
const paramsToAppend = [];
|
|
680
|
-
for (const [key, value] of Object.entries(
|
|
516
|
+
for (const [key, value] of Object.entries(matchedParams)) {
|
|
681
517
|
if (key === "nextInternalLocale" || existingQueryKeys.has(key)) continue;
|
|
682
518
|
paramsToAppend.push([key, value]);
|
|
683
519
|
}
|
|
@@ -893,23 +729,118 @@ async function proxyExternalRequest(request, externalUrl) {
|
|
|
893
729
|
* when evaluating headers, so this parameter is required.
|
|
894
730
|
*/
|
|
895
731
|
function matchHeaders(pathname, headers, ctx, basePathState = _BASEPATH_DEFAULT, onRuleSourceMatch) {
|
|
896
|
-
const pathnameHadTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
|
|
897
|
-
pathname = stripTrailingSlashForConfigMatch(pathname);
|
|
898
732
|
const result = [];
|
|
899
733
|
for (const rule of headers) {
|
|
900
734
|
if (!shouldEvaluateRule(rule.basePath, basePathState)) continue;
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
if (
|
|
735
|
+
const compiledSource = compileConfigSource(rule.source);
|
|
736
|
+
const sourceMatch = compiledSource?.re.exec(pathname);
|
|
737
|
+
if (compiledSource && sourceMatch) {
|
|
904
738
|
onRuleSourceMatch?.(rule);
|
|
905
739
|
if (rule.has || rule.missing) {
|
|
906
740
|
if (!checkHasConditions(rule.has, rule.missing, ctx)) continue;
|
|
907
741
|
}
|
|
908
|
-
|
|
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;
|
|
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;
|
|
909
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");
|
|
910
836
|
}
|
|
911
837
|
return result;
|
|
912
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
|
+
}
|
|
913
844
|
/**
|
|
914
845
|
* Escape a string for inclusion in a regex character class / alternation.
|
|
915
846
|
* Mirrors `escape-string-regexp` semantics used by Next.js's processRoutes.
|
|
@@ -931,17 +862,19 @@ function _escapeRegexString(value) {
|
|
|
931
862
|
* starts with `/:nextInternalLocale(en|sv|nl)` so that locale-prefixed
|
|
932
863
|
* URLs match. For redirects only, a second variant prefixed with
|
|
933
864
|
* `/${defaultLocale}` is also emitted, matching Next.js exactly.
|
|
934
|
-
* -
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
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.
|
|
945
878
|
*
|
|
946
879
|
* Destinations that are local (start with `/`) are similarly rewritten with
|
|
947
880
|
* `/:nextInternalLocale` for the locale-capture variant so the locale
|
|
@@ -980,9 +913,9 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
|
|
|
980
913
|
};
|
|
981
914
|
if ("destination" in internalRoute && internalDestination !== void 0) internalRoute.destination = internalDestination;
|
|
982
915
|
out.push(internalRoute);
|
|
983
|
-
out.push(r);
|
|
916
|
+
if (type !== "header") out.push(r);
|
|
984
917
|
}
|
|
985
918
|
return out;
|
|
986
919
|
}
|
|
987
920
|
//#endregion
|
|
988
|
-
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
|