vinext 0.0.10 → 0.0.12
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/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/config/config-matchers.d.ts +11 -2
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +167 -45
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +6 -4
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -51
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -2
- package/dist/init.js.map +1 -1
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +34 -9
- package/dist/server/app-dev-server.js.map +1 -1
- package/dist/server/app-router-entry.d.ts.map +1 -1
- package/dist/server/app-router-entry.js +8 -1
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +14 -2
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts.map +1 -1
- package/dist/server/middleware-codegen.js +13 -7
- package/dist/server/middleware-codegen.js.map +1 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +34 -13
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +26 -3
- package/dist/server/prod-server.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts.map +1 -1
- package/dist/shims/fetch-cache.js +153 -55
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head.d.ts.map +1 -1
- package/dist/shims/head.js +4 -1
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +4 -2
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/navigation.js +2 -2
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.d.ts +1 -1
- package/dist/shims/router.d.ts.map +1 -1
- package/dist/shims/router.js +2 -2
- package/dist/shims/router.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormaliz
|
|
|
10
10
|
import { normalizePath } from "./server/normalize-path.js";
|
|
11
11
|
import { findInstrumentationFile, runInstrumentation } from "./server/instrumentation.js";
|
|
12
12
|
import { validateDevRequest } from "./server/dev-origin-check.js";
|
|
13
|
-
import { safeRegExp, isExternalUrl, proxyExternalRequest } from "./config/config-matchers.js";
|
|
13
|
+
import { safeRegExp, escapeHeaderSource, isExternalUrl, proxyExternalRequest } from "./config/config-matchers.js";
|
|
14
14
|
import { scanMetadataFiles } from "./server/metadata-routes.js";
|
|
15
15
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
16
16
|
import MagicString from "magic-string";
|
|
@@ -577,7 +577,11 @@ export async function runMiddleware(request) {
|
|
|
577
577
|
|
|
578
578
|
// Normalize pathname before matching to prevent path-confusion bypasses
|
|
579
579
|
// (percent-encoding like /%61dmin, double slashes like /dashboard//settings).
|
|
580
|
-
var
|
|
580
|
+
var decodedPathname;
|
|
581
|
+
try { decodedPathname = decodeURIComponent(url.pathname); } catch (e) {
|
|
582
|
+
return { continue: false, response: new Response("Bad Request", { status: 400 }) };
|
|
583
|
+
}
|
|
584
|
+
var normalizedPathname = __normalizePath(decodedPathname);
|
|
581
585
|
|
|
582
586
|
if (!matchesMiddleware(normalizedPathname, matcher)) return { continue: true };
|
|
583
587
|
|
|
@@ -896,7 +900,8 @@ function parseCookieLocaleFromHeader(cookieHeader) {
|
|
|
896
900
|
if (!i18nConfig || !cookieHeader) return null;
|
|
897
901
|
const match = cookieHeader.match(/(?:^|;\\s*)NEXT_LOCALE=([^;]*)/);
|
|
898
902
|
if (!match) return null;
|
|
899
|
-
|
|
903
|
+
var value;
|
|
904
|
+
try { value = decodeURIComponent(match[1].trim()); } catch (e) { return null; }
|
|
900
905
|
if (i18nConfig.locales.indexOf(value) !== -1) return value;
|
|
901
906
|
return null;
|
|
902
907
|
}
|
|
@@ -1115,7 +1120,7 @@ export async function renderPage(request, url, manifest) {
|
|
|
1115
1120
|
if (result && result.props) pageProps = result.props;
|
|
1116
1121
|
if (result && result.redirect) {
|
|
1117
1122
|
var gsspStatus = result.redirect.statusCode != null ? result.redirect.statusCode : (result.redirect.permanent ? 308 : 307);
|
|
1118
|
-
return new Response(null, { status: gsspStatus, headers: { Location: result.redirect.destination } });
|
|
1123
|
+
return new Response(null, { status: gsspStatus, headers: { Location: sanitizeDestinationLocal(result.redirect.destination) } });
|
|
1119
1124
|
}
|
|
1120
1125
|
if (result && result.notFound) {
|
|
1121
1126
|
return new Response("404", { status: 404 });
|
|
@@ -1174,7 +1179,7 @@ export async function renderPage(request, url, manifest) {
|
|
|
1174
1179
|
if (result && result.props) pageProps = result.props;
|
|
1175
1180
|
if (result && result.redirect) {
|
|
1176
1181
|
var gspStatus = result.redirect.statusCode != null ? result.redirect.statusCode : (result.redirect.permanent ? 308 : 307);
|
|
1177
|
-
return new Response(null, { status: gspStatus, headers: { Location: result.redirect.destination } });
|
|
1182
|
+
return new Response(null, { status: gspStatus, headers: { Location: sanitizeDestinationLocal(result.redirect.destination) } });
|
|
1178
1183
|
}
|
|
1179
1184
|
if (result && result.notFound) {
|
|
1180
1185
|
return new Response("404", { status: 404 });
|
|
@@ -1395,6 +1400,9 @@ ${middlewareExportCode}
|
|
|
1395
1400
|
const loaderEntries = pageRoutes.map((r) => {
|
|
1396
1401
|
const absPath = r.filePath.replace(/\\/g, "/");
|
|
1397
1402
|
const nextFormatPattern = pagesPatternToNextFormat(r.pattern);
|
|
1403
|
+
// JSON.stringify safely escapes quotes, backslashes, and special chars in
|
|
1404
|
+
// both the route pattern and the absolute file path.
|
|
1405
|
+
// lgtm[js/bad-code-sanitization]
|
|
1398
1406
|
return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
|
|
1399
1407
|
});
|
|
1400
1408
|
const appFileBase = path.join(pagesDir, "_app").replace(/\\/g, "/");
|
|
@@ -2183,7 +2191,15 @@ hydrate();
|
|
|
2183
2191
|
// Normalize the pathname to prevent path-confusion attacks.
|
|
2184
2192
|
// decodeURIComponent prevents /%61dmin bypassing /admin matchers.
|
|
2185
2193
|
// normalizePath collapses // and resolves . / .. segments.
|
|
2186
|
-
|
|
2194
|
+
try {
|
|
2195
|
+
pathname = normalizePath(decodeURIComponent(pathname));
|
|
2196
|
+
}
|
|
2197
|
+
catch {
|
|
2198
|
+
// Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of crashing.
|
|
2199
|
+
res.writeHead(400);
|
|
2200
|
+
res.end("Bad Request");
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2187
2203
|
// Strip basePath prefix from URL for route matching.
|
|
2188
2204
|
// All internal routing uses basePath-free paths.
|
|
2189
2205
|
//
|
|
@@ -2990,6 +3006,29 @@ function getNextPublicEnvDefines() {
|
|
|
2990
3006
|
}
|
|
2991
3007
|
return defines;
|
|
2992
3008
|
}
|
|
3009
|
+
/**
|
|
3010
|
+
* If the current position in `str` starts with a parenthesized group, consume
|
|
3011
|
+
* it and advance `re.lastIndex` past the closing `)`. Returns the group
|
|
3012
|
+
* contents or null if no group is present.
|
|
3013
|
+
*/
|
|
3014
|
+
function extractConstraint(str, re) {
|
|
3015
|
+
if (str[re.lastIndex] !== "(")
|
|
3016
|
+
return null;
|
|
3017
|
+
const start = re.lastIndex + 1;
|
|
3018
|
+
let depth = 1;
|
|
3019
|
+
let i = start;
|
|
3020
|
+
while (i < str.length && depth > 0) {
|
|
3021
|
+
if (str[i] === "(")
|
|
3022
|
+
depth++;
|
|
3023
|
+
else if (str[i] === ")")
|
|
3024
|
+
depth--;
|
|
3025
|
+
i++;
|
|
3026
|
+
}
|
|
3027
|
+
if (depth !== 0)
|
|
3028
|
+
return null;
|
|
3029
|
+
re.lastIndex = i;
|
|
3030
|
+
return str.slice(start, i - 1);
|
|
3031
|
+
}
|
|
2993
3032
|
/**
|
|
2994
3033
|
* Match a Next.js route pattern (e.g. "/blog/:slug", "/docs/:path*") against a pathname.
|
|
2995
3034
|
* Returns matched params or null.
|
|
@@ -3016,28 +3055,44 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3016
3055
|
// :param* -> (.*)
|
|
3017
3056
|
// :param+ -> (.+)
|
|
3018
3057
|
const paramNames = [];
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3058
|
+
// Single-pass conversion with procedural suffix handling. The tokenizer
|
|
3059
|
+
// matches only simple, non-overlapping tokens; quantifier/constraint
|
|
3060
|
+
// suffixes after :param are consumed procedurally to avoid polynomial
|
|
3061
|
+
// backtracking in the regex engine.
|
|
3062
|
+
let regexStr = "";
|
|
3063
|
+
const tokenRe = /:(\w+)|[.]|[^:.]+/g; // lgtm[js/redos] — alternatives are non-overlapping (`:` and `.` excluded from `[^:.]+`)
|
|
3064
|
+
let tok;
|
|
3065
|
+
while ((tok = tokenRe.exec(pattern)) !== null) {
|
|
3066
|
+
if (tok[1] !== undefined) {
|
|
3067
|
+
const name = tok[1];
|
|
3068
|
+
const rest = pattern.slice(tokenRe.lastIndex);
|
|
3069
|
+
// Check for quantifier (* or +) with optional constraint
|
|
3070
|
+
if (rest.startsWith("*") || rest.startsWith("+")) {
|
|
3071
|
+
const quantifier = rest[0];
|
|
3072
|
+
tokenRe.lastIndex += 1;
|
|
3073
|
+
const constraint = extractConstraint(pattern, tokenRe);
|
|
3074
|
+
paramNames.push(name);
|
|
3075
|
+
if (constraint !== null) {
|
|
3076
|
+
regexStr += `(${constraint})`;
|
|
3077
|
+
}
|
|
3078
|
+
else {
|
|
3079
|
+
regexStr += quantifier === "*" ? "(.*)" : "(.+)";
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
else {
|
|
3083
|
+
// Check for inline constraint without quantifier
|
|
3084
|
+
const constraint = extractConstraint(pattern, tokenRe);
|
|
3085
|
+
paramNames.push(name);
|
|
3086
|
+
regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
else if (tok[0] === ".") {
|
|
3090
|
+
regexStr += "\\.";
|
|
3091
|
+
}
|
|
3092
|
+
else {
|
|
3093
|
+
regexStr += tok[0];
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3041
3096
|
const re = safeRegExp("^" + regexStr + "$");
|
|
3042
3097
|
if (!re)
|
|
3043
3098
|
return null;
|
|
@@ -3067,7 +3122,12 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3067
3122
|
if (isPlus && (!rest || rest === "/"))
|
|
3068
3123
|
return null;
|
|
3069
3124
|
// For :path* zero segments is fine
|
|
3070
|
-
|
|
3125
|
+
let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
|
|
3126
|
+
try {
|
|
3127
|
+
restValue = decodeURIComponent(restValue);
|
|
3128
|
+
}
|
|
3129
|
+
catch { /* malformed percent-encoding */ }
|
|
3130
|
+
return { [paramName]: restValue };
|
|
3071
3131
|
}
|
|
3072
3132
|
// Simple segment-based matching for exact patterns and :param
|
|
3073
3133
|
const parts = pattern.split("/");
|
|
@@ -3086,14 +3146,14 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3086
3146
|
return params;
|
|
3087
3147
|
}
|
|
3088
3148
|
/**
|
|
3089
|
-
* Sanitize a redirect/rewrite destination by collapsing leading
|
|
3090
|
-
* for non-external URLs
|
|
3149
|
+
* Sanitize a redirect/rewrite destination by collapsing leading slashes and
|
|
3150
|
+
* backslashes to a single "/" for non-external URLs. Browsers interpret "\"
|
|
3151
|
+
* as "/" in URL contexts, so "\/evil.com" becomes "//evil.com" (protocol-relative).
|
|
3091
3152
|
*/
|
|
3092
3153
|
function sanitizeDestinationLocal(dest) {
|
|
3093
3154
|
if (dest.startsWith("http://") || dest.startsWith("https://"))
|
|
3094
3155
|
return dest;
|
|
3095
|
-
|
|
3096
|
-
dest = dest.replace(/^\/\/+/, "/");
|
|
3156
|
+
dest = dest.replace(/^[\\/]+/, "/");
|
|
3097
3157
|
return dest;
|
|
3098
3158
|
}
|
|
3099
3159
|
/**
|
|
@@ -3204,24 +3264,7 @@ function applyRewrites(pathname, rewrites) {
|
|
|
3204
3264
|
*/
|
|
3205
3265
|
function applyHeaders(pathname, res, headers) {
|
|
3206
3266
|
for (const rule of headers) {
|
|
3207
|
-
|
|
3208
|
-
// Strategy: extract regex groups first, process the rest, then restore groups.
|
|
3209
|
-
const groups = [];
|
|
3210
|
-
const withPlaceholders = rule.source.replace(/\(([^)]+)\)/g, (_m, inner) => {
|
|
3211
|
-
groups.push(inner);
|
|
3212
|
-
return `___GROUP_${groups.length - 1}___`;
|
|
3213
|
-
});
|
|
3214
|
-
const escaped = withPlaceholders
|
|
3215
|
-
// Escape dots and other metacharacters
|
|
3216
|
-
.replace(/\./g, "\\.")
|
|
3217
|
-
.replace(/\+/g, "\\+")
|
|
3218
|
-
.replace(/\?/g, "\\?")
|
|
3219
|
-
// Convert glob * to .*
|
|
3220
|
-
.replace(/\*/g, ".*")
|
|
3221
|
-
// Convert :param to [^/]+
|
|
3222
|
-
.replace(/:\w+/g, "[^/]+")
|
|
3223
|
-
// Restore regex groups (contents are untouched)
|
|
3224
|
-
.replace(/___GROUP_(\d+)___/g, (_m, idx) => `(${groups[Number(idx)]})`);
|
|
3267
|
+
const escaped = escapeHeaderSource(rule.source);
|
|
3225
3268
|
const sourceRegex = safeRegExp("^" + escaped + "$");
|
|
3226
3269
|
if (sourceRegex && sourceRegex.test(pathname)) {
|
|
3227
3270
|
for (const header of rule.headers) {
|