vinext 0.0.10 → 0.0.11
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 +8 -0
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +163 -42
- 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 +78 -48
- package/dist/index.js.map +1 -1
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +13 -4
- package/dist/server/app-dev-server.js.map +1 -1
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +13 -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 +25 -12
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +6 -1
- 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 +6 -0
- 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/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";
|
|
@@ -1115,7 +1115,7 @@ export async function renderPage(request, url, manifest) {
|
|
|
1115
1115
|
if (result && result.props) pageProps = result.props;
|
|
1116
1116
|
if (result && result.redirect) {
|
|
1117
1117
|
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 } });
|
|
1118
|
+
return new Response(null, { status: gsspStatus, headers: { Location: sanitizeDestinationLocal(result.redirect.destination) } });
|
|
1119
1119
|
}
|
|
1120
1120
|
if (result && result.notFound) {
|
|
1121
1121
|
return new Response("404", { status: 404 });
|
|
@@ -1174,7 +1174,7 @@ export async function renderPage(request, url, manifest) {
|
|
|
1174
1174
|
if (result && result.props) pageProps = result.props;
|
|
1175
1175
|
if (result && result.redirect) {
|
|
1176
1176
|
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 } });
|
|
1177
|
+
return new Response(null, { status: gspStatus, headers: { Location: sanitizeDestinationLocal(result.redirect.destination) } });
|
|
1178
1178
|
}
|
|
1179
1179
|
if (result && result.notFound) {
|
|
1180
1180
|
return new Response("404", { status: 404 });
|
|
@@ -1395,6 +1395,9 @@ ${middlewareExportCode}
|
|
|
1395
1395
|
const loaderEntries = pageRoutes.map((r) => {
|
|
1396
1396
|
const absPath = r.filePath.replace(/\\/g, "/");
|
|
1397
1397
|
const nextFormatPattern = pagesPatternToNextFormat(r.pattern);
|
|
1398
|
+
// JSON.stringify safely escapes quotes, backslashes, and special chars in
|
|
1399
|
+
// both the route pattern and the absolute file path.
|
|
1400
|
+
// lgtm[js/bad-code-sanitization]
|
|
1398
1401
|
return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
|
|
1399
1402
|
});
|
|
1400
1403
|
const appFileBase = path.join(pagesDir, "_app").replace(/\\/g, "/");
|
|
@@ -2990,6 +2993,29 @@ function getNextPublicEnvDefines() {
|
|
|
2990
2993
|
}
|
|
2991
2994
|
return defines;
|
|
2992
2995
|
}
|
|
2996
|
+
/**
|
|
2997
|
+
* If the current position in `str` starts with a parenthesized group, consume
|
|
2998
|
+
* it and advance `re.lastIndex` past the closing `)`. Returns the group
|
|
2999
|
+
* contents or null if no group is present.
|
|
3000
|
+
*/
|
|
3001
|
+
function extractConstraint(str, re) {
|
|
3002
|
+
if (str[re.lastIndex] !== "(")
|
|
3003
|
+
return null;
|
|
3004
|
+
const start = re.lastIndex + 1;
|
|
3005
|
+
let depth = 1;
|
|
3006
|
+
let i = start;
|
|
3007
|
+
while (i < str.length && depth > 0) {
|
|
3008
|
+
if (str[i] === "(")
|
|
3009
|
+
depth++;
|
|
3010
|
+
else if (str[i] === ")")
|
|
3011
|
+
depth--;
|
|
3012
|
+
i++;
|
|
3013
|
+
}
|
|
3014
|
+
if (depth !== 0)
|
|
3015
|
+
return null;
|
|
3016
|
+
re.lastIndex = i;
|
|
3017
|
+
return str.slice(start, i - 1);
|
|
3018
|
+
}
|
|
2993
3019
|
/**
|
|
2994
3020
|
* Match a Next.js route pattern (e.g. "/blog/:slug", "/docs/:path*") against a pathname.
|
|
2995
3021
|
* Returns matched params or null.
|
|
@@ -3016,28 +3042,44 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3016
3042
|
// :param* -> (.*)
|
|
3017
3043
|
// :param+ -> (.+)
|
|
3018
3044
|
const paramNames = [];
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3045
|
+
// Single-pass conversion with procedural suffix handling. The tokenizer
|
|
3046
|
+
// matches only simple, non-overlapping tokens; quantifier/constraint
|
|
3047
|
+
// suffixes after :param are consumed procedurally to avoid polynomial
|
|
3048
|
+
// backtracking in the regex engine.
|
|
3049
|
+
let regexStr = "";
|
|
3050
|
+
const tokenRe = /:(\w+)|[.]|[^:.]+/g; // lgtm[js/redos] — alternatives are non-overlapping (`:` and `.` excluded from `[^:.]+`)
|
|
3051
|
+
let tok;
|
|
3052
|
+
while ((tok = tokenRe.exec(pattern)) !== null) {
|
|
3053
|
+
if (tok[1] !== undefined) {
|
|
3054
|
+
const name = tok[1];
|
|
3055
|
+
const rest = pattern.slice(tokenRe.lastIndex);
|
|
3056
|
+
// Check for quantifier (* or +) with optional constraint
|
|
3057
|
+
if (rest.startsWith("*") || rest.startsWith("+")) {
|
|
3058
|
+
const quantifier = rest[0];
|
|
3059
|
+
tokenRe.lastIndex += 1;
|
|
3060
|
+
const constraint = extractConstraint(pattern, tokenRe);
|
|
3061
|
+
paramNames.push(name);
|
|
3062
|
+
if (constraint !== null) {
|
|
3063
|
+
regexStr += `(${constraint})`;
|
|
3064
|
+
}
|
|
3065
|
+
else {
|
|
3066
|
+
regexStr += quantifier === "*" ? "(.*)" : "(.+)";
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
else {
|
|
3070
|
+
// Check for inline constraint without quantifier
|
|
3071
|
+
const constraint = extractConstraint(pattern, tokenRe);
|
|
3072
|
+
paramNames.push(name);
|
|
3073
|
+
regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
else if (tok[0] === ".") {
|
|
3077
|
+
regexStr += "\\.";
|
|
3078
|
+
}
|
|
3079
|
+
else {
|
|
3080
|
+
regexStr += tok[0];
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3041
3083
|
const re = safeRegExp("^" + regexStr + "$");
|
|
3042
3084
|
if (!re)
|
|
3043
3085
|
return null;
|
|
@@ -3067,7 +3109,12 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3067
3109
|
if (isPlus && (!rest || rest === "/"))
|
|
3068
3110
|
return null;
|
|
3069
3111
|
// For :path* zero segments is fine
|
|
3070
|
-
|
|
3112
|
+
let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
|
|
3113
|
+
try {
|
|
3114
|
+
restValue = decodeURIComponent(restValue);
|
|
3115
|
+
}
|
|
3116
|
+
catch { /* malformed percent-encoding */ }
|
|
3117
|
+
return { [paramName]: restValue };
|
|
3071
3118
|
}
|
|
3072
3119
|
// Simple segment-based matching for exact patterns and :param
|
|
3073
3120
|
const parts = pattern.split("/");
|
|
@@ -3086,14 +3133,14 @@ export function matchConfigPattern(pathname, pattern) {
|
|
|
3086
3133
|
return params;
|
|
3087
3134
|
}
|
|
3088
3135
|
/**
|
|
3089
|
-
* Sanitize a redirect/rewrite destination by collapsing leading
|
|
3090
|
-
* for non-external URLs
|
|
3136
|
+
* Sanitize a redirect/rewrite destination by collapsing leading slashes and
|
|
3137
|
+
* backslashes to a single "/" for non-external URLs. Browsers interpret "\"
|
|
3138
|
+
* as "/" in URL contexts, so "\/evil.com" becomes "//evil.com" (protocol-relative).
|
|
3091
3139
|
*/
|
|
3092
3140
|
function sanitizeDestinationLocal(dest) {
|
|
3093
3141
|
if (dest.startsWith("http://") || dest.startsWith("https://"))
|
|
3094
3142
|
return dest;
|
|
3095
|
-
|
|
3096
|
-
dest = dest.replace(/^\/\/+/, "/");
|
|
3143
|
+
dest = dest.replace(/^[\\/]+/, "/");
|
|
3097
3144
|
return dest;
|
|
3098
3145
|
}
|
|
3099
3146
|
/**
|
|
@@ -3204,24 +3251,7 @@ function applyRewrites(pathname, rewrites) {
|
|
|
3204
3251
|
*/
|
|
3205
3252
|
function applyHeaders(pathname, res, headers) {
|
|
3206
3253
|
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)]})`);
|
|
3254
|
+
const escaped = escapeHeaderSource(rule.source);
|
|
3225
3255
|
const sourceRegex = safeRegExp("^" + escaped + "$");
|
|
3226
3256
|
if (sourceRegex && sourceRegex.test(pathname)) {
|
|
3227
3257
|
for (const header of rule.headers) {
|