vinext 0.0.8 → 0.0.10
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 +30 -1
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/entry.js +1 -15
- package/dist/client/entry.js.map +1 -1
- package/dist/client/validate-module-path.d.ts +15 -0
- package/dist/client/validate-module-path.d.ts.map +1 -0
- package/dist/client/validate-module-path.js +31 -0
- package/dist/client/validate-module-path.js.map +1 -0
- package/dist/config/config-matchers.d.ts +12 -0
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +28 -0
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/dotenv.d.ts +40 -0
- package/dist/config/dotenv.d.ts.map +1 -0
- package/dist/config/dotenv.js +100 -0
- package/dist/config/dotenv.js.map +1 -0
- package/dist/config/next-config.d.ts +4 -0
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +16 -8
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -111
- package/dist/index.js.map +1 -1
- package/dist/server/api-handler.d.ts.map +1 -1
- package/dist/server/api-handler.js +2 -1
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-dev-server.d.ts +2 -0
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +292 -155
- 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 +16 -3
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/dev-origin-check.d.ts +61 -0
- package/dist/server/dev-origin-check.d.ts.map +1 -0
- package/dist/server/dev-origin-check.js +164 -0
- package/dist/server/dev-origin-check.js.map +1 -0
- package/dist/server/dev-server.d.ts +0 -2
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +379 -372
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts +32 -2
- package/dist/server/image-optimization.d.ts.map +1 -1
- package/dist/server/image-optimization.js +110 -9
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts +41 -0
- package/dist/server/middleware-codegen.d.ts.map +1 -0
- package/dist/server/middleware-codegen.js +181 -0
- package/dist/server/middleware-codegen.js.map +1 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +12 -7
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/normalize-path.d.ts +22 -0
- package/dist/server/normalize-path.d.ts.map +1 -0
- package/dist/server/normalize-path.js +50 -0
- package/dist/server/normalize-path.js.map +1 -0
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +89 -25
- package/dist/server/prod-server.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +7 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -1
- package/dist/shims/cache-runtime.js +19 -15
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +8 -0
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +20 -15
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +2 -3
- package/dist/shims/fetch-cache.d.ts.map +1 -1
- package/dist/shims/fetch-cache.js +74 -9
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head-state.d.ts +6 -1
- package/dist/shims/head-state.d.ts.map +1 -1
- package/dist/shims/head-state.js +18 -15
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/headers.d.ts +9 -13
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +26 -47
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts.map +1 -1
- package/dist/shims/image.js +11 -2
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +6 -1
- package/dist/shims/navigation-state.d.ts.map +1 -1
- package/dist/shims/navigation-state.js +20 -29
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.js +2 -2
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router-state.d.ts +6 -1
- package/dist/shims/router-state.d.ts.map +1 -1
- package/dist/shims/router-state.js +16 -21
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts.map +1 -1
- package/dist/shims/router.js +19 -6
- package/dist/shims/router.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared middleware matching code generator.
|
|
3
|
+
*
|
|
4
|
+
* Both the App Router RSC entry (app-dev-server.ts) and the Pages Router
|
|
5
|
+
* production entry (index.ts) need middleware matching logic inlined as
|
|
6
|
+
* generated JavaScript strings. This module provides a single source of
|
|
7
|
+
* truth to prevent the implementations from diverging.
|
|
8
|
+
*
|
|
9
|
+
* The regex detection guard (checking for "(" or "\\") is critical.
|
|
10
|
+
* Without it, dot-escaping corrupts regex patterns like
|
|
11
|
+
* /((?!api|_next).*), causing middleware to silently skip paths.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Returns the generated JavaScript source for `__isSafeRegex` and `__safeRegExp`.
|
|
15
|
+
*
|
|
16
|
+
* @param style - "modern" emits const/let (for RSC entry), "es5" emits var (for prod entry)
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateSafeRegExpCode(style?: "modern" | "es5"): string;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the generated JavaScript source for `__normalizePath`.
|
|
21
|
+
*
|
|
22
|
+
* This must be kept in sync with `normalizePath()` in `normalize-path.ts`.
|
|
23
|
+
* The inline version is used by codegen entries that can't import modules.
|
|
24
|
+
*
|
|
25
|
+
* @param style - "modern" emits const/let, "es5" emits var
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateNormalizePathCode(style?: "modern" | "es5"): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the generated JavaScript source for middleware pattern matching.
|
|
30
|
+
*
|
|
31
|
+
* This includes:
|
|
32
|
+
* - `matchMiddlewarePattern(pathname, pattern)` — matches a single pattern
|
|
33
|
+
* - `matchesMiddleware(pathname, matcher)` — matches the full matcher config
|
|
34
|
+
*
|
|
35
|
+
* The generated code depends on `__safeRegExp` being defined in the same scope
|
|
36
|
+
* (use `generateSafeRegExpCode` to emit it).
|
|
37
|
+
*
|
|
38
|
+
* @param style - "modern" emits const/let/arrow functions, "es5" emits var/function
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateMiddlewareMatcherCode(style?: "modern" | "es5"): string;
|
|
41
|
+
//# sourceMappingURL=middleware-codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-codegen.d.ts","sourceRoot":"","sources":["../../src/server/middleware-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,QAAQ,GAAG,KAAgB,GAAG,MAAM,CAuEjF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,GAAE,QAAQ,GAAG,KAAgB,GAAG,MAAM,CA2BpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,GAAE,QAAQ,GAAG,KAAgB,GAAG,MAAM,CA2CxF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared middleware matching code generator.
|
|
3
|
+
*
|
|
4
|
+
* Both the App Router RSC entry (app-dev-server.ts) and the Pages Router
|
|
5
|
+
* production entry (index.ts) need middleware matching logic inlined as
|
|
6
|
+
* generated JavaScript strings. This module provides a single source of
|
|
7
|
+
* truth to prevent the implementations from diverging.
|
|
8
|
+
*
|
|
9
|
+
* The regex detection guard (checking for "(" or "\\") is critical.
|
|
10
|
+
* Without it, dot-escaping corrupts regex patterns like
|
|
11
|
+
* /((?!api|_next).*), causing middleware to silently skip paths.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Returns the generated JavaScript source for `__isSafeRegex` and `__safeRegExp`.
|
|
15
|
+
*
|
|
16
|
+
* @param style - "modern" emits const/let (for RSC entry), "es5" emits var (for prod entry)
|
|
17
|
+
*/
|
|
18
|
+
export function generateSafeRegExpCode(style = "modern") {
|
|
19
|
+
const v = style === "modern" ? "const" : "var";
|
|
20
|
+
const l = style === "modern" ? "let" : "var";
|
|
21
|
+
return `
|
|
22
|
+
function __isSafeRegex(pattern) {
|
|
23
|
+
${v} quantifierAtDepth = [];
|
|
24
|
+
${l} depth = 0;
|
|
25
|
+
${l} i = 0;
|
|
26
|
+
while (i < pattern.length) {
|
|
27
|
+
${v} ch = pattern[i];
|
|
28
|
+
if (ch === "\\\\") { i += 2; continue; }
|
|
29
|
+
if (ch === "[") {
|
|
30
|
+
i++;
|
|
31
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
32
|
+
if (pattern[i] === "\\\\") i++;
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
i++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === "(") {
|
|
39
|
+
depth++;
|
|
40
|
+
if (quantifierAtDepth.length <= depth) quantifierAtDepth.push(false);
|
|
41
|
+
else quantifierAtDepth[depth] = false;
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === ")") {
|
|
46
|
+
${v} hadQ = depth > 0 && quantifierAtDepth[depth];
|
|
47
|
+
if (depth > 0) depth--;
|
|
48
|
+
${v} next = pattern[i + 1];
|
|
49
|
+
if (next === "+" || next === "*" || next === "{") {
|
|
50
|
+
if (hadQ) return false;
|
|
51
|
+
if (depth >= 0 && depth < quantifierAtDepth.length) quantifierAtDepth[depth] = true;
|
|
52
|
+
}
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === "+" || ch === "*") {
|
|
57
|
+
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
58
|
+
i++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === "?") {
|
|
62
|
+
${v} prev = i > 0 ? pattern[i - 1] : "";
|
|
63
|
+
if (prev !== "+" && prev !== "*" && prev !== "?" && prev !== "}") {
|
|
64
|
+
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ch === "{") {
|
|
70
|
+
${l} j = i + 1;
|
|
71
|
+
while (j < pattern.length && /[\\d,]/.test(pattern[j])) j++;
|
|
72
|
+
if (j < pattern.length && pattern[j] === "}" && j > i + 1) {
|
|
73
|
+
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
74
|
+
i = j + 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
function __safeRegExp(pattern, flags) {
|
|
83
|
+
if (!__isSafeRegex(pattern)) {
|
|
84
|
+
console.warn("[vinext] Ignoring potentially unsafe regex pattern (ReDoS risk): " + pattern);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try { return new RegExp(pattern, flags); } catch { return null; }
|
|
88
|
+
}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns the generated JavaScript source for `__normalizePath`.
|
|
92
|
+
*
|
|
93
|
+
* This must be kept in sync with `normalizePath()` in `normalize-path.ts`.
|
|
94
|
+
* The inline version is used by codegen entries that can't import modules.
|
|
95
|
+
*
|
|
96
|
+
* @param style - "modern" emits const/let, "es5" emits var
|
|
97
|
+
*/
|
|
98
|
+
export function generateNormalizePathCode(style = "modern") {
|
|
99
|
+
const v = style === "modern" ? "const" : "var";
|
|
100
|
+
const l = style === "modern" ? "let" : "var";
|
|
101
|
+
return `
|
|
102
|
+
function __normalizePath(pathname) {
|
|
103
|
+
if (
|
|
104
|
+
pathname === "/" ||
|
|
105
|
+
(pathname.length > 1 &&
|
|
106
|
+
pathname[0] === "/" &&
|
|
107
|
+
!pathname.includes("//") &&
|
|
108
|
+
!pathname.includes("/./") &&
|
|
109
|
+
!pathname.includes("/../") &&
|
|
110
|
+
!pathname.endsWith("/.") &&
|
|
111
|
+
!pathname.endsWith("/.."))
|
|
112
|
+
) {
|
|
113
|
+
return pathname;
|
|
114
|
+
}
|
|
115
|
+
${v} segments = pathname.split("/");
|
|
116
|
+
${v} resolved = [];
|
|
117
|
+
for (${l} i = 0; i < segments.length; i++) {
|
|
118
|
+
${v} seg = segments[i];
|
|
119
|
+
if (seg === "" || seg === ".") continue;
|
|
120
|
+
if (seg === "..") { resolved.pop(); }
|
|
121
|
+
else { resolved.push(seg); }
|
|
122
|
+
}
|
|
123
|
+
return "/" + resolved.join("/");
|
|
124
|
+
}`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns the generated JavaScript source for middleware pattern matching.
|
|
128
|
+
*
|
|
129
|
+
* This includes:
|
|
130
|
+
* - `matchMiddlewarePattern(pathname, pattern)` — matches a single pattern
|
|
131
|
+
* - `matchesMiddleware(pathname, matcher)` — matches the full matcher config
|
|
132
|
+
*
|
|
133
|
+
* The generated code depends on `__safeRegExp` being defined in the same scope
|
|
134
|
+
* (use `generateSafeRegExpCode` to emit it).
|
|
135
|
+
*
|
|
136
|
+
* @param style - "modern" emits const/let/arrow functions, "es5" emits var/function
|
|
137
|
+
*/
|
|
138
|
+
export function generateMiddlewareMatcherCode(style = "modern") {
|
|
139
|
+
const v = style === "modern" ? "const" : "var";
|
|
140
|
+
const fn = style === "modern"
|
|
141
|
+
? (params, body) => `(${params}) => { ${body} }`
|
|
142
|
+
: (params, body) => `function(${params}) { ${body} }`;
|
|
143
|
+
// The pattern matching logic must be identical to matchPattern() in
|
|
144
|
+
// packages/vinext/src/server/middleware.ts. Any changes here must be
|
|
145
|
+
// mirrored there and vice versa.
|
|
146
|
+
return `
|
|
147
|
+
function matchMiddlewarePattern(pathname, pattern) {
|
|
148
|
+
// Regex patterns: if the pattern contains "(" or "\\" it's a regex —
|
|
149
|
+
// pass it through to RegExp directly WITHOUT dot-escaping.
|
|
150
|
+
// This guard prevents regex pattern corruption from dot-escaping.
|
|
151
|
+
if (pattern.includes("(") || pattern.includes("\\\\")) {
|
|
152
|
+
${v} re = __safeRegExp("^" + pattern + "$");
|
|
153
|
+
if (re) return re.test(pathname);
|
|
154
|
+
}
|
|
155
|
+
// Convert Next.js named-param patterns to regex.
|
|
156
|
+
// Escape dots FIRST (before replacements that produce regex metacharacters).
|
|
157
|
+
${v} regexStr = pattern
|
|
158
|
+
.replace(/\\./g, "\\\\.")
|
|
159
|
+
.replace(/\\/:([\\w]+)\\*/g, "(?:/.*)?")
|
|
160
|
+
.replace(/\\/:([\\w]+)\\+/g, "(?:/.+)")
|
|
161
|
+
.replace(/:([\\w]+)/g, "([^/]+)");
|
|
162
|
+
${v} re2 = __safeRegExp("^" + regexStr + "$");
|
|
163
|
+
return re2 ? re2.test(pathname) : pathname === pattern;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function matchesMiddleware(pathname, matcher) {
|
|
167
|
+
if (!matcher) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
${v} patterns = [];
|
|
171
|
+
if (typeof matcher === "string") { patterns.push(matcher); }
|
|
172
|
+
else if (Array.isArray(matcher)) {
|
|
173
|
+
for (${v} m of matcher) {
|
|
174
|
+
if (typeof m === "string") patterns.push(m);
|
|
175
|
+
else if (m && typeof m === "object" && "source" in m) patterns.push(m.source);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return patterns.some(${fn("p", "return matchMiddlewarePattern(pathname, p);")});
|
|
179
|
+
}`;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=middleware-codegen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-codegen.js","sourceRoot":"","sources":["../../src/server/middleware-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA0B,QAAQ;IACvE,MAAM,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/C,MAAM,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7C,OAAO;;IAEL,CAAC;IACD,CAAC;IACD,CAAC;;MAEC,CAAC;;;;;;;;;;;;;;;;;;;QAmBC,CAAC;;QAED,CAAC;;;;;;;;;;;;;;QAcD,CAAC;;;;;;;;QAQD,CAAC;;;;;;;;;;;;;;;;;;EAkBP,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAA0B,QAAQ;IAC1E,MAAM,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/C,MAAM,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7C,OAAO;;;;;;;;;;;;;;IAcL,CAAC;IACD,CAAC;SACI,CAAC;MACJ,CAAC;;;;;;EAML,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAA0B,QAAQ;IAC9E,MAAM,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/C,MAAM,EAAE,GAAG,KAAK,KAAK,QAAQ;QAC3B,CAAC,CAAC,CAAC,MAAc,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,MAAM,UAAU,IAAI,IAAI;QAChE,CAAC,CAAC,CAAC,MAAc,EAAE,IAAY,EAAE,EAAE,CAAC,YAAY,MAAM,OAAO,IAAI,IAAI,CAAC;IAExE,oEAAoE;IACpE,qEAAqE;IACrE,iCAAiC;IACjC,OAAO;;;;;;MAMH,CAAC;;;;;IAKH,CAAC;;;;;IAKD,CAAC;;;;;;;;IAQD,CAAC;;;WAGM,CAAC;;;;;yBAKa,EAAE,CAAC,GAAG,EAAE,6CAA6C,CAAC;EAC7E,CAAC;AACH,CAAC","sourcesContent":["/**\n * Shared middleware matching code generator.\n *\n * Both the App Router RSC entry (app-dev-server.ts) and the Pages Router\n * production entry (index.ts) need middleware matching logic inlined as\n * generated JavaScript strings. This module provides a single source of\n * truth to prevent the implementations from diverging.\n *\n * The regex detection guard (checking for \"(\" or \"\\\\\") is critical.\n * Without it, dot-escaping corrupts regex patterns like\n * /((?!api|_next).*), causing middleware to silently skip paths.\n */\n\n/**\n * Returns the generated JavaScript source for `__isSafeRegex` and `__safeRegExp`.\n *\n * @param style - \"modern\" emits const/let (for RSC entry), \"es5\" emits var (for prod entry)\n */\nexport function generateSafeRegExpCode(style: \"modern\" | \"es5\" = \"modern\"): string {\n const v = style === \"modern\" ? \"const\" : \"var\";\n const l = style === \"modern\" ? \"let\" : \"var\";\n return `\nfunction __isSafeRegex(pattern) {\n ${v} quantifierAtDepth = [];\n ${l} depth = 0;\n ${l} i = 0;\n while (i < pattern.length) {\n ${v} ch = pattern[i];\n if (ch === \"\\\\\\\\\") { i += 2; continue; }\n if (ch === \"[\") {\n i++;\n while (i < pattern.length && pattern[i] !== \"]\") {\n if (pattern[i] === \"\\\\\\\\\") i++;\n i++;\n }\n i++;\n continue;\n }\n if (ch === \"(\") {\n depth++;\n if (quantifierAtDepth.length <= depth) quantifierAtDepth.push(false);\n else quantifierAtDepth[depth] = false;\n i++;\n continue;\n }\n if (ch === \")\") {\n ${v} hadQ = depth > 0 && quantifierAtDepth[depth];\n if (depth > 0) depth--;\n ${v} next = pattern[i + 1];\n if (next === \"+\" || next === \"*\" || next === \"{\") {\n if (hadQ) return false;\n if (depth >= 0 && depth < quantifierAtDepth.length) quantifierAtDepth[depth] = true;\n }\n i++;\n continue;\n }\n if (ch === \"+\" || ch === \"*\") {\n if (depth > 0) quantifierAtDepth[depth] = true;\n i++;\n continue;\n }\n if (ch === \"?\") {\n ${v} prev = i > 0 ? pattern[i - 1] : \"\";\n if (prev !== \"+\" && prev !== \"*\" && prev !== \"?\" && prev !== \"}\") {\n if (depth > 0) quantifierAtDepth[depth] = true;\n }\n i++;\n continue;\n }\n if (ch === \"{\") {\n ${l} j = i + 1;\n while (j < pattern.length && /[\\\\d,]/.test(pattern[j])) j++;\n if (j < pattern.length && pattern[j] === \"}\" && j > i + 1) {\n if (depth > 0) quantifierAtDepth[depth] = true;\n i = j + 1;\n continue;\n }\n }\n i++;\n }\n return true;\n}\nfunction __safeRegExp(pattern, flags) {\n if (!__isSafeRegex(pattern)) {\n console.warn(\"[vinext] Ignoring potentially unsafe regex pattern (ReDoS risk): \" + pattern);\n return null;\n }\n try { return new RegExp(pattern, flags); } catch { return null; }\n}`;\n}\n\n/**\n * Returns the generated JavaScript source for `__normalizePath`.\n *\n * This must be kept in sync with `normalizePath()` in `normalize-path.ts`.\n * The inline version is used by codegen entries that can't import modules.\n *\n * @param style - \"modern\" emits const/let, \"es5\" emits var\n */\nexport function generateNormalizePathCode(style: \"modern\" | \"es5\" = \"modern\"): string {\n const v = style === \"modern\" ? \"const\" : \"var\";\n const l = style === \"modern\" ? \"let\" : \"var\";\n return `\nfunction __normalizePath(pathname) {\n if (\n pathname === \"/\" ||\n (pathname.length > 1 &&\n pathname[0] === \"/\" &&\n !pathname.includes(\"//\") &&\n !pathname.includes(\"/./\") &&\n !pathname.includes(\"/../\") &&\n !pathname.endsWith(\"/.\") &&\n !pathname.endsWith(\"/..\"))\n ) {\n return pathname;\n }\n ${v} segments = pathname.split(\"/\");\n ${v} resolved = [];\n for (${l} i = 0; i < segments.length; i++) {\n ${v} seg = segments[i];\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") { resolved.pop(); }\n else { resolved.push(seg); }\n }\n return \"/\" + resolved.join(\"/\");\n}`;\n}\n\n/**\n * Returns the generated JavaScript source for middleware pattern matching.\n *\n * This includes:\n * - `matchMiddlewarePattern(pathname, pattern)` — matches a single pattern\n * - `matchesMiddleware(pathname, matcher)` — matches the full matcher config\n *\n * The generated code depends on `__safeRegExp` being defined in the same scope\n * (use `generateSafeRegExpCode` to emit it).\n *\n * @param style - \"modern\" emits const/let/arrow functions, \"es5\" emits var/function\n */\nexport function generateMiddlewareMatcherCode(style: \"modern\" | \"es5\" = \"modern\"): string {\n const v = style === \"modern\" ? \"const\" : \"var\";\n const fn = style === \"modern\"\n ? (params: string, body: string) => `(${params}) => { ${body} }`\n : (params: string, body: string) => `function(${params}) { ${body} }`;\n\n // The pattern matching logic must be identical to matchPattern() in\n // packages/vinext/src/server/middleware.ts. Any changes here must be\n // mirrored there and vice versa.\n return `\nfunction matchMiddlewarePattern(pathname, pattern) {\n // Regex patterns: if the pattern contains \"(\" or \"\\\\\" it's a regex —\n // pass it through to RegExp directly WITHOUT dot-escaping.\n // This guard prevents regex pattern corruption from dot-escaping.\n if (pattern.includes(\"(\") || pattern.includes(\"\\\\\\\\\")) {\n ${v} re = __safeRegExp(\"^\" + pattern + \"$\");\n if (re) return re.test(pathname);\n }\n // Convert Next.js named-param patterns to regex.\n // Escape dots FIRST (before replacements that produce regex metacharacters).\n ${v} regexStr = pattern\n .replace(/\\\\./g, \"\\\\\\\\.\")\n .replace(/\\\\/:([\\\\w]+)\\\\*/g, \"(?:/.*)?\")\n .replace(/\\\\/:([\\\\w]+)\\\\+/g, \"(?:/.+)\")\n .replace(/:([\\\\w]+)/g, \"([^/]+)\");\n ${v} re2 = __safeRegExp(\"^\" + regexStr + \"$\");\n return re2 ? re2.test(pathname) : pathname === pattern;\n}\n\nfunction matchesMiddleware(pathname, matcher) {\n if (!matcher) {\n return true;\n }\n ${v} patterns = [];\n if (typeof matcher === \"string\") { patterns.push(matcher); }\n else if (Array.isArray(matcher)) {\n for (${v} m of matcher) {\n if (typeof m === \"string\") patterns.push(m);\n else if (m && typeof m === \"object\" && \"source\" in m) patterns.push(m.source);\n }\n }\n return patterns.some(${fn(\"p\", \"return matchMiddlewarePattern(pathname, p);\")});\n}`;\n}\n\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AA+B1C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB9D;AAED,qDAAqD;AACrD,KAAK,aAAa,GACd,MAAM,GACN,MAAM,EAAE,GACR;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,EAAE,CAAC;AAE1F;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,GAAG,SAAS,GACjC,OAAO,CAqBT;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAwBvE;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAC;IAClB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,gBAAgB,CAAC,CAyG3B"}
|
|
@@ -21,6 +21,7 @@ import fs from "node:fs";
|
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { NextRequest } from "../shims/server.js";
|
|
23
23
|
import { safeRegExp } from "../config/config-matchers.js";
|
|
24
|
+
import { normalizePath } from "./normalize-path.js";
|
|
24
25
|
/**
|
|
25
26
|
* Possible proxy/middleware file names.
|
|
26
27
|
* proxy.ts (Next.js 16) is checked first, then middleware.ts (deprecated).
|
|
@@ -74,11 +75,9 @@ export function findMiddlewareFile(root) {
|
|
|
74
75
|
*/
|
|
75
76
|
export function matchesMiddleware(pathname, matcher) {
|
|
76
77
|
if (!matcher) {
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
!pathname.includes(".") &&
|
|
81
|
-
pathname !== "/favicon.ico");
|
|
78
|
+
// Next.js default: middleware runs on ALL paths when no matcher is configured.
|
|
79
|
+
// Users opt out of specific paths by configuring a matcher pattern.
|
|
80
|
+
return true;
|
|
82
81
|
}
|
|
83
82
|
const patterns = [];
|
|
84
83
|
if (typeof matcher === "string") {
|
|
@@ -150,7 +149,10 @@ export async function runMiddleware(server, middlewarePath, request) {
|
|
|
150
149
|
const config = mod.config;
|
|
151
150
|
const matcher = config?.matcher;
|
|
152
151
|
const url = new URL(request.url);
|
|
153
|
-
|
|
152
|
+
// Normalize the pathname before middleware matching to prevent bypasses
|
|
153
|
+
// via percent-encoding (/%61dmin → /admin) or double slashes (/dashboard//settings).
|
|
154
|
+
const normalizedPathname = normalizePath(decodeURIComponent(url.pathname));
|
|
155
|
+
if (!matchesMiddleware(normalizedPathname, matcher)) {
|
|
154
156
|
return { continue: true };
|
|
155
157
|
}
|
|
156
158
|
// Wrap in NextRequest so middleware gets .nextUrl, .cookies, .geo, .ip, etc.
|
|
@@ -162,9 +164,12 @@ export async function runMiddleware(server, middlewarePath, request) {
|
|
|
162
164
|
}
|
|
163
165
|
catch (e) {
|
|
164
166
|
console.error("[vinext] Middleware error:", e);
|
|
167
|
+
const message = process.env.NODE_ENV === "production"
|
|
168
|
+
? "Internal Server Error"
|
|
169
|
+
: "Middleware Error: " + (e?.message ?? String(e));
|
|
165
170
|
return {
|
|
166
171
|
continue: false,
|
|
167
|
-
response: new Response(
|
|
172
|
+
response: new Response(message, {
|
|
168
173
|
status: 500,
|
|
169
174
|
}),
|
|
170
175
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * proxy.ts / middleware.ts runner\n *\n * Loads and executes the user's proxy.ts (Next.js 16) or middleware.ts file\n * before routing. Runs in Node (not Edge Runtime), per the vinext design.\n *\n * In Next.js 16, proxy.ts replaces middleware.ts:\n * - proxy.ts: default export function, runs on Node.js runtime\n * - middleware.ts: deprecated but still supported for Edge runtime use cases\n *\n * The proxy/middleware receives a NextRequest and can:\n * - Return NextResponse.next() to continue to the route\n * - Return NextResponse.redirect() to redirect\n * - Return NextResponse.rewrite() to rewrite the URL\n * - Set/modify headers and cookies\n * - Return a Response directly (e.g., for auth guards)\n *\n * Supports the `config.matcher` export for path filtering.\n */\n\nimport type { ViteDevServer } from \"vite\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { NextRequest } from \"../shims/server.js\";\nimport { safeRegExp } from \"../config/config-matchers.js\";\n\n/**\n * Possible proxy/middleware file names.\n * proxy.ts (Next.js 16) is checked first, then middleware.ts (deprecated).\n */\nconst PROXY_FILES = [\n \"proxy.ts\",\n \"proxy.js\",\n \"proxy.mjs\",\n \"src/proxy.ts\",\n \"src/proxy.js\",\n \"src/proxy.mjs\",\n];\n\nconst MIDDLEWARE_FILES = [\n \"middleware.ts\",\n \"middleware.tsx\",\n \"middleware.js\",\n \"middleware.mjs\",\n \"src/middleware.ts\",\n \"src/middleware.tsx\",\n \"src/middleware.js\",\n \"src/middleware.mjs\",\n];\n\n/**\n * Find the proxy or middleware file in the project root.\n * Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.\n * If middleware.ts is found, logs a deprecation warning.\n */\nexport function findMiddlewareFile(root: string): string | null {\n // Check proxy.ts first (Next.js 16 replacement for middleware.ts)\n for (const file of PROXY_FILES) {\n const fullPath = path.join(root, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n\n // Fall back to middleware.ts (deprecated in Next.js 16)\n for (const file of MIDDLEWARE_FILES) {\n const fullPath = path.join(root, file);\n if (fs.existsSync(fullPath)) {\n console.warn(\n \"[vinext] middleware.ts is deprecated in Next.js 16. \" +\n \"Rename to proxy.ts and export a default function.\",\n );\n return fullPath;\n }\n }\n return null;\n}\n\n/** Matcher pattern from middleware config export. */\ntype MatcherConfig =\n | string\n | string[]\n | { source: string; regexp?: string; locale?: boolean; has?: any[]; missing?: any[] }[];\n\n/**\n * Check if a pathname matches the middleware matcher config.\n * If no matcher is configured, middleware runs on all paths\n * except static files and internal Next.js paths.\n */\nexport function matchesMiddleware(\n pathname: string,\n matcher: MatcherConfig | undefined,\n): boolean {\n if (!matcher) {\n // Default: match all paths except static files, _next, and favicon\n return (\n !pathname.startsWith(\"/_next\") &&\n !pathname.startsWith(\"/api\") &&\n !pathname.includes(\".\") &&\n pathname !== \"/favicon.ico\"\n );\n }\n\n const patterns: string[] = [];\n if (typeof matcher === \"string\") {\n patterns.push(matcher);\n } else if (Array.isArray(matcher)) {\n for (const m of matcher) {\n if (typeof m === \"string\") {\n patterns.push(m);\n } else if (m && typeof m === \"object\" && \"source\" in m) {\n patterns.push(m.source);\n }\n }\n }\n\n return patterns.some((pattern) => matchPattern(pathname, pattern));\n}\n\n/**\n * Match a single pattern against a pathname.\n * Supports Next.js matcher patterns:\n * /about -> exact match\n * /dashboard/:path* -> prefix match with params\n * /api/:path+ -> one or more segments\n * /((?!api|_next).*) -> regex patterns\n */\nexport function matchPattern(pathname: string, pattern: string): boolean {\n // Handle regex patterns (starts with /)\n if (pattern.includes(\"(\") || pattern.includes(\"\\\\\")) {\n const re = safeRegExp(\"^\" + pattern + \"$\");\n if (re) return re.test(pathname);\n // Fall through to simple matching\n }\n\n // Convert Next.js path patterns to regex.\n // Escape dots FIRST (before replacements that produce regex metacharacters\n // like .* and .+ which must not be escaped).\n const regexStr = pattern\n // Escape dots in the literal path segments\n .replace(/\\./g, \"\\\\.\")\n // /:path* -> optionally match slash + zero or more segments\n .replace(/\\/:(\\w+)\\*/g, \"(?:/.*)?\")\n // /:path+ -> match slash + one or more segments\n .replace(/\\/:(\\w+)\\+/g, \"(?:/.+)\")\n // :param -> match one segment\n .replace(/:(\\w+)/g, \"([^/]+)\");\n\n const re = safeRegExp(\"^\" + regexStr + \"$\");\n if (re) return re.test(pathname);\n return pathname === pattern;\n}\n\n/** Result of running middleware. */\nexport interface MiddlewareResult {\n /** Whether to continue to the route handler. */\n continue: boolean;\n /** If set, redirect to this URL. */\n redirectUrl?: string;\n /** HTTP status for redirect (default 307). */\n redirectStatus?: number;\n /** If set, rewrite to this URL (internal). */\n rewriteUrl?: string;\n /** HTTP status for rewrite (e.g. 403 from NextResponse.rewrite(url, { status: 403 })). */\n rewriteStatus?: number;\n /** Headers to set on the response. */\n responseHeaders?: Headers;\n /** If the middleware returned a full Response, use it directly. */\n response?: Response;\n}\n\n/**\n * Load and execute middleware for a given request.\n *\n * @param server - Vite dev server (for SSR module loading)\n * @param middlewarePath - Absolute path to the middleware file\n * @param request - The incoming Request object\n * @returns Middleware result describing what action to take\n */\nexport async function runMiddleware(\n server: ViteDevServer,\n middlewarePath: string,\n request: Request,\n): Promise<MiddlewareResult> {\n // Load the middleware module via Vite's SSR module loader\n const mod = await server.ssrLoadModule(middlewarePath);\n\n // Accept: default export, named \"proxy\" (Next.js 16), or named \"middleware\"\n const middlewareFn = mod.default ?? mod.proxy ?? mod.middleware;\n if (typeof middlewareFn !== \"function\") {\n // No proxy/middleware function exported — continue as normal\n return { continue: true };\n }\n\n // Check matcher config\n const config = mod.config;\n const matcher = config?.matcher;\n const url = new URL(request.url);\n\n if (!matchesMiddleware(url.pathname, matcher)) {\n return { continue: true };\n }\n\n // Wrap in NextRequest so middleware gets .nextUrl, .cookies, .geo, .ip, etc.\n const nextRequest = request instanceof NextRequest ? request : new NextRequest(request);\n\n // Execute the middleware\n let response: Response | undefined;\n try {\n response = await middlewareFn(nextRequest);\n } catch (e: any) {\n console.error(\"[vinext] Middleware error:\", e);\n return {\n continue: false,\n response: new Response(\"Middleware Error: \" + (e?.message ?? String(e)), {\n status: 500,\n }),\n };\n }\n\n // No response = continue\n if (!response) {\n return { continue: true };\n }\n\n // Check for x-middleware-next header (NextResponse.next())\n if (response.headers.get(\"x-middleware-next\") === \"1\") {\n // Continue to the route, but apply any headers the middleware set\n const responseHeaders = new Headers();\n for (const [key, value] of response.headers) {\n if (\n key !== \"x-middleware-next\" &&\n key !== \"x-middleware-rewrite\"\n ) {\n responseHeaders.set(key, value);\n }\n }\n return { continue: true, responseHeaders };\n }\n\n // Check for redirect (3xx status)\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"Location\") ?? response.headers.get(\"location\");\n if (location) {\n return {\n continue: false,\n redirectUrl: location,\n redirectStatus: response.status,\n };\n }\n }\n\n // Check for rewrite (x-middleware-rewrite header)\n const rewriteUrl = response.headers.get(\"x-middleware-rewrite\");\n if (rewriteUrl) {\n // Continue to the route but with a rewritten URL\n const responseHeaders = new Headers();\n for (const [key, value] of response.headers) {\n if (key !== \"x-middleware-rewrite\") {\n responseHeaders.set(key, value);\n }\n }\n // Parse the rewrite URL — may be absolute or relative\n let rewritePath: string;\n try {\n const rewriteParsed = new URL(rewriteUrl, request.url);\n rewritePath = rewriteParsed.pathname + rewriteParsed.search;\n } catch {\n rewritePath = rewriteUrl;\n }\n return {\n continue: true,\n rewriteUrl: rewritePath,\n rewriteStatus: response.status !== 200 ? response.status : undefined,\n responseHeaders,\n };\n }\n\n // Middleware returned a full Response (e.g., blocking, custom body)\n return { continue: false, response };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * proxy.ts / middleware.ts runner\n *\n * Loads and executes the user's proxy.ts (Next.js 16) or middleware.ts file\n * before routing. Runs in Node (not Edge Runtime), per the vinext design.\n *\n * In Next.js 16, proxy.ts replaces middleware.ts:\n * - proxy.ts: default export function, runs on Node.js runtime\n * - middleware.ts: deprecated but still supported for Edge runtime use cases\n *\n * The proxy/middleware receives a NextRequest and can:\n * - Return NextResponse.next() to continue to the route\n * - Return NextResponse.redirect() to redirect\n * - Return NextResponse.rewrite() to rewrite the URL\n * - Set/modify headers and cookies\n * - Return a Response directly (e.g., for auth guards)\n *\n * Supports the `config.matcher` export for path filtering.\n */\n\nimport type { ViteDevServer } from \"vite\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { NextRequest } from \"../shims/server.js\";\nimport { safeRegExp } from \"../config/config-matchers.js\";\nimport { normalizePath } from \"./normalize-path.js\";\n\n/**\n * Possible proxy/middleware file names.\n * proxy.ts (Next.js 16) is checked first, then middleware.ts (deprecated).\n */\nconst PROXY_FILES = [\n \"proxy.ts\",\n \"proxy.js\",\n \"proxy.mjs\",\n \"src/proxy.ts\",\n \"src/proxy.js\",\n \"src/proxy.mjs\",\n];\n\nconst MIDDLEWARE_FILES = [\n \"middleware.ts\",\n \"middleware.tsx\",\n \"middleware.js\",\n \"middleware.mjs\",\n \"src/middleware.ts\",\n \"src/middleware.tsx\",\n \"src/middleware.js\",\n \"src/middleware.mjs\",\n];\n\n/**\n * Find the proxy or middleware file in the project root.\n * Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.\n * If middleware.ts is found, logs a deprecation warning.\n */\nexport function findMiddlewareFile(root: string): string | null {\n // Check proxy.ts first (Next.js 16 replacement for middleware.ts)\n for (const file of PROXY_FILES) {\n const fullPath = path.join(root, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n\n // Fall back to middleware.ts (deprecated in Next.js 16)\n for (const file of MIDDLEWARE_FILES) {\n const fullPath = path.join(root, file);\n if (fs.existsSync(fullPath)) {\n console.warn(\n \"[vinext] middleware.ts is deprecated in Next.js 16. \" +\n \"Rename to proxy.ts and export a default function.\",\n );\n return fullPath;\n }\n }\n return null;\n}\n\n/** Matcher pattern from middleware config export. */\ntype MatcherConfig =\n | string\n | string[]\n | { source: string; regexp?: string; locale?: boolean; has?: any[]; missing?: any[] }[];\n\n/**\n * Check if a pathname matches the middleware matcher config.\n * If no matcher is configured, middleware runs on all paths\n * except static files and internal Next.js paths.\n */\nexport function matchesMiddleware(\n pathname: string,\n matcher: MatcherConfig | undefined,\n): boolean {\n if (!matcher) {\n // Next.js default: middleware runs on ALL paths when no matcher is configured.\n // Users opt out of specific paths by configuring a matcher pattern.\n return true;\n }\n\n const patterns: string[] = [];\n if (typeof matcher === \"string\") {\n patterns.push(matcher);\n } else if (Array.isArray(matcher)) {\n for (const m of matcher) {\n if (typeof m === \"string\") {\n patterns.push(m);\n } else if (m && typeof m === \"object\" && \"source\" in m) {\n patterns.push(m.source);\n }\n }\n }\n\n return patterns.some((pattern) => matchPattern(pathname, pattern));\n}\n\n/**\n * Match a single pattern against a pathname.\n * Supports Next.js matcher patterns:\n * /about -> exact match\n * /dashboard/:path* -> prefix match with params\n * /api/:path+ -> one or more segments\n * /((?!api|_next).*) -> regex patterns\n */\nexport function matchPattern(pathname: string, pattern: string): boolean {\n // Handle regex patterns (starts with /)\n if (pattern.includes(\"(\") || pattern.includes(\"\\\\\")) {\n const re = safeRegExp(\"^\" + pattern + \"$\");\n if (re) return re.test(pathname);\n // Fall through to simple matching\n }\n\n // Convert Next.js path patterns to regex.\n // Escape dots FIRST (before replacements that produce regex metacharacters\n // like .* and .+ which must not be escaped).\n const regexStr = pattern\n // Escape dots in the literal path segments\n .replace(/\\./g, \"\\\\.\")\n // /:path* -> optionally match slash + zero or more segments\n .replace(/\\/:(\\w+)\\*/g, \"(?:/.*)?\")\n // /:path+ -> match slash + one or more segments\n .replace(/\\/:(\\w+)\\+/g, \"(?:/.+)\")\n // :param -> match one segment\n .replace(/:(\\w+)/g, \"([^/]+)\");\n\n const re = safeRegExp(\"^\" + regexStr + \"$\");\n if (re) return re.test(pathname);\n return pathname === pattern;\n}\n\n/** Result of running middleware. */\nexport interface MiddlewareResult {\n /** Whether to continue to the route handler. */\n continue: boolean;\n /** If set, redirect to this URL. */\n redirectUrl?: string;\n /** HTTP status for redirect (default 307). */\n redirectStatus?: number;\n /** If set, rewrite to this URL (internal). */\n rewriteUrl?: string;\n /** HTTP status for rewrite (e.g. 403 from NextResponse.rewrite(url, { status: 403 })). */\n rewriteStatus?: number;\n /** Headers to set on the response. */\n responseHeaders?: Headers;\n /** If the middleware returned a full Response, use it directly. */\n response?: Response;\n}\n\n/**\n * Load and execute middleware for a given request.\n *\n * @param server - Vite dev server (for SSR module loading)\n * @param middlewarePath - Absolute path to the middleware file\n * @param request - The incoming Request object\n * @returns Middleware result describing what action to take\n */\nexport async function runMiddleware(\n server: ViteDevServer,\n middlewarePath: string,\n request: Request,\n): Promise<MiddlewareResult> {\n // Load the middleware module via Vite's SSR module loader\n const mod = await server.ssrLoadModule(middlewarePath);\n\n // Accept: default export, named \"proxy\" (Next.js 16), or named \"middleware\"\n const middlewareFn = mod.default ?? mod.proxy ?? mod.middleware;\n if (typeof middlewareFn !== \"function\") {\n // No proxy/middleware function exported — continue as normal\n return { continue: true };\n }\n\n // Check matcher config\n const config = mod.config;\n const matcher = config?.matcher;\n const url = new URL(request.url);\n\n // Normalize the pathname before middleware matching to prevent bypasses\n // via percent-encoding (/%61dmin → /admin) or double slashes (/dashboard//settings).\n const normalizedPathname = normalizePath(decodeURIComponent(url.pathname));\n\n if (!matchesMiddleware(normalizedPathname, matcher)) {\n return { continue: true };\n }\n\n // Wrap in NextRequest so middleware gets .nextUrl, .cookies, .geo, .ip, etc.\n const nextRequest = request instanceof NextRequest ? request : new NextRequest(request);\n\n // Execute the middleware\n let response: Response | undefined;\n try {\n response = await middlewareFn(nextRequest);\n } catch (e: any) {\n console.error(\"[vinext] Middleware error:\", e);\n const message =\n process.env.NODE_ENV === \"production\"\n ? \"Internal Server Error\"\n : \"Middleware Error: \" + (e?.message ?? String(e));\n return {\n continue: false,\n response: new Response(message, {\n status: 500,\n }),\n };\n }\n\n // No response = continue\n if (!response) {\n return { continue: true };\n }\n\n // Check for x-middleware-next header (NextResponse.next())\n if (response.headers.get(\"x-middleware-next\") === \"1\") {\n // Continue to the route, but apply any headers the middleware set\n const responseHeaders = new Headers();\n for (const [key, value] of response.headers) {\n if (\n key !== \"x-middleware-next\" &&\n key !== \"x-middleware-rewrite\"\n ) {\n responseHeaders.set(key, value);\n }\n }\n return { continue: true, responseHeaders };\n }\n\n // Check for redirect (3xx status)\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"Location\") ?? response.headers.get(\"location\");\n if (location) {\n return {\n continue: false,\n redirectUrl: location,\n redirectStatus: response.status,\n };\n }\n }\n\n // Check for rewrite (x-middleware-rewrite header)\n const rewriteUrl = response.headers.get(\"x-middleware-rewrite\");\n if (rewriteUrl) {\n // Continue to the route but with a rewritten URL\n const responseHeaders = new Headers();\n for (const [key, value] of response.headers) {\n if (key !== \"x-middleware-rewrite\") {\n responseHeaders.set(key, value);\n }\n }\n // Parse the rewrite URL — may be absolute or relative\n let rewritePath: string;\n try {\n const rewriteParsed = new URL(rewriteUrl, request.url);\n rewritePath = rewriteParsed.pathname + rewriteParsed.search;\n } catch {\n rewritePath = rewriteUrl;\n }\n return {\n continue: true,\n rewriteUrl: rewritePath,\n rewriteStatus: response.status !== 200 ? response.status : undefined,\n responseHeaders,\n };\n }\n\n // Middleware returned a full Response (e.g., blocking, custom body)\n return { continue: false, response };\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path normalization utility for request handling.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes URL pathnames to a canonical form BEFORE any matching occurs
|
|
5
|
+
* (middleware, routing, redirects, rewrites). This ensures middleware and
|
|
6
|
+
* the router always see the same path, preventing path-confusion issues like
|
|
7
|
+
* double-slash mismatches.
|
|
8
|
+
*
|
|
9
|
+
* Normalization rules:
|
|
10
|
+
* 1. Collapse consecutive slashes: //foo///bar → /foo/bar
|
|
11
|
+
* 2. Resolve single-dot segments: /foo/./bar → /foo/bar
|
|
12
|
+
* 3. Resolve double-dot segments: /foo/../bar → /bar
|
|
13
|
+
* 4. Ensure leading slash: foo/bar → /foo/bar
|
|
14
|
+
* 5. Preserve root: / → /
|
|
15
|
+
*
|
|
16
|
+
* This function does NOT:
|
|
17
|
+
* - Strip or add trailing slashes (handled separately by trailingSlash config)
|
|
18
|
+
* - Decode percent-encoded characters (callers should decode before calling this)
|
|
19
|
+
* - Lowercase the path (route matching is case-sensitive)
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizePath(pathname: string): string;
|
|
22
|
+
//# sourceMappingURL=normalize-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-path.d.ts","sourceRoot":"","sources":["../../src/server/normalize-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgCtD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path normalization utility for request handling.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes URL pathnames to a canonical form BEFORE any matching occurs
|
|
5
|
+
* (middleware, routing, redirects, rewrites). This ensures middleware and
|
|
6
|
+
* the router always see the same path, preventing path-confusion issues like
|
|
7
|
+
* double-slash mismatches.
|
|
8
|
+
*
|
|
9
|
+
* Normalization rules:
|
|
10
|
+
* 1. Collapse consecutive slashes: //foo///bar → /foo/bar
|
|
11
|
+
* 2. Resolve single-dot segments: /foo/./bar → /foo/bar
|
|
12
|
+
* 3. Resolve double-dot segments: /foo/../bar → /bar
|
|
13
|
+
* 4. Ensure leading slash: foo/bar → /foo/bar
|
|
14
|
+
* 5. Preserve root: / → /
|
|
15
|
+
*
|
|
16
|
+
* This function does NOT:
|
|
17
|
+
* - Strip or add trailing slashes (handled separately by trailingSlash config)
|
|
18
|
+
* - Decode percent-encoded characters (callers should decode before calling this)
|
|
19
|
+
* - Lowercase the path (route matching is case-sensitive)
|
|
20
|
+
*/
|
|
21
|
+
export function normalizePath(pathname) {
|
|
22
|
+
// Fast path: already canonical (single leading /, no //, no /./, no /../)
|
|
23
|
+
if (pathname === "/" ||
|
|
24
|
+
(pathname.length > 1 &&
|
|
25
|
+
pathname[0] === "/" &&
|
|
26
|
+
!pathname.includes("//") &&
|
|
27
|
+
!pathname.includes("/./") &&
|
|
28
|
+
!pathname.includes("/../") &&
|
|
29
|
+
!pathname.endsWith("/.") &&
|
|
30
|
+
!pathname.endsWith("/.."))) {
|
|
31
|
+
return pathname;
|
|
32
|
+
}
|
|
33
|
+
const segments = pathname.split("/");
|
|
34
|
+
const resolved = [];
|
|
35
|
+
for (const segment of segments) {
|
|
36
|
+
if (segment === "" || segment === ".") {
|
|
37
|
+
// Skip empty segments (from // or leading /) and single-dot segments
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (segment === "..") {
|
|
41
|
+
// Go up one level, but never above root
|
|
42
|
+
resolved.pop();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resolved.push(segment);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return "/" + resolved.join("/");
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=normalize-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-path.js","sourceRoot":"","sources":["../../src/server/normalize-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,0EAA0E;IAC1E,IACE,QAAQ,KAAK,GAAG;QAChB,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YAClB,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG;YACnB,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxB,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;YACzB,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC1B,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxB,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAC5B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACtC,qEAAqE;YACrE,SAAS;QACX,CAAC;QACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,wCAAwC;YACxC,QAAQ,CAAC,GAAG,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC","sourcesContent":["/**\n * Path normalization utility for request handling.\n *\n * Normalizes URL pathnames to a canonical form BEFORE any matching occurs\n * (middleware, routing, redirects, rewrites). This ensures middleware and\n * the router always see the same path, preventing path-confusion issues like\n * double-slash mismatches.\n *\n * Normalization rules:\n * 1. Collapse consecutive slashes: //foo///bar → /foo/bar\n * 2. Resolve single-dot segments: /foo/./bar → /foo/bar\n * 3. Resolve double-dot segments: /foo/../bar → /bar\n * 4. Ensure leading slash: foo/bar → /foo/bar\n * 5. Preserve root: / → /\n *\n * This function does NOT:\n * - Strip or add trailing slashes (handled separately by trailingSlash config)\n * - Decode percent-encoded characters (callers should decode before calling this)\n * - Lowercase the path (route matching is case-sensitive)\n */\nexport function normalizePath(pathname: string): string {\n // Fast path: already canonical (single leading /, no //, no /./, no /../)\n if (\n pathname === \"/\" ||\n (pathname.length > 1 &&\n pathname[0] === \"/\" &&\n !pathname.includes(\"//\") &&\n !pathname.includes(\"/./\") &&\n !pathname.includes(\"/../\") &&\n !pathname.endsWith(\"/.\") &&\n !pathname.endsWith(\"/..\"))\n ) {\n return pathname;\n }\n\n const segments = pathname.split(\"/\");\n const resolved: string[] = [];\n\n for (const segment of segments) {\n if (segment === \"\" || segment === \".\") {\n // Skip empty segments (from // or leading /) and single-dot segments\n continue;\n }\n if (segment === \"..\") {\n // Go up one level, but never above root\n resolved.pop();\n } else {\n resolved.push(segment);\n }\n }\n\n return \"/\" + resolved.join(\"/\");\n}\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../../src/server/prod-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../../src/server/prod-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAuBpF,MAAM,WAAW,iBAAiB;IAChC,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,mDAAmD;AACnD,QAAA,MAAM,kBAAkB,aAetB,CAAC;AAEH,0GAA0G;AAC1G,QAAA,MAAM,kBAAkB,OAAO,CAAC;AAEhC;;;GAGG;AACH,iBAAS,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAQjF;AAoBD;;;GAGG;AACH,iBAAS,cAAc,CACrB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACzC,QAAQ,GAAE,OAAc,GACvB,IAAI,CAkCN;AA0FD;;;;;;;;;;;GAWG;AACH,iBAAS,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED,4EAA4E;AAC5E,QAAA,MAAM,YAAY,EAAE,GAAG,CAAC,MAAM,CAK7B,CAAC;AAEF;;;;GAIG;AACH,QAAA,MAAM,UAAU,SAAkE,CAAC;AAEnF;;GAEG;AACH,iBAAS,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAmCvD;AA+ED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,iBAAsB,sFA6BpE;AAodD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}
|