vinext 0.0.30 → 0.0.31

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.
Files changed (435) hide show
  1. package/README.md +12 -6
  2. package/dist/build/prerender.d.ts +188 -0
  3. package/dist/build/prerender.js +675 -0
  4. package/dist/build/prerender.js.map +1 -0
  5. package/dist/build/report.d.ts +45 -46
  6. package/dist/build/report.js +247 -276
  7. package/dist/build/report.js.map +1 -1
  8. package/dist/build/run-prerender.d.ts +62 -0
  9. package/dist/build/run-prerender.js +183 -0
  10. package/dist/build/run-prerender.js.map +1 -0
  11. package/dist/build/server-manifest.d.ts +19 -0
  12. package/dist/build/server-manifest.js +29 -0
  13. package/dist/build/server-manifest.js.map +1 -0
  14. package/dist/build/static-export.d.ts +51 -66
  15. package/dist/build/static-export.js +51 -545
  16. package/dist/build/static-export.js.map +1 -1
  17. package/dist/check.d.ts +26 -24
  18. package/dist/check.js +591 -571
  19. package/dist/check.js.map +1 -1
  20. package/dist/cli.d.ts +1 -15
  21. package/dist/cli.js +430 -491
  22. package/dist/cli.js.map +1 -1
  23. package/dist/client/entry.d.ts +1 -2
  24. package/dist/client/entry.js +49 -62
  25. package/dist/client/entry.js.map +1 -1
  26. package/dist/client/validate-module-path.d.ts +4 -1
  27. package/dist/client/validate-module-path.js +23 -28
  28. package/dist/client/validate-module-path.js.map +1 -1
  29. package/dist/client/vinext-next-data.d.ts +15 -20
  30. package/dist/client/vinext-next-data.js +0 -1
  31. package/dist/cloudflare/index.d.ts +3 -8
  32. package/dist/cloudflare/index.js +3 -8
  33. package/dist/cloudflare/kv-cache-handler.d.ts +95 -105
  34. package/dist/cloudflare/kv-cache-handler.js +354 -380
  35. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  36. package/dist/cloudflare/tpr.d.ts +36 -34
  37. package/dist/cloudflare/tpr.js +460 -603
  38. package/dist/cloudflare/tpr.js.map +1 -1
  39. package/dist/config/config-matchers.d.ts +31 -40
  40. package/dist/config/config-matchers.js +727 -936
  41. package/dist/config/config-matchers.js.map +1 -1
  42. package/dist/config/dotenv.d.ts +18 -11
  43. package/dist/config/dotenv.js +79 -84
  44. package/dist/config/dotenv.js.map +1 -1
  45. package/dist/config/next-config.d.ts +156 -146
  46. package/dist/config/next-config.js +374 -464
  47. package/dist/config/next-config.js.map +1 -1
  48. package/dist/deploy.d.ts +87 -96
  49. package/dist/deploy.js +490 -628
  50. package/dist/deploy.js.map +1 -1
  51. package/dist/entries/app-browser-entry.d.ts +4 -1
  52. package/dist/entries/app-browser-entry.js +12 -8
  53. package/dist/entries/app-browser-entry.js.map +1 -1
  54. package/dist/entries/app-rsc-entry.d.ts +33 -20
  55. package/dist/entries/app-rsc-entry.js +442 -211
  56. package/dist/entries/app-rsc-entry.js.map +1 -1
  57. package/dist/entries/app-ssr-entry.d.ts +9 -1
  58. package/dist/entries/app-ssr-entry.js +61 -28
  59. package/dist/entries/app-ssr-entry.js.map +1 -1
  60. package/dist/entries/pages-client-entry.d.ts +6 -2
  61. package/dist/entries/pages-client-entry.js +30 -33
  62. package/dist/entries/pages-client-entry.js.map +1 -1
  63. package/dist/entries/pages-entry-helpers.d.ts +5 -1
  64. package/dist/entries/pages-entry-helpers.js +17 -14
  65. package/dist/entries/pages-entry-helpers.js.map +1 -1
  66. package/dist/entries/pages-server-entry.d.ts +6 -2
  67. package/dist/entries/pages-server-entry.js +84 -113
  68. package/dist/entries/pages-server-entry.js.map +1 -1
  69. package/dist/index.d.ts +82 -62
  70. package/dist/index.js +2172 -3133
  71. package/dist/index.js.map +1 -1
  72. package/dist/init.d.ts +40 -37
  73. package/dist/init.js +201 -258
  74. package/dist/init.js.map +1 -1
  75. package/dist/plugins/async-hooks-stub.d.ts +7 -3
  76. package/dist/plugins/async-hooks-stub.js +39 -42
  77. package/dist/plugins/async-hooks-stub.js.map +1 -1
  78. package/dist/plugins/client-reference-dedup.d.ts +7 -3
  79. package/dist/plugins/client-reference-dedup.js +63 -88
  80. package/dist/plugins/client-reference-dedup.js.map +1 -1
  81. package/dist/routing/app-router.d.ts +100 -96
  82. package/dist/routing/app-router.js +560 -670
  83. package/dist/routing/app-router.js.map +1 -1
  84. package/dist/routing/file-matcher.d.ts +18 -15
  85. package/dist/routing/file-matcher.js +65 -65
  86. package/dist/routing/file-matcher.js.map +1 -1
  87. package/dist/routing/pages-router.d.ts +23 -24
  88. package/dist/routing/pages-router.js +147 -172
  89. package/dist/routing/pages-router.js.map +1 -1
  90. package/dist/routing/route-trie.d.ts +23 -20
  91. package/dist/routing/route-trie.js +131 -151
  92. package/dist/routing/route-trie.js.map +1 -1
  93. package/dist/routing/route-validation.d.ts +5 -2
  94. package/dist/routing/route-validation.js +98 -130
  95. package/dist/routing/route-validation.js.map +1 -1
  96. package/dist/routing/utils.d.ts +10 -7
  97. package/dist/routing/utils.js +75 -111
  98. package/dist/routing/utils.js.map +1 -1
  99. package/dist/server/api-handler.d.ts +8 -13
  100. package/dist/server/api-handler.js +161 -193
  101. package/dist/server/api-handler.js.map +1 -1
  102. package/dist/server/app-router-entry.d.ts +6 -16
  103. package/dist/server/app-router-entry.js +26 -54
  104. package/dist/server/app-router-entry.js.map +1 -1
  105. package/dist/server/dev-module-runner.d.ts +11 -64
  106. package/dist/server/dev-module-runner.js +89 -101
  107. package/dist/server/dev-module-runner.js.map +1 -1
  108. package/dist/server/dev-origin-check.d.ts +12 -10
  109. package/dist/server/dev-origin-check.js +98 -108
  110. package/dist/server/dev-origin-check.js.map +1 -1
  111. package/dist/server/dev-server.d.ts +17 -14
  112. package/dist/server/dev-server.js +542 -869
  113. package/dist/server/dev-server.js.map +1 -1
  114. package/dist/server/html.d.ts +4 -1
  115. package/dist/server/html.js +25 -26
  116. package/dist/server/html.js.map +1 -1
  117. package/dist/server/image-optimization.d.ts +31 -28
  118. package/dist/server/image-optimization.js +181 -210
  119. package/dist/server/image-optimization.js.map +1 -1
  120. package/dist/server/instrumentation.d.ts +25 -22
  121. package/dist/server/instrumentation.js +110 -122
  122. package/dist/server/instrumentation.js.map +1 -1
  123. package/dist/server/isr-cache.d.ts +16 -26
  124. package/dist/server/isr-cache.js +106 -128
  125. package/dist/server/isr-cache.js.map +1 -1
  126. package/dist/server/metadata-routes.d.ts +85 -88
  127. package/dist/server/metadata-routes.js +270 -317
  128. package/dist/server/metadata-routes.js.map +1 -1
  129. package/dist/server/middleware-codegen.d.ts +7 -4
  130. package/dist/server/middleware-codegen.js +61 -61
  131. package/dist/server/middleware-codegen.js.map +1 -1
  132. package/dist/server/middleware-request-headers.d.ts +8 -6
  133. package/dist/server/middleware-request-headers.js +47 -65
  134. package/dist/server/middleware-request-headers.js.map +1 -1
  135. package/dist/server/middleware.d.ts +31 -47
  136. package/dist/server/middleware.js +273 -404
  137. package/dist/server/middleware.js.map +1 -1
  138. package/dist/server/normalize-path.d.ts +4 -1
  139. package/dist/server/normalize-path.js +33 -47
  140. package/dist/server/normalize-path.js.map +1 -1
  141. package/dist/server/pages-i18n.d.ts +38 -30
  142. package/dist/server/pages-i18n.js +112 -139
  143. package/dist/server/pages-i18n.js.map +1 -1
  144. package/dist/server/prod-server.d.ts +19 -31
  145. package/dist/server/prod-server.js +714 -945
  146. package/dist/server/prod-server.js.map +1 -1
  147. package/dist/server/request-log.d.ts +18 -12
  148. package/dist/server/request-log.js +45 -52
  149. package/dist/server/request-log.js.map +1 -1
  150. package/dist/server/request-pipeline.d.ts +9 -17
  151. package/dist/server/request-pipeline.js +133 -184
  152. package/dist/server/request-pipeline.js.map +1 -1
  153. package/dist/server/worker-utils.d.ts +4 -1
  154. package/dist/server/worker-utils.js +31 -37
  155. package/dist/server/worker-utils.js.map +1 -1
  156. package/dist/shims/amp.d.ts +5 -2
  157. package/dist/shims/amp.js +19 -15
  158. package/dist/shims/amp.js.map +1 -1
  159. package/dist/shims/app.d.ts +8 -10
  160. package/dist/shims/app.js +0 -1
  161. package/dist/shims/cache-runtime.d.ts +20 -45
  162. package/dist/shims/cache-runtime.js +271 -422
  163. package/dist/shims/cache-runtime.js.map +1 -1
  164. package/dist/shims/cache.d.ts +130 -121
  165. package/dist/shims/cache.js +339 -427
  166. package/dist/shims/cache.js.map +1 -1
  167. package/dist/shims/client-only.d.ts +1 -18
  168. package/dist/shims/client-only.js +0 -17
  169. package/dist/shims/compat-router.d.ts +4 -1
  170. package/dist/shims/compat-router.js +23 -19
  171. package/dist/shims/compat-router.js.map +1 -1
  172. package/dist/shims/config.d.ts +7 -5
  173. package/dist/shims/config.js +16 -23
  174. package/dist/shims/config.js.map +1 -1
  175. package/dist/shims/constants.d.ts +119 -118
  176. package/dist/shims/constants.js +159 -164
  177. package/dist/shims/constants.js.map +1 -1
  178. package/dist/shims/document.d.ts +20 -16
  179. package/dist/shims/document.js +41 -22
  180. package/dist/shims/document.js.map +1 -1
  181. package/dist/shims/dynamic.d.ts +13 -22
  182. package/dist/shims/dynamic.js +122 -136
  183. package/dist/shims/dynamic.js.map +1 -1
  184. package/dist/shims/error-boundary.d.ts +22 -15
  185. package/dist/shims/error-boundary.js +81 -79
  186. package/dist/shims/error-boundary.js.map +1 -1
  187. package/dist/shims/error.d.ts +11 -12
  188. package/dist/shims/error.js +35 -39
  189. package/dist/shims/error.js.map +1 -1
  190. package/dist/shims/fetch-cache.d.ts +16 -14
  191. package/dist/shims/fetch-cache.js +437 -645
  192. package/dist/shims/fetch-cache.js.map +1 -1
  193. package/dist/shims/font-google-base.d.ts +28 -26
  194. package/dist/shims/font-google-base.js +238 -325
  195. package/dist/shims/font-google-base.js.map +1 -1
  196. package/dist/shims/font-google.d.ts +3 -3
  197. package/dist/shims/font-google.generated.d.ts +1928 -1924
  198. package/dist/shims/font-google.generated.js +1928 -2133
  199. package/dist/shims/font-google.generated.js.map +1 -1
  200. package/dist/shims/font-google.js +3 -3
  201. package/dist/shims/font-local.d.ts +28 -26
  202. package/dist/shims/font-local.js +204 -260
  203. package/dist/shims/font-local.js.map +1 -1
  204. package/dist/shims/form.d.ts +13 -27
  205. package/dist/shims/form.js +128 -180
  206. package/dist/shims/form.js.map +1 -1
  207. package/dist/shims/head-state.d.ts +8 -13
  208. package/dist/shims/head-state.js +25 -42
  209. package/dist/shims/head-state.js.map +1 -1
  210. package/dist/shims/head.d.ts +16 -20
  211. package/dist/shims/head.js +172 -250
  212. package/dist/shims/head.js.map +1 -1
  213. package/dist/shims/headers.d.ts +84 -78
  214. package/dist/shims/headers.js +447 -575
  215. package/dist/shims/headers.js.map +1 -1
  216. package/dist/shims/i18n-context.d.ts +16 -20
  217. package/dist/shims/i18n-context.js +35 -48
  218. package/dist/shims/i18n-context.js.map +1 -1
  219. package/dist/shims/i18n-state.d.ts +8 -14
  220. package/dist/shims/i18n-state.js +34 -42
  221. package/dist/shims/i18n-state.js.map +1 -1
  222. package/dist/shims/image-config.d.ts +11 -8
  223. package/dist/shims/image-config.js +50 -83
  224. package/dist/shims/image-config.js.map +1 -1
  225. package/dist/shims/image.d.ts +37 -46
  226. package/dist/shims/image.js +283 -308
  227. package/dist/shims/image.js.map +1 -1
  228. package/dist/shims/internal/api-utils.d.ts +7 -4
  229. package/dist/shims/internal/api-utils.js +0 -6
  230. package/dist/shims/internal/app-router-context.d.ts +22 -17
  231. package/dist/shims/internal/app-router-context.js +17 -13
  232. package/dist/shims/internal/app-router-context.js.map +1 -1
  233. package/dist/shims/internal/cookies.d.ts +2 -9
  234. package/dist/shims/internal/cookies.js +2 -9
  235. package/dist/shims/internal/parse-cookie-header.d.ts +4 -1
  236. package/dist/shims/internal/parse-cookie-header.js +29 -29
  237. package/dist/shims/internal/parse-cookie-header.js.map +1 -1
  238. package/dist/shims/internal/router-context.d.ts +6 -1
  239. package/dist/shims/internal/router-context.js +11 -7
  240. package/dist/shims/internal/router-context.js.map +1 -1
  241. package/dist/shims/internal/utils.d.ts +40 -37
  242. package/dist/shims/internal/utils.js +24 -30
  243. package/dist/shims/internal/utils.js.map +1 -1
  244. package/dist/shims/internal/work-unit-async-storage.d.ts +6 -10
  245. package/dist/shims/internal/work-unit-async-storage.js +14 -11
  246. package/dist/shims/internal/work-unit-async-storage.js.map +1 -1
  247. package/dist/shims/layout-segment-context.d.ts +11 -14
  248. package/dist/shims/layout-segment-context.js +24 -23
  249. package/dist/shims/layout-segment-context.js.map +1 -1
  250. package/dist/shims/legacy-image.d.ts +39 -46
  251. package/dist/shims/legacy-image.js +47 -42
  252. package/dist/shims/legacy-image.js.map +1 -1
  253. package/dist/shims/link.d.ts +32 -36
  254. package/dist/shims/link.js +255 -391
  255. package/dist/shims/link.js.map +1 -1
  256. package/dist/shims/metadata.d.ts +210 -202
  257. package/dist/shims/metadata.js +545 -546
  258. package/dist/shims/metadata.js.map +1 -1
  259. package/dist/shims/navigation-state.d.ts +10 -18
  260. package/dist/shims/navigation-state.js +66 -74
  261. package/dist/shims/navigation-state.js.map +1 -1
  262. package/dist/shims/navigation.d.ts +59 -63
  263. package/dist/shims/navigation.js +505 -704
  264. package/dist/shims/navigation.js.map +1 -1
  265. package/dist/shims/og.d.ts +2 -20
  266. package/dist/shims/og.js +2 -19
  267. package/dist/shims/readonly-url-search-params.d.ts +8 -5
  268. package/dist/shims/readonly-url-search-params.js +26 -22
  269. package/dist/shims/readonly-url-search-params.js.map +1 -1
  270. package/dist/shims/request-context.d.ts +8 -5
  271. package/dist/shims/request-context.js +50 -60
  272. package/dist/shims/request-context.js.map +1 -1
  273. package/dist/shims/request-state-types.d.ts +11 -11
  274. package/dist/shims/request-state-types.js +0 -1
  275. package/dist/shims/router-state.d.ts +13 -10
  276. package/dist/shims/router-state.js +34 -43
  277. package/dist/shims/router-state.js.map +1 -1
  278. package/dist/shims/router.d.ts +81 -85
  279. package/dist/shims/router.js +506 -628
  280. package/dist/shims/router.js.map +1 -1
  281. package/dist/shims/script.d.ts +39 -48
  282. package/dist/shims/script.js +107 -160
  283. package/dist/shims/script.js.map +1 -1
  284. package/dist/shims/server-only.d.ts +1 -19
  285. package/dist/shims/server-only.js +0 -18
  286. package/dist/shims/server.d.ts +175 -164
  287. package/dist/shims/server.js +462 -478
  288. package/dist/shims/server.js.map +1 -1
  289. package/dist/shims/unified-request-context.d.ts +20 -20
  290. package/dist/shims/unified-request-context.js +81 -99
  291. package/dist/shims/unified-request-context.js.map +1 -1
  292. package/dist/shims/url-safety.d.ts +4 -1
  293. package/dist/shims/url-safety.js +15 -11
  294. package/dist/shims/url-safety.js.map +1 -1
  295. package/dist/shims/url-utils.d.ts +8 -5
  296. package/dist/shims/url-utils.js +62 -93
  297. package/dist/shims/url-utils.js.map +1 -1
  298. package/dist/shims/web-vitals.d.ts +10 -8
  299. package/dist/shims/web-vitals.js +9 -15
  300. package/dist/shims/web-vitals.js.map +1 -1
  301. package/dist/utils/base-path.d.ts +5 -2
  302. package/dist/utils/base-path.js +21 -19
  303. package/dist/utils/base-path.js.map +1 -1
  304. package/dist/utils/domain-locale.d.ts +17 -9
  305. package/dist/utils/domain-locale.js +36 -56
  306. package/dist/utils/domain-locale.js.map +1 -1
  307. package/dist/utils/hash.d.ts +4 -1
  308. package/dist/utils/hash.js +19 -17
  309. package/dist/utils/hash.js.map +1 -1
  310. package/dist/utils/manifest-paths.d.ts +6 -3
  311. package/dist/utils/manifest-paths.js +15 -16
  312. package/dist/utils/manifest-paths.js.map +1 -1
  313. package/dist/utils/project.d.ts +13 -11
  314. package/dist/utils/project.js +169 -216
  315. package/dist/utils/project.js.map +1 -1
  316. package/dist/utils/query.d.ts +8 -6
  317. package/dist/utils/query.js +57 -67
  318. package/dist/utils/query.js.map +1 -1
  319. package/package.json +10 -9
  320. package/dist/build/report.d.ts.map +0 -1
  321. package/dist/build/static-export.d.ts.map +0 -1
  322. package/dist/check.d.ts.map +0 -1
  323. package/dist/cli.d.ts.map +0 -1
  324. package/dist/client/entry.d.ts.map +0 -1
  325. package/dist/client/validate-module-path.d.ts.map +0 -1
  326. package/dist/client/vinext-next-data.d.ts.map +0 -1
  327. package/dist/client/vinext-next-data.js.map +0 -1
  328. package/dist/cloudflare/index.d.ts.map +0 -1
  329. package/dist/cloudflare/index.js.map +0 -1
  330. package/dist/cloudflare/kv-cache-handler.d.ts.map +0 -1
  331. package/dist/cloudflare/tpr.d.ts.map +0 -1
  332. package/dist/config/config-matchers.d.ts.map +0 -1
  333. package/dist/config/dotenv.d.ts.map +0 -1
  334. package/dist/config/next-config.d.ts.map +0 -1
  335. package/dist/deploy.d.ts.map +0 -1
  336. package/dist/entries/app-browser-entry.d.ts.map +0 -1
  337. package/dist/entries/app-rsc-entry.d.ts.map +0 -1
  338. package/dist/entries/app-ssr-entry.d.ts.map +0 -1
  339. package/dist/entries/pages-client-entry.d.ts.map +0 -1
  340. package/dist/entries/pages-entry-helpers.d.ts.map +0 -1
  341. package/dist/entries/pages-server-entry.d.ts.map +0 -1
  342. package/dist/index.d.ts.map +0 -1
  343. package/dist/init.d.ts.map +0 -1
  344. package/dist/plugins/async-hooks-stub.d.ts.map +0 -1
  345. package/dist/plugins/client-reference-dedup.d.ts.map +0 -1
  346. package/dist/routing/app-router.d.ts.map +0 -1
  347. package/dist/routing/file-matcher.d.ts.map +0 -1
  348. package/dist/routing/pages-router.d.ts.map +0 -1
  349. package/dist/routing/route-trie.d.ts.map +0 -1
  350. package/dist/routing/route-validation.d.ts.map +0 -1
  351. package/dist/routing/utils.d.ts.map +0 -1
  352. package/dist/server/api-handler.d.ts.map +0 -1
  353. package/dist/server/app-router-entry.d.ts.map +0 -1
  354. package/dist/server/dev-module-runner.d.ts.map +0 -1
  355. package/dist/server/dev-origin-check.d.ts.map +0 -1
  356. package/dist/server/dev-server.d.ts.map +0 -1
  357. package/dist/server/html.d.ts.map +0 -1
  358. package/dist/server/image-optimization.d.ts.map +0 -1
  359. package/dist/server/instrumentation.d.ts.map +0 -1
  360. package/dist/server/isr-cache.d.ts.map +0 -1
  361. package/dist/server/metadata-routes.d.ts.map +0 -1
  362. package/dist/server/middleware-codegen.d.ts.map +0 -1
  363. package/dist/server/middleware-request-headers.d.ts.map +0 -1
  364. package/dist/server/middleware.d.ts.map +0 -1
  365. package/dist/server/normalize-path.d.ts.map +0 -1
  366. package/dist/server/pages-i18n.d.ts.map +0 -1
  367. package/dist/server/prod-server.d.ts.map +0 -1
  368. package/dist/server/request-log.d.ts.map +0 -1
  369. package/dist/server/request-pipeline.d.ts.map +0 -1
  370. package/dist/server/worker-utils.d.ts.map +0 -1
  371. package/dist/shims/amp.d.ts.map +0 -1
  372. package/dist/shims/app.d.ts.map +0 -1
  373. package/dist/shims/app.js.map +0 -1
  374. package/dist/shims/cache-runtime.d.ts.map +0 -1
  375. package/dist/shims/cache.d.ts.map +0 -1
  376. package/dist/shims/client-only.d.ts.map +0 -1
  377. package/dist/shims/client-only.js.map +0 -1
  378. package/dist/shims/compat-router.d.ts.map +0 -1
  379. package/dist/shims/config.d.ts.map +0 -1
  380. package/dist/shims/constants.d.ts.map +0 -1
  381. package/dist/shims/document.d.ts.map +0 -1
  382. package/dist/shims/dynamic.d.ts.map +0 -1
  383. package/dist/shims/error-boundary.d.ts.map +0 -1
  384. package/dist/shims/error.d.ts.map +0 -1
  385. package/dist/shims/fetch-cache.d.ts.map +0 -1
  386. package/dist/shims/font-google-base.d.ts.map +0 -1
  387. package/dist/shims/font-google.d.ts.map +0 -1
  388. package/dist/shims/font-google.generated.d.ts.map +0 -1
  389. package/dist/shims/font-google.js.map +0 -1
  390. package/dist/shims/font-local.d.ts.map +0 -1
  391. package/dist/shims/form.d.ts.map +0 -1
  392. package/dist/shims/head-state.d.ts.map +0 -1
  393. package/dist/shims/head.d.ts.map +0 -1
  394. package/dist/shims/headers.d.ts.map +0 -1
  395. package/dist/shims/i18n-context.d.ts.map +0 -1
  396. package/dist/shims/i18n-state.d.ts.map +0 -1
  397. package/dist/shims/image-config.d.ts.map +0 -1
  398. package/dist/shims/image.d.ts.map +0 -1
  399. package/dist/shims/internal/api-utils.d.ts.map +0 -1
  400. package/dist/shims/internal/api-utils.js.map +0 -1
  401. package/dist/shims/internal/app-router-context.d.ts.map +0 -1
  402. package/dist/shims/internal/cookies.d.ts.map +0 -1
  403. package/dist/shims/internal/cookies.js.map +0 -1
  404. package/dist/shims/internal/parse-cookie-header.d.ts.map +0 -1
  405. package/dist/shims/internal/router-context.d.ts.map +0 -1
  406. package/dist/shims/internal/utils.d.ts.map +0 -1
  407. package/dist/shims/internal/work-unit-async-storage.d.ts.map +0 -1
  408. package/dist/shims/layout-segment-context.d.ts.map +0 -1
  409. package/dist/shims/legacy-image.d.ts.map +0 -1
  410. package/dist/shims/link.d.ts.map +0 -1
  411. package/dist/shims/metadata.d.ts.map +0 -1
  412. package/dist/shims/navigation-state.d.ts.map +0 -1
  413. package/dist/shims/navigation.d.ts.map +0 -1
  414. package/dist/shims/og.d.ts.map +0 -1
  415. package/dist/shims/og.js.map +0 -1
  416. package/dist/shims/readonly-url-search-params.d.ts.map +0 -1
  417. package/dist/shims/request-context.d.ts.map +0 -1
  418. package/dist/shims/request-state-types.d.ts.map +0 -1
  419. package/dist/shims/request-state-types.js.map +0 -1
  420. package/dist/shims/router-state.d.ts.map +0 -1
  421. package/dist/shims/router.d.ts.map +0 -1
  422. package/dist/shims/script.d.ts.map +0 -1
  423. package/dist/shims/server-only.d.ts.map +0 -1
  424. package/dist/shims/server-only.js.map +0 -1
  425. package/dist/shims/server.d.ts.map +0 -1
  426. package/dist/shims/unified-request-context.d.ts.map +0 -1
  427. package/dist/shims/url-safety.d.ts.map +0 -1
  428. package/dist/shims/url-utils.d.ts.map +0 -1
  429. package/dist/shims/web-vitals.d.ts.map +0 -1
  430. package/dist/utils/base-path.d.ts.map +0 -1
  431. package/dist/utils/domain-locale.d.ts.map +0 -1
  432. package/dist/utils/hash.d.ts.map +0 -1
  433. package/dist/utils/manifest-paths.d.ts.map +0 -1
  434. package/dist/utils/project.d.ts.map +0 -1
  435. package/dist/utils/query.d.ts.map +0 -1
@@ -1,991 +1,782 @@
1
- /**
2
- * Config pattern matching and rule application utilities.
3
- *
4
- * Shared between the dev server (index.ts) and the production server
5
- * (prod-server.ts) so both apply next.config.js rules identically.
6
- */
7
1
  import { buildRequestHeadersFromMiddlewareResponse } from "../server/middleware-request-headers.js";
2
+ //#region src/config/config-matchers.ts
8
3
  /**
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 = new Map();
4
+ * Cache for compiled regex patterns in matchConfigPattern.
5
+ *
6
+ * Redirect/rewrite patterns are static — they come from next.config.js and
7
+ * never change at runtime. Without caching, every request that hits the regex
8
+ * branch re-runs the full tokeniser walk + isSafeRegex + new RegExp() for
9
+ * every rule in the array. On apps with many locale-prefixed rules (which all
10
+ * contain `(` and therefore enter the regex branch) this dominated profiling
11
+ * at ~2.4 seconds of CPU self-time.
12
+ *
13
+ * Value is `null` when safeRegExp rejected the pattern (ReDoS risk), so we
14
+ * skip it on subsequent requests too without re-running the scanner.
15
+ */
16
+ const _compiledPatternCache = /* @__PURE__ */ new Map();
22
17
  /**
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 = new Map();
18
+ * Cache for compiled header source regexes in matchHeaders.
19
+ *
20
+ * Each NextHeader rule has a `source` that is run through escapeHeaderSource()
21
+ * then safeRegExp() to produce a RegExp. Both are pure functions of the source
22
+ * string and the result never changes. Without caching, every request
23
+ * re-runs the full escapeHeaderSource tokeniser + isSafeRegex scan + new RegExp()
24
+ * for every header rule.
25
+ *
26
+ * Value is `null` when safeRegExp rejected the pattern (ReDoS risk).
27
+ */
28
+ const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
34
29
  /**
35
- * Cache for compiled has/missing condition value regexes in checkSingleCondition.
36
- *
37
- * Each has/missing condition may carry a `value` string that is passed directly
38
- * to safeRegExp() for matching against header/cookie/query/host values. The
39
- * condition objects are static (from next.config.js) so the compiled RegExp
40
- * never changes. Without caching, safeRegExp() is called on every request for
41
- * every condition on every rule.
42
- *
43
- * Value is `null` when safeRegExp rejected the pattern, or `false` when the
44
- * value string was undefined (no regex needed — use exact string comparison).
45
- */
46
- const _compiledConditionCache = new Map();
30
+ * Cache for compiled has/missing condition value regexes in checkSingleCondition.
31
+ *
32
+ * Each has/missing condition may carry a `value` string that is passed directly
33
+ * to safeRegExp() for matching against header/cookie/query/host values. The
34
+ * condition objects are static (from next.config.js) so the compiled RegExp
35
+ * never changes. Without caching, safeRegExp() is called on every request for
36
+ * every condition on every rule.
37
+ *
38
+ * Value is `null` when safeRegExp rejected the pattern, or `false` when the
39
+ * value string was undefined (no regex needed — use exact string comparison).
40
+ */
41
+ const _compiledConditionCache = /* @__PURE__ */ new Map();
47
42
  /**
48
- * Cache for destination substitution regexes in substituteDestinationParams.
49
- *
50
- * The regex depends only on the set of param keys captured from the matched
51
- * source pattern. Caching by sorted key list avoids recompiling a new RegExp
52
- * for repeated redirect/rewrite calls that use the same param shape.
53
- */
54
- const _compiledDestinationParamCache = new Map();
43
+ * Cache for destination substitution regexes in substituteDestinationParams.
44
+ *
45
+ * The regex depends only on the set of param keys captured from the matched
46
+ * source pattern. Caching by sorted key list avoids recompiling a new RegExp
47
+ * for repeated redirect/rewrite calls that use the same param shape.
48
+ */
49
+ const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
55
50
  /**
56
- * Redirect index for O(1) locale-static rule lookup.
57
- *
58
- * Many Next.js apps generate 50-100 redirect rules of the form:
59
- * /:locale(en|es|fr|...)?/some-static-path → /some-destination
60
- *
61
- * The compiled regex for each is like:
62
- * ^/(en|es|fr|...)?/some-static-path$
63
- *
64
- * When no redirect matches (the common case for ordinary page loads),
65
- * matchRedirect previously ran exec() on every one of those regexes —
66
- * ~2ms per call, ~2992ms total self-time in profiles.
67
- *
68
- * The index splits rules into two buckets:
69
- *
70
- * localeStatic — rules whose source is exactly /:paramName(alt1|alt2|...)?/suffix
71
- * where `suffix` is a static path with no further params or regex groups.
72
- * These are indexed in a Map<suffix, entry[]> for O(1) lookup after a
73
- * single fast strip of the optional locale prefix.
74
- *
75
- * linear — all other rules. Matched with the original O(n) loop.
76
- *
77
- * The index is stored in a WeakMap keyed by the redirects array so it is
78
- * computed once per config load and GC'd when the array is no longer live.
79
- *
80
- * ## Ordering invariant
81
- *
82
- * Redirect rules must be evaluated in their original order (first match wins).
83
- * Each locale-static entry stores its `originalIndex` so that, when a
84
- * locale-static fast-path match is found, any linear rules that appear earlier
85
- * in the array are still checked first.
86
- */
51
+ * Redirect index for O(1) locale-static rule lookup.
52
+ *
53
+ * Many Next.js apps generate 50-100 redirect rules of the form:
54
+ * /:locale(en|es|fr|...)?/some-static-path → /some-destination
55
+ *
56
+ * The compiled regex for each is like:
57
+ * ^/(en|es|fr|...)?/some-static-path$
58
+ *
59
+ * When no redirect matches (the common case for ordinary page loads),
60
+ * matchRedirect previously ran exec() on every one of those regexes —
61
+ * ~2ms per call, ~2992ms total self-time in profiles.
62
+ *
63
+ * The index splits rules into two buckets:
64
+ *
65
+ * localeStatic — rules whose source is exactly /:paramName(alt1|alt2|...)?/suffix
66
+ * where `suffix` is a static path with no further params or regex groups.
67
+ * These are indexed in a Map<suffix, entry[]> for O(1) lookup after a
68
+ * single fast strip of the optional locale prefix.
69
+ *
70
+ * linear — all other rules. Matched with the original O(n) loop.
71
+ *
72
+ * The index is stored in a WeakMap keyed by the redirects array so it is
73
+ * computed once per config load and GC'd when the array is no longer live.
74
+ *
75
+ * ## Ordering invariant
76
+ *
77
+ * Redirect rules must be evaluated in their original order (first match wins).
78
+ * Each locale-static entry stores its `originalIndex` so that, when a
79
+ * locale-static fast-path match is found, any linear rules that appear earlier
80
+ * in the array are still checked first.
81
+ */
87
82
  /** Matches `/:param(alternation)?/static/suffix` — the locale-static pattern. */
88
83
  const _LOCALE_STATIC_RE = /^\/:[\w-]+\(([^)]+)\)\?\/([a-zA-Z0-9_~.%@!$&'*+,;=:/-]+)$/;
89
- const _redirectIndexCache = new WeakMap();
84
+ const _redirectIndexCache = /* @__PURE__ */ new WeakMap();
90
85
  /**
91
- * Build (or retrieve from cache) the redirect index for a given redirects array.
92
- *
93
- * Called once per config load from matchRedirect. The WeakMap ensures the index
94
- * is recomputed if the config is reloaded (new array reference) and GC'd when
95
- * the array is collected.
96
- */
86
+ * Build (or retrieve from cache) the redirect index for a given redirects array.
87
+ *
88
+ * Called once per config load from matchRedirect. The WeakMap ensures the index
89
+ * is recomputed if the config is reloaded (new array reference) and GC'd when
90
+ * the array is collected.
91
+ */
97
92
  function _getRedirectIndex(redirects) {
98
- let index = _redirectIndexCache.get(redirects);
99
- if (index !== undefined)
100
- return index;
101
- const localeStatic = new Map();
102
- const linear = [];
103
- for (let i = 0; i < redirects.length; i++) {
104
- const redirect = redirects[i];
105
- const m = _LOCALE_STATIC_RE.exec(redirect.source);
106
- if (m) {
107
- const paramName = redirect.source.slice(2, redirect.source.indexOf("("));
108
- const alternation = m[1];
109
- const suffix = "/" + m[2]; // e.g. "/security"
110
- // Build a small regex to validate the captured locale value against the
111
- // alternation. Using anchored match to avoid partial matches.
112
- // The alternation comes from user config; run it through safeRegExp to
113
- // guard against ReDoS in pathological configs.
114
- const altRe = safeRegExp("^(?:" + alternation + ")$");
115
- if (!altRe) {
116
- // Unsafe alternation — fall back to linear scan for this rule.
117
- linear.push([i, redirect]);
118
- continue;
119
- }
120
- const entry = { paramName, altRe, redirect, originalIndex: i };
121
- const bucket = localeStatic.get(suffix);
122
- if (bucket) {
123
- bucket.push(entry);
124
- }
125
- else {
126
- localeStatic.set(suffix, [entry]);
127
- }
128
- }
129
- else {
130
- linear.push([i, redirect]);
131
- }
132
- }
133
- index = { localeStatic, linear };
134
- _redirectIndexCache.set(redirects, index);
135
- return index;
93
+ let index = _redirectIndexCache.get(redirects);
94
+ if (index !== void 0) return index;
95
+ const localeStatic = /* @__PURE__ */ new Map();
96
+ const linear = [];
97
+ for (let i = 0; i < redirects.length; i++) {
98
+ const redirect = redirects[i];
99
+ const m = _LOCALE_STATIC_RE.exec(redirect.source);
100
+ if (m) {
101
+ const paramName = redirect.source.slice(2, redirect.source.indexOf("("));
102
+ const alternation = m[1];
103
+ const suffix = "/" + m[2];
104
+ const altRe = safeRegExp("^(?:" + alternation + ")$");
105
+ if (!altRe) {
106
+ linear.push([i, redirect]);
107
+ continue;
108
+ }
109
+ const entry = {
110
+ paramName,
111
+ altRe,
112
+ redirect,
113
+ originalIndex: i
114
+ };
115
+ const bucket = localeStatic.get(suffix);
116
+ if (bucket) bucket.push(entry);
117
+ else localeStatic.set(suffix, [entry]);
118
+ } else linear.push([i, redirect]);
119
+ }
120
+ index = {
121
+ localeStatic,
122
+ linear
123
+ };
124
+ _redirectIndexCache.set(redirects, index);
125
+ return index;
136
126
  }
137
127
  /** Hop-by-hop headers that should not be forwarded through a proxy. */
138
128
  const HOP_BY_HOP_HEADERS = new Set([
139
- "connection",
140
- "keep-alive",
141
- "proxy-authenticate",
142
- "proxy-authorization",
143
- "te",
144
- "trailers",
145
- "transfer-encoding",
146
- "upgrade",
129
+ "connection",
130
+ "keep-alive",
131
+ "proxy-authenticate",
132
+ "proxy-authorization",
133
+ "te",
134
+ "trailers",
135
+ "transfer-encoding",
136
+ "upgrade"
137
+ ]);
138
+ /**
139
+ * Request hop-by-hop headers to strip before proxying with fetch().
140
+ * Intentionally narrower than HOP_BY_HOP_HEADERS: external rewrite proxying
141
+ * still forwards proxy auth credentials, while response sanitization strips
142
+ * them before returning data to the client.
143
+ */
144
+ const REQUEST_HOP_BY_HOP_HEADERS = new Set([
145
+ "connection",
146
+ "keep-alive",
147
+ "te",
148
+ "trailers",
149
+ "transfer-encoding",
150
+ "upgrade"
147
151
  ]);
152
+ function stripHopByHopRequestHeaders(headers) {
153
+ const connectionTokens = (headers.get("connection") || "").split(",").map((value) => value.trim().toLowerCase()).filter(Boolean);
154
+ for (const header of REQUEST_HOP_BY_HOP_HEADERS) headers.delete(header);
155
+ for (const token of connectionTokens) headers.delete(token);
156
+ }
148
157
  /**
149
- * Detect regex patterns vulnerable to catastrophic backtracking (ReDoS).
150
- *
151
- * Uses a lightweight heuristic: scans the pattern string for nested quantifiers
152
- * (a quantifier applied to a group that itself contains a quantifier). This
153
- * catches the most common pathological patterns like `(a+)+`, `(.*)*`,
154
- * `([^/]+)+`, `(a|a+)+` without needing a full regex parser.
155
- *
156
- * Returns true if the pattern appears safe, false if it's potentially dangerous.
157
- */
158
- export function isSafeRegex(pattern) {
159
- // Track parenthesis nesting depth and whether we've seen a quantifier
160
- // at each depth level.
161
- const quantifierAtDepth = [];
162
- let depth = 0;
163
- let i = 0;
164
- while (i < pattern.length) {
165
- const ch = pattern[i];
166
- // Skip escaped characters
167
- if (ch === "\\") {
168
- i += 2;
169
- continue;
170
- }
171
- // Skip character classes [...] quantifiers inside them are literal
172
- if (ch === "[") {
173
- i++;
174
- while (i < pattern.length && pattern[i] !== "]") {
175
- if (pattern[i] === "\\")
176
- i++; // skip escaped char in class
177
- i++;
178
- }
179
- i++; // skip closing ]
180
- continue;
181
- }
182
- if (ch === "(") {
183
- depth++;
184
- // Initialize: no quantifier seen yet at this new depth
185
- if (quantifierAtDepth.length <= depth) {
186
- quantifierAtDepth.push(false);
187
- }
188
- else {
189
- quantifierAtDepth[depth] = false;
190
- }
191
- i++;
192
- continue;
193
- }
194
- if (ch === ")") {
195
- const hadQuantifier = depth > 0 && quantifierAtDepth[depth];
196
- if (depth > 0)
197
- depth--;
198
- // Look ahead for a quantifier on this group: +, *, {n,m}
199
- // Note: '?' after ')' means "zero or one" which does NOT cause catastrophic
200
- // backtracking it only allows 2 paths (match/skip), not exponential.
201
- // Only unbounded repetition (+, *, {n,}) on a group with inner quantifiers is dangerous.
202
- const next = pattern[i + 1];
203
- if (next === "+" || next === "*" || next === "{") {
204
- if (hadQuantifier) {
205
- // Nested quantifier detected: quantifier on a group that contains a quantifier
206
- return false;
207
- }
208
- // Mark the enclosing depth as having a quantifier
209
- if (depth >= 0 && depth < quantifierAtDepth.length) {
210
- quantifierAtDepth[depth] = true;
211
- }
212
- }
213
- i++;
214
- continue;
215
- }
216
- // Detect quantifiers: +, *, ?, {n,m}
217
- // '?' is a quantifier (optional) unless it follows another quantifier (+, *, ?, })
218
- // in which case it's a non-greedy modifier.
219
- if (ch === "+" || ch === "*") {
220
- if (depth > 0) {
221
- quantifierAtDepth[depth] = true;
222
- }
223
- i++;
224
- continue;
225
- }
226
- if (ch === "?") {
227
- // '?' after +, *, ?, or } is a non-greedy modifier, not a quantifier
228
- const prev = i > 0 ? pattern[i - 1] : "";
229
- if (prev !== "+" && prev !== "*" && prev !== "?" && prev !== "}") {
230
- if (depth > 0) {
231
- quantifierAtDepth[depth] = true;
232
- }
233
- }
234
- i++;
235
- continue;
236
- }
237
- if (ch === "{") {
238
- // Check if this is a quantifier {n}, {n,}, {n,m}
239
- let j = i + 1;
240
- while (j < pattern.length && /[\d,]/.test(pattern[j]))
241
- j++;
242
- if (j < pattern.length && pattern[j] === "}" && j > i + 1) {
243
- if (depth > 0) {
244
- quantifierAtDepth[depth] = true;
245
- }
246
- i = j + 1;
247
- continue;
248
- }
249
- }
250
- i++;
251
- }
252
- return true;
158
+ * Detect regex patterns vulnerable to catastrophic backtracking (ReDoS).
159
+ *
160
+ * Uses a lightweight heuristic: scans the pattern string for nested quantifiers
161
+ * (a quantifier applied to a group that itself contains a quantifier). This
162
+ * catches the most common pathological patterns like `(a+)+`, `(.*)*`,
163
+ * `([^/]+)+`, `(a|a+)+` without needing a full regex parser.
164
+ *
165
+ * Returns true if the pattern appears safe, false if it's potentially dangerous.
166
+ */
167
+ function isSafeRegex(pattern) {
168
+ const quantifierAtDepth = [];
169
+ let depth = 0;
170
+ let i = 0;
171
+ while (i < pattern.length) {
172
+ const ch = pattern[i];
173
+ if (ch === "\\") {
174
+ i += 2;
175
+ continue;
176
+ }
177
+ if (ch === "[") {
178
+ i++;
179
+ while (i < pattern.length && pattern[i] !== "]") {
180
+ if (pattern[i] === "\\") i++;
181
+ i++;
182
+ }
183
+ i++;
184
+ continue;
185
+ }
186
+ if (ch === "(") {
187
+ depth++;
188
+ if (quantifierAtDepth.length <= depth) quantifierAtDepth.push(false);
189
+ else quantifierAtDepth[depth] = false;
190
+ i++;
191
+ continue;
192
+ }
193
+ if (ch === ")") {
194
+ const hadQuantifier = depth > 0 && quantifierAtDepth[depth];
195
+ if (depth > 0) depth--;
196
+ const next = pattern[i + 1];
197
+ if (next === "+" || next === "*" || next === "{") {
198
+ if (hadQuantifier) return false;
199
+ if (depth >= 0 && depth < quantifierAtDepth.length) quantifierAtDepth[depth] = true;
200
+ }
201
+ i++;
202
+ continue;
203
+ }
204
+ if (ch === "+" || ch === "*") {
205
+ if (depth > 0) quantifierAtDepth[depth] = true;
206
+ i++;
207
+ continue;
208
+ }
209
+ if (ch === "?") {
210
+ const prev = i > 0 ? pattern[i - 1] : "";
211
+ if (prev !== "+" && prev !== "*" && prev !== "?" && prev !== "}") {
212
+ if (depth > 0) quantifierAtDepth[depth] = true;
213
+ }
214
+ i++;
215
+ continue;
216
+ }
217
+ if (ch === "{") {
218
+ let j = i + 1;
219
+ while (j < pattern.length && /[\d,]/.test(pattern[j])) j++;
220
+ if (j < pattern.length && pattern[j] === "}" && j > i + 1) {
221
+ if (depth > 0) quantifierAtDepth[depth] = true;
222
+ i = j + 1;
223
+ continue;
224
+ }
225
+ }
226
+ i++;
227
+ }
228
+ return true;
253
229
  }
254
230
  /**
255
- * Compile a regex pattern safely. Returns the compiled RegExp or null if the
256
- * pattern is invalid or vulnerable to ReDoS.
257
- *
258
- * Logs a warning when a pattern is rejected so developers can fix their config.
259
- */
260
- export function safeRegExp(pattern, flags) {
261
- if (!isSafeRegex(pattern)) {
262
- console.warn(`[vinext] Ignoring potentially unsafe regex pattern (ReDoS risk): ${pattern}\n` +
263
- ` Patterns with nested quantifiers (e.g. (a+)+) can cause catastrophic backtracking.\n` +
264
- ` Simplify the pattern to avoid nested repetition.`);
265
- return null;
266
- }
267
- try {
268
- return new RegExp(pattern, flags);
269
- }
270
- catch {
271
- return null;
272
- }
231
+ * Compile a regex pattern safely. Returns the compiled RegExp or null if the
232
+ * pattern is invalid or vulnerable to ReDoS.
233
+ *
234
+ * Logs a warning when a pattern is rejected so developers can fix their config.
235
+ */
236
+ function safeRegExp(pattern, flags) {
237
+ if (!isSafeRegex(pattern)) {
238
+ console.warn(`[vinext] Ignoring potentially unsafe regex pattern (ReDoS risk): ${pattern}\n Patterns with nested quantifiers (e.g. (a+)+) can cause catastrophic backtracking.\n Simplify the pattern to avoid nested repetition.`);
239
+ return null;
240
+ }
241
+ try {
242
+ return new RegExp(pattern, flags);
243
+ } catch {
244
+ return null;
245
+ }
273
246
  }
274
247
  /**
275
- * Convert a Next.js header/rewrite/redirect source pattern into a regex string.
276
- *
277
- * Regex groups in the source (e.g. `(\d+)`) are extracted first, the remaining
278
- * text is escaped/converted in a **single pass** (avoiding chained `.replace()`
279
- * which CodeQL flags as incomplete sanitization), then groups are restored.
280
- */
281
- export function escapeHeaderSource(source) {
282
- // Sentinel character for group placeholders. Uses a Unicode private-use-area
283
- // codepoint that will never appear in real source patterns.
284
- const S = "\uE000";
285
- // Step 1: extract regex groups and replace with numbered placeholders.
286
- const groups = [];
287
- const withPlaceholders = source.replace(/\(([^)]+)\)/g, (_m, inner) => {
288
- groups.push(inner);
289
- return `${S}G${groups.length - 1}${S}`;
290
- });
291
- // Step 2: single-pass conversion of the placeholder-bearing string.
292
- // Match named params (:[\w-]+), sentinel group placeholders, metacharacters, and literal text.
293
- // The regex uses non-overlapping alternatives to avoid backtracking:
294
- // :[\w-]+ — named parameter (constraint sentinel is checked procedurally;
295
- // param names may contain hyphens, e.g. :auth-method)
296
- // sentinel group — standalone regex group placeholder
297
- // [.+?*] single metachar to escape/convert
298
- // [^.+?*:\uE000]+ literal text (excludes all chars that start other alternatives)
299
- let result = "";
300
- const re = new RegExp(`${S}G(\\d+)${S}|:[\\w-]+|[.+?*]|[^.+?*:\\uE000]+`, // lgtm[js/redos] — alternatives are non-overlapping
301
- "g");
302
- let m;
303
- while ((m = re.exec(withPlaceholders)) !== null) {
304
- if (m[1] !== undefined) {
305
- // Standalone regex group — restore as-is
306
- result += `(${groups[Number(m[1])]})`;
307
- }
308
- else if (m[0].startsWith(":")) {
309
- // Named parameter — check if followed by a constraint group placeholder
310
- const afterParam = withPlaceholders.slice(re.lastIndex);
311
- const constraintMatch = afterParam.match(new RegExp(`^${S}G(\\d+)${S}`));
312
- if (constraintMatch) {
313
- // :param(constraint) — use the constraint as the capture group
314
- re.lastIndex += constraintMatch[0].length;
315
- result += `(${groups[Number(constraintMatch[1])]})`;
316
- }
317
- else {
318
- // Plain named parameter → match one segment
319
- result += "[^/]+";
320
- }
321
- }
322
- else {
323
- switch (m[0]) {
324
- case ".":
325
- result += "\\.";
326
- break;
327
- case "+":
328
- result += "\\+";
329
- break;
330
- case "?":
331
- result += "\\?";
332
- break;
333
- case "*":
334
- result += ".*";
335
- break;
336
- default:
337
- result += m[0];
338
- break;
339
- }
340
- }
341
- }
342
- return result;
248
+ * Convert a Next.js header/rewrite/redirect source pattern into a regex string.
249
+ *
250
+ * Regex groups in the source (e.g. `(\d+)`) are extracted first, the remaining
251
+ * text is escaped/converted in a **single pass** (avoiding chained `.replace()`
252
+ * which CodeQL flags as incomplete sanitization), then groups are restored.
253
+ */
254
+ function escapeHeaderSource(source) {
255
+ const S = "";
256
+ const groups = [];
257
+ const withPlaceholders = source.replace(/\(([^)]+)\)/g, (_m, inner) => {
258
+ groups.push(inner);
259
+ return `${S}G${groups.length - 1}${S}`;
260
+ });
261
+ let result = "";
262
+ const re = new RegExp(`${S}G(\\d+)${S}|:[\\w-]+|[.+?*]|[^.+?*:\\uE000]+`, "g");
263
+ let m;
264
+ while ((m = re.exec(withPlaceholders)) !== null) if (m[1] !== void 0) result += `(${groups[Number(m[1])]})`;
265
+ else if (m[0].startsWith(":")) {
266
+ const constraintMatch = withPlaceholders.slice(re.lastIndex).match(new RegExp(`^${S}G(\\d+)${S}`));
267
+ if (constraintMatch) {
268
+ re.lastIndex += constraintMatch[0].length;
269
+ result += `(${groups[Number(constraintMatch[1])]})`;
270
+ } else result += "[^/]+";
271
+ } else switch (m[0]) {
272
+ case ".":
273
+ result += "\\.";
274
+ break;
275
+ case "+":
276
+ result += "\\+";
277
+ break;
278
+ case "?":
279
+ result += "\\?";
280
+ break;
281
+ case "*":
282
+ result += ".*";
283
+ break;
284
+ default:
285
+ result += m[0];
286
+ break;
287
+ }
288
+ return result;
343
289
  }
344
290
  /**
345
- * Parse a Cookie header string into a key-value record.
346
- */
347
- export function parseCookies(cookieHeader) {
348
- if (!cookieHeader)
349
- return {};
350
- const cookies = {};
351
- for (const part of cookieHeader.split(";")) {
352
- const eq = part.indexOf("=");
353
- if (eq === -1)
354
- continue;
355
- const key = part.slice(0, eq).trim();
356
- const value = part.slice(eq + 1).trim();
357
- if (key)
358
- cookies[key] = value;
359
- }
360
- return cookies;
291
+ * Parse a Cookie header string into a key-value record.
292
+ */
293
+ function parseCookies(cookieHeader) {
294
+ if (!cookieHeader) return {};
295
+ const cookies = {};
296
+ for (const part of cookieHeader.split(";")) {
297
+ const eq = part.indexOf("=");
298
+ if (eq === -1) continue;
299
+ const key = part.slice(0, eq).trim();
300
+ const value = part.slice(eq + 1).trim();
301
+ if (key) cookies[key] = value;
302
+ }
303
+ return cookies;
361
304
  }
362
305
  /**
363
- * Build a RequestContext from a Web Request object.
364
- */
365
- export function requestContextFromRequest(request) {
366
- const url = new URL(request.url);
367
- return {
368
- headers: request.headers,
369
- cookies: parseCookies(request.headers.get("cookie")),
370
- query: url.searchParams,
371
- host: normalizeHost(request.headers.get("host"), url.hostname),
372
- };
306
+ * Build a RequestContext from a Web Request object.
307
+ */
308
+ function requestContextFromRequest(request) {
309
+ const url = new URL(request.url);
310
+ return {
311
+ headers: request.headers,
312
+ cookies: parseCookies(request.headers.get("cookie")),
313
+ query: url.searchParams,
314
+ host: normalizeHost(request.headers.get("host"), url.hostname)
315
+ };
373
316
  }
374
- export function normalizeHost(hostHeader, fallbackHostname) {
375
- const host = hostHeader ?? fallbackHostname;
376
- return host.split(":", 1)[0].toLowerCase();
317
+ function normalizeHost(hostHeader, fallbackHostname) {
318
+ return (hostHeader ?? fallbackHostname).split(":", 1)[0].toLowerCase();
377
319
  }
378
320
  /**
379
- * Unpack `x-middleware-request-*` headers from the collected middleware
380
- * response headers into the actual request, and strip all `x-middleware-*`
381
- * internal signals so they never reach clients.
382
- *
383
- * `middlewareHeaders` is mutated in-place (matching keys are deleted).
384
- * Returns a (possibly cloned) `Request` with the unpacked headers applied,
385
- * and a fresh `RequestContext` built from it — ready for post-middleware
386
- * config rule matching (beforeFiles, afterFiles, fallback).
387
- *
388
- * Works for both Node.js requests (mutable headers) and Workers requests
389
- * (immutable — cloned only when there are headers to apply).
390
- *
391
- * `x-middleware-request-*` values are always plain strings (they carry
392
- * individual header values), so the wider `string | string[]` type of
393
- * `middlewareHeaders` is safe to cast here.
394
- */
395
- export function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
396
- const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders);
397
- for (const key of Object.keys(middlewareHeaders)) {
398
- if (key.startsWith("x-middleware-")) {
399
- delete middlewareHeaders[key];
400
- }
401
- }
402
- if (nextHeaders) {
403
- // Headers may be immutable (Workers), so always clone via new Headers().
404
- request = new Request(request.url, {
405
- method: request.method,
406
- headers: nextHeaders,
407
- body: request.body,
408
- // @ts-expect-error — duplex needed for streaming request bodies
409
- duplex: request.body ? "half" : undefined,
410
- });
411
- }
412
- return { request, postMwReqCtx: requestContextFromRequest(request) };
321
+ * Unpack `x-middleware-request-*` headers from the collected middleware
322
+ * response headers into the actual request, and strip all `x-middleware-*`
323
+ * internal signals so they never reach clients.
324
+ *
325
+ * `middlewareHeaders` is mutated in-place (matching keys are deleted).
326
+ * Returns a (possibly cloned) `Request` with the unpacked headers applied,
327
+ * and a fresh `RequestContext` built from it — ready for post-middleware
328
+ * config rule matching (beforeFiles, afterFiles, fallback).
329
+ *
330
+ * Works for both Node.js requests (mutable headers) and Workers requests
331
+ * (immutable — cloned only when there are headers to apply).
332
+ *
333
+ * `x-middleware-request-*` values are always plain strings (they carry
334
+ * individual header values), so the wider `string | string[]` type of
335
+ * `middlewareHeaders` is safe to cast here.
336
+ */
337
+ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
338
+ const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders);
339
+ for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-")) delete middlewareHeaders[key];
340
+ if (nextHeaders) request = new Request(request.url, {
341
+ method: request.method,
342
+ headers: nextHeaders,
343
+ body: request.body,
344
+ duplex: request.body ? "half" : void 0
345
+ });
346
+ return {
347
+ request,
348
+ postMwReqCtx: requestContextFromRequest(request)
349
+ };
350
+ }
351
+ function _emptyParams() {
352
+ return Object.create(null);
353
+ }
354
+ function _matchConditionValue(actualValue, expectedValue) {
355
+ if (expectedValue === void 0) return _emptyParams();
356
+ const re = _cachedConditionRegex(expectedValue);
357
+ if (re) {
358
+ const match = re.exec(actualValue);
359
+ if (!match) return null;
360
+ const params = _emptyParams();
361
+ if (match.groups) {
362
+ for (const [key, value] of Object.entries(match.groups)) if (value !== void 0) params[key] = value;
363
+ }
364
+ return params;
365
+ }
366
+ return actualValue === expectedValue ? _emptyParams() : null;
413
367
  }
414
368
  /**
415
- * Check a single has/missing condition against request context.
416
- * Returns true if the condition is satisfied.
417
- */
418
- function checkSingleCondition(condition, ctx) {
419
- switch (condition.type) {
420
- case "header": {
421
- const headerValue = ctx.headers.get(condition.key);
422
- if (headerValue === null)
423
- return false;
424
- if (condition.value !== undefined) {
425
- const re = _cachedConditionRegex(condition.value);
426
- if (re)
427
- return re.test(headerValue);
428
- return headerValue === condition.value;
429
- }
430
- return true; // Key exists, no value constraint
431
- }
432
- case "cookie": {
433
- const cookieValue = ctx.cookies[condition.key];
434
- if (cookieValue === undefined)
435
- return false;
436
- if (condition.value !== undefined) {
437
- const re = _cachedConditionRegex(condition.value);
438
- if (re)
439
- return re.test(cookieValue);
440
- return cookieValue === condition.value;
441
- }
442
- return true;
443
- }
444
- case "query": {
445
- const queryValue = ctx.query.get(condition.key);
446
- if (queryValue === null)
447
- return false;
448
- if (condition.value !== undefined) {
449
- const re = _cachedConditionRegex(condition.value);
450
- if (re)
451
- return re.test(queryValue);
452
- return queryValue === condition.value;
453
- }
454
- return true;
455
- }
456
- case "host": {
457
- if (condition.value !== undefined) {
458
- const re = _cachedConditionRegex(condition.value);
459
- if (re)
460
- return re.test(ctx.host);
461
- return ctx.host === condition.value;
462
- }
463
- return ctx.host === condition.key;
464
- }
465
- default:
466
- return false;
467
- }
369
+ * Check a single has/missing condition against request context.
370
+ * Returns captured params when the condition is satisfied, or null otherwise.
371
+ */
372
+ function matchSingleCondition(condition, ctx) {
373
+ switch (condition.type) {
374
+ case "header": {
375
+ const headerValue = ctx.headers.get(condition.key);
376
+ if (headerValue === null) return null;
377
+ return _matchConditionValue(headerValue, condition.value);
378
+ }
379
+ case "cookie": {
380
+ const cookieValue = ctx.cookies[condition.key];
381
+ if (cookieValue === void 0) return null;
382
+ return _matchConditionValue(cookieValue, condition.value);
383
+ }
384
+ case "query": {
385
+ const queryValue = ctx.query.get(condition.key);
386
+ if (queryValue === null) return null;
387
+ return _matchConditionValue(queryValue, condition.value);
388
+ }
389
+ case "host":
390
+ if (condition.value !== void 0) return _matchConditionValue(ctx.host, condition.value);
391
+ return ctx.host === condition.key ? _emptyParams() : null;
392
+ default: return null;
393
+ }
468
394
  }
469
395
  /**
470
- * Return a cached RegExp for a has/missing condition value string, compiling
471
- * on first use. Returns null if safeRegExp rejected the pattern or if the
472
- * value is not a valid regex (fall back to exact string comparison).
473
- */
396
+ * Return a cached RegExp for a has/missing condition value string, compiling
397
+ * on first use. Returns null if safeRegExp rejected the pattern or if the
398
+ * value is not a valid regex (fall back to exact string comparison).
399
+ */
474
400
  function _cachedConditionRegex(value) {
475
- let re = _compiledConditionCache.get(value);
476
- if (re === undefined) {
477
- re = safeRegExp(value);
478
- _compiledConditionCache.set(value, re);
479
- }
480
- return re;
401
+ let re = _compiledConditionCache.get(value);
402
+ if (re === void 0) {
403
+ re = safeRegExp(value);
404
+ _compiledConditionCache.set(value, re);
405
+ }
406
+ return re;
481
407
  }
482
408
  /**
483
- * Check all has/missing conditions for a config rule.
484
- * Returns true if the rule should be applied (all has conditions pass, all missing conditions pass).
485
- *
486
- * - has: every condition must match (the request must have it)
487
- * - missing: every condition must NOT match (the request must not have it)
488
- */
489
- export function checkHasConditions(has, missing, ctx) {
490
- if (has) {
491
- for (const condition of has) {
492
- if (!checkSingleCondition(condition, ctx))
493
- return false;
494
- }
495
- }
496
- if (missing) {
497
- for (const condition of missing) {
498
- if (checkSingleCondition(condition, ctx))
499
- return false;
500
- }
501
- }
502
- return true;
409
+ * Check all has/missing conditions for a config rule.
410
+ * Returns true if the rule should be applied (all has conditions pass, all missing conditions pass).
411
+ *
412
+ * - has: every condition must match (the request must have it)
413
+ * - missing: every condition must NOT match (the request must not have it)
414
+ */
415
+ function collectConditionParams(has, missing, ctx) {
416
+ const params = _emptyParams();
417
+ if (has) for (const condition of has) {
418
+ const conditionParams = matchSingleCondition(condition, ctx);
419
+ if (!conditionParams) return null;
420
+ Object.assign(params, conditionParams);
421
+ }
422
+ if (missing) {
423
+ for (const condition of missing) if (matchSingleCondition(condition, ctx)) return null;
424
+ }
425
+ return params;
426
+ }
427
+ function checkHasConditions(has, missing, ctx) {
428
+ return collectConditionParams(has, missing, ctx) !== null;
503
429
  }
504
430
  /**
505
- * If the current position in `str` starts with a parenthesized group, consume
506
- * it and advance `re.lastIndex` past the closing `)`. Returns the group
507
- * contents or null if no group is present.
508
- */
431
+ * If the current position in `str` starts with a parenthesized group, consume
432
+ * it and advance `re.lastIndex` past the closing `)`. Returns the group
433
+ * contents or null if no group is present.
434
+ */
509
435
  function extractConstraint(str, re) {
510
- if (str[re.lastIndex] !== "(")
511
- return null;
512
- const start = re.lastIndex + 1;
513
- let depth = 1;
514
- let i = start;
515
- while (i < str.length && depth > 0) {
516
- if (str[i] === "(")
517
- depth++;
518
- else if (str[i] === ")")
519
- depth--;
520
- i++;
521
- }
522
- if (depth !== 0)
523
- return null;
524
- re.lastIndex = i;
525
- return str.slice(start, i - 1);
436
+ if (str[re.lastIndex] !== "(") return null;
437
+ const start = re.lastIndex + 1;
438
+ let depth = 1;
439
+ let i = start;
440
+ while (i < str.length && depth > 0) {
441
+ if (str[i] === "(") depth++;
442
+ else if (str[i] === ")") depth--;
443
+ i++;
444
+ }
445
+ if (depth !== 0) return null;
446
+ re.lastIndex = i;
447
+ return str.slice(start, i - 1);
526
448
  }
527
449
  /**
528
- * Match a Next.js config pattern (from redirects/rewrites sources) against a pathname.
529
- * Returns matched params or null.
530
- *
531
- * Supports:
532
- * :param - matches a single path segment
533
- * :param* - matches zero or more segments (catch-all)
534
- * :param+ - matches one or more segments
535
- * (regex) - inline regex patterns in the source
536
- * :param(constraint) - named param with inline regex constraint
537
- */
538
- export function matchConfigPattern(pathname, pattern) {
539
- // If the pattern contains regex groups like (\d+) or (.*), use regex matching.
540
- // Also enter this branch when a catch-all parameter (:param* or :param+) is
541
- // followed by a literal suffix (e.g. "/:path*.md"). Without this, the suffix
542
- // pattern falls through to the simple segment matcher which incorrectly treats
543
- // the whole segment (":path*.md") as a named parameter and matches everything.
544
- // The last condition catches simple params with literal suffixes (e.g. "/:slug.md")
545
- // where the param name is followed by a dot — the simple matcher would treat
546
- // "slug.md" as the param name and match any single segment regardless of suffix.
547
- if (pattern.includes("(") ||
548
- pattern.includes("\\") ||
549
- /:[\w-]+[*+][^/]/.test(pattern) ||
550
- /:[\w-]+\./.test(pattern)) {
551
- try {
552
- // Look up the compiled regex in the module-level cache. Patterns come
553
- // from next.config.js and are static, so we only need to compile each
554
- // one once across the lifetime of the worker/server process.
555
- let compiled = _compiledPatternCache.get(pattern);
556
- if (compiled === undefined) {
557
- // Cache miss — compile the pattern now and store the result.
558
- // Param names may contain hyphens (e.g. :auth-method, :sign-in).
559
- const paramNames = [];
560
- // Single-pass conversion with procedural suffix handling. The tokenizer
561
- // matches only simple, non-overlapping tokens; quantifier/constraint
562
- // suffixes after :param are consumed procedurally to avoid polynomial
563
- // backtracking in the regex engine.
564
- let regexStr = "";
565
- const tokenRe = /:([\w-]+)|[.]|[^:.]+/g; // lgtm[js/redos] — alternatives are non-overlapping (`:` and `.` excluded from `[^:.]+`)
566
- let tok;
567
- while ((tok = tokenRe.exec(pattern)) !== null) {
568
- if (tok[1] !== undefined) {
569
- const name = tok[1];
570
- const rest = pattern.slice(tokenRe.lastIndex);
571
- // Check for quantifier (* or +) with optional constraint
572
- if (rest.startsWith("*") || rest.startsWith("+")) {
573
- const quantifier = rest[0];
574
- tokenRe.lastIndex += 1;
575
- const constraint = extractConstraint(pattern, tokenRe);
576
- paramNames.push(name);
577
- if (constraint !== null) {
578
- regexStr += `(${constraint})`;
579
- }
580
- else {
581
- regexStr += quantifier === "*" ? "(.*)" : "(.+)";
582
- }
583
- }
584
- else {
585
- // Check for inline constraint without quantifier
586
- const constraint = extractConstraint(pattern, tokenRe);
587
- paramNames.push(name);
588
- regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
589
- }
590
- }
591
- else if (tok[0] === ".") {
592
- regexStr += "\\.";
593
- }
594
- else {
595
- regexStr += tok[0];
596
- }
597
- }
598
- const re = safeRegExp("^" + regexStr + "$");
599
- // Store null for rejected patterns so we don't re-run isSafeRegex.
600
- compiled = re ? { re, paramNames } : null;
601
- _compiledPatternCache.set(pattern, compiled);
602
- }
603
- if (!compiled)
604
- return null;
605
- const match = compiled.re.exec(pathname);
606
- if (!match)
607
- return null;
608
- const params = Object.create(null);
609
- for (let i = 0; i < compiled.paramNames.length; i++) {
610
- params[compiled.paramNames[i]] = match[i + 1] ?? "";
611
- }
612
- return params;
613
- }
614
- catch {
615
- // Fall through to segment-based matching
616
- }
617
- }
618
- // Check for catch-all patterns (:param* or :param+) without regex groups
619
- // Param names may contain hyphens (e.g. :sign-in*, :sign-up+).
620
- const catchAllMatch = pattern.match(/:([\w-]+)(\*|\+)$/);
621
- if (catchAllMatch) {
622
- const prefix = pattern.slice(0, pattern.lastIndexOf(":"));
623
- const paramName = catchAllMatch[1];
624
- const isPlus = catchAllMatch[2] === "+";
625
- const prefixNoSlash = prefix.replace(/\/$/, "");
626
- if (!pathname.startsWith(prefixNoSlash))
627
- return null;
628
- const charAfter = pathname[prefixNoSlash.length];
629
- if (charAfter !== undefined && charAfter !== "/")
630
- return null;
631
- const rest = pathname.slice(prefixNoSlash.length);
632
- if (isPlus && (!rest || rest === "/"))
633
- return null;
634
- let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
635
- // NOTE: Do NOT decodeURIComponent here. The pathname is already decoded at
636
- // the request entry point. Decoding again would produce incorrect param values.
637
- return { [paramName]: restValue };
638
- }
639
- // Simple segment-based matching for exact patterns and :param
640
- const parts = pattern.split("/");
641
- const pathParts = pathname.split("/");
642
- if (parts.length !== pathParts.length)
643
- return null;
644
- const params = Object.create(null);
645
- for (let i = 0; i < parts.length; i++) {
646
- if (parts[i].startsWith(":")) {
647
- params[parts[i].slice(1)] = pathParts[i];
648
- }
649
- else if (parts[i] !== pathParts[i]) {
650
- return null;
651
- }
652
- }
653
- return params;
450
+ * Match a Next.js config pattern (from redirects/rewrites sources) against a pathname.
451
+ * Returns matched params or null.
452
+ *
453
+ * Supports:
454
+ * :param - matches a single path segment
455
+ * :param* - matches zero or more segments (catch-all)
456
+ * :param+ - matches one or more segments
457
+ * (regex) - inline regex patterns in the source
458
+ * :param(constraint) - named param with inline regex constraint
459
+ */
460
+ function matchConfigPattern(pathname, pattern) {
461
+ if (pattern.includes("(") || pattern.includes("\\") || /:[\w-]+[*+][^/]/.test(pattern) || /:[\w-]+\./.test(pattern)) try {
462
+ let compiled = _compiledPatternCache.get(pattern);
463
+ if (compiled === void 0) {
464
+ const paramNames = [];
465
+ let regexStr = "";
466
+ const tokenRe = /:([\w-]+)|[.]|[^:.]+/g;
467
+ let tok;
468
+ while ((tok = tokenRe.exec(pattern)) !== null) if (tok[1] !== void 0) {
469
+ const name = tok[1];
470
+ const rest = pattern.slice(tokenRe.lastIndex);
471
+ if (rest.startsWith("*") || rest.startsWith("+")) {
472
+ const quantifier = rest[0];
473
+ tokenRe.lastIndex += 1;
474
+ const constraint = extractConstraint(pattern, tokenRe);
475
+ paramNames.push(name);
476
+ if (constraint !== null) regexStr += `(${constraint})`;
477
+ else regexStr += quantifier === "*" ? "(.*)" : "(.+)";
478
+ } else {
479
+ const constraint = extractConstraint(pattern, tokenRe);
480
+ paramNames.push(name);
481
+ regexStr += constraint !== null ? `(${constraint})` : "([^/]+)";
482
+ }
483
+ } else if (tok[0] === ".") regexStr += "\\.";
484
+ else regexStr += tok[0];
485
+ const re = safeRegExp("^" + regexStr + "$");
486
+ compiled = re ? {
487
+ re,
488
+ paramNames
489
+ } : null;
490
+ _compiledPatternCache.set(pattern, compiled);
491
+ }
492
+ if (!compiled) return null;
493
+ const match = compiled.re.exec(pathname);
494
+ if (!match) return null;
495
+ const params = Object.create(null);
496
+ for (let i = 0; i < compiled.paramNames.length; i++) params[compiled.paramNames[i]] = match[i + 1] ?? "";
497
+ return params;
498
+ } catch {}
499
+ const catchAllMatch = pattern.match(/:([\w-]+)(\*|\+)$/);
500
+ if (catchAllMatch) {
501
+ const prefix = pattern.slice(0, pattern.lastIndexOf(":"));
502
+ const paramName = catchAllMatch[1];
503
+ const isPlus = catchAllMatch[2] === "+";
504
+ const prefixNoSlash = prefix.replace(/\/$/, "");
505
+ if (!pathname.startsWith(prefixNoSlash)) return null;
506
+ const charAfter = pathname[prefixNoSlash.length];
507
+ if (charAfter !== void 0 && charAfter !== "/") return null;
508
+ const rest = pathname.slice(prefixNoSlash.length);
509
+ if (isPlus && (!rest || rest === "/")) return null;
510
+ let restValue = rest.startsWith("/") ? rest.slice(1) : rest;
511
+ return { [paramName]: restValue };
512
+ }
513
+ const parts = pattern.split("/");
514
+ const pathParts = pathname.split("/");
515
+ if (parts.length !== pathParts.length) return null;
516
+ const params = Object.create(null);
517
+ for (let i = 0; i < parts.length; i++) if (parts[i].startsWith(":")) params[parts[i].slice(1)] = pathParts[i];
518
+ else if (parts[i] !== pathParts[i]) return null;
519
+ return params;
654
520
  }
655
521
  /**
656
- * Apply redirect rules from next.config.js.
657
- * Returns the redirect info if a redirect was matched, or null.
658
- *
659
- * `ctx` provides the request context (cookies, headers, query, host) used
660
- * to evaluate has/missing conditions. Next.js always has request context
661
- * when evaluating redirects, so this parameter is required.
662
- *
663
- * ## Performance
664
- *
665
- * Rules with a locale-capture-group prefix (the dominant pattern in large
666
- * Next.js apps — e.g. `/:locale(en|es|fr|...)?/some-path`) are handled via
667
- * a pre-built index. Instead of running exec() on each locale regex
668
- * individually, we:
669
- *
670
- * 1. Strip the optional locale prefix from the pathname with one cheap
671
- * string-slice check (no regex exec on the hot path).
672
- * 2. Look up the stripped suffix in a Map<suffix, entry[]>.
673
- * 3. For each matching entry, validate the captured locale string against
674
- * a small, anchored alternation regex.
675
- *
676
- * This reduces the per-request cost from O(n × regex) to O(1) map lookup +
677
- * O(matches × tiny-regex), eliminating the ~2992ms self-time reported in
678
- * profiles for apps with 63+ locale-prefixed rules.
679
- *
680
- * Rules that don't fit the locale-static pattern fall back to the original
681
- * linear matchConfigPattern scan.
682
- *
683
- * ## Ordering invariant
684
- *
685
- * First match wins, preserving the original redirect array order. When a
686
- * locale-static fast-path match is found at position N, all linear rules with
687
- * an original index < N are checked via matchConfigPattern first — they are
688
- * few in practice (typically zero) so this is not a hot-path concern.
689
- */
690
- export function matchRedirect(pathname, redirects, ctx) {
691
- if (redirects.length === 0)
692
- return null;
693
- const index = _getRedirectIndex(redirects);
694
- // --- Locate the best locale-static candidate ---
695
- //
696
- // We look for the locale-static entry with the LOWEST originalIndex that
697
- // matches this pathname (and passes has/missing conditions).
698
- //
699
- // Strategy: try both the full pathname (locale omitted, e.g. "/security")
700
- // and the pathname with the first segment stripped (locale present, e.g.
701
- // "/en/security" → suffix "/security", locale "en").
702
- //
703
- // We do NOT use a regex here — just a single indexOf('/') to locate the
704
- // second slash, which is O(n) on the path length but far cheaper than
705
- // running 63 compiled regexes.
706
- let localeMatch = null;
707
- let localeMatchIndex = Infinity;
708
- if (index.localeStatic.size > 0) {
709
- // Case 1: no locale prefix — pathname IS the suffix.
710
- const noLocaleBucket = index.localeStatic.get(pathname);
711
- if (noLocaleBucket) {
712
- for (const entry of noLocaleBucket) {
713
- if (entry.originalIndex >= localeMatchIndex)
714
- continue; // already have a better match
715
- const redirect = entry.redirect;
716
- if (redirect.has || redirect.missing) {
717
- if (!checkHasConditions(redirect.has, redirect.missing, ctx))
718
- continue;
719
- }
720
- // Locale was omitted (the `?` made it optional) — param value is "".
721
- let dest = substituteDestinationParams(redirect.destination, {
722
- [entry.paramName]: "",
723
- });
724
- dest = sanitizeDestination(dest);
725
- localeMatch = { destination: dest, permanent: redirect.permanent };
726
- localeMatchIndex = entry.originalIndex;
727
- break; // bucket entries are in insertion order = original order
728
- }
729
- }
730
- // Case 2: locale prefix present — first path segment is the locale.
731
- // Find the second slash: pathname = "/locale/rest/of/path"
732
- // ^--- slashTwo
733
- const slashTwo = pathname.indexOf("/", 1);
734
- if (slashTwo !== -1) {
735
- const suffix = pathname.slice(slashTwo); // e.g. "/security"
736
- const localePart = pathname.slice(1, slashTwo); // e.g. "en"
737
- const localeBucket = index.localeStatic.get(suffix);
738
- if (localeBucket) {
739
- for (const entry of localeBucket) {
740
- if (entry.originalIndex >= localeMatchIndex)
741
- continue;
742
- // Validate that `localePart` is one of the allowed alternation values.
743
- if (!entry.altRe.test(localePart))
744
- continue;
745
- const redirect = entry.redirect;
746
- if (redirect.has || redirect.missing) {
747
- if (!checkHasConditions(redirect.has, redirect.missing, ctx))
748
- continue;
749
- }
750
- let dest = substituteDestinationParams(redirect.destination, {
751
- [entry.paramName]: localePart,
752
- });
753
- dest = sanitizeDestination(dest);
754
- localeMatch = { destination: dest, permanent: redirect.permanent };
755
- localeMatchIndex = entry.originalIndex;
756
- break; // bucket entries are in insertion order = original order
757
- }
758
- }
759
- }
760
- }
761
- // --- Linear fallback: all non-locale-static rules ---
762
- //
763
- // We only need to check linear rules whose originalIndex < localeMatchIndex.
764
- // If localeMatchIndex is Infinity (no locale match), we check all of them.
765
- for (const [origIdx, redirect] of index.linear) {
766
- if (origIdx >= localeMatchIndex) {
767
- // This linear rule comes after the best locale-static match —
768
- // the locale-static match wins. Stop scanning.
769
- break;
770
- }
771
- const params = matchConfigPattern(pathname, redirect.source);
772
- if (params) {
773
- if (redirect.has || redirect.missing) {
774
- if (!checkHasConditions(redirect.has, redirect.missing, ctx)) {
775
- continue;
776
- }
777
- }
778
- let dest = substituteDestinationParams(redirect.destination, params);
779
- // Collapse protocol-relative URLs (e.g. //evil.com from decoded %2F in catch-all params).
780
- dest = sanitizeDestination(dest);
781
- return { destination: dest, permanent: redirect.permanent };
782
- }
783
- }
784
- // Return the locale-static match if found (no earlier linear rule matched).
785
- return localeMatch;
522
+ * Apply redirect rules from next.config.js.
523
+ * Returns the redirect info if a redirect was matched, or null.
524
+ *
525
+ * `ctx` provides the request context (cookies, headers, query, host) used
526
+ * to evaluate has/missing conditions. Next.js always has request context
527
+ * when evaluating redirects, so this parameter is required.
528
+ *
529
+ * ## Performance
530
+ *
531
+ * Rules with a locale-capture-group prefix (the dominant pattern in large
532
+ * Next.js apps — e.g. `/:locale(en|es|fr|...)?/some-path`) are handled via
533
+ * a pre-built index. Instead of running exec() on each locale regex
534
+ * individually, we:
535
+ *
536
+ * 1. Strip the optional locale prefix from the pathname with one cheap
537
+ * string-slice check (no regex exec on the hot path).
538
+ * 2. Look up the stripped suffix in a Map<suffix, entry[]>.
539
+ * 3. For each matching entry, validate the captured locale string against
540
+ * a small, anchored alternation regex.
541
+ *
542
+ * This reduces the per-request cost from O(n × regex) to O(1) map lookup +
543
+ * O(matches × tiny-regex), eliminating the ~2992ms self-time reported in
544
+ * profiles for apps with 63+ locale-prefixed rules.
545
+ *
546
+ * Rules that don't fit the locale-static pattern fall back to the original
547
+ * linear matchConfigPattern scan.
548
+ *
549
+ * ## Ordering invariant
550
+ *
551
+ * First match wins, preserving the original redirect array order. When a
552
+ * locale-static fast-path match is found at position N, all linear rules with
553
+ * an original index < N are checked via matchConfigPattern first — they are
554
+ * few in practice (typically zero) so this is not a hot-path concern.
555
+ */
556
+ function matchRedirect(pathname, redirects, ctx) {
557
+ if (redirects.length === 0) return null;
558
+ const index = _getRedirectIndex(redirects);
559
+ let localeMatch = null;
560
+ let localeMatchIndex = Infinity;
561
+ if (index.localeStatic.size > 0) {
562
+ const noLocaleBucket = index.localeStatic.get(pathname);
563
+ if (noLocaleBucket) for (const entry of noLocaleBucket) {
564
+ if (entry.originalIndex >= localeMatchIndex) continue;
565
+ const redirect = entry.redirect;
566
+ const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
567
+ if (!conditionParams) continue;
568
+ let dest = substituteDestinationParams(redirect.destination, {
569
+ [entry.paramName]: "",
570
+ ...conditionParams
571
+ });
572
+ dest = sanitizeDestination(dest);
573
+ localeMatch = {
574
+ destination: dest,
575
+ permanent: redirect.permanent
576
+ };
577
+ localeMatchIndex = entry.originalIndex;
578
+ break;
579
+ }
580
+ const slashTwo = pathname.indexOf("/", 1);
581
+ if (slashTwo !== -1) {
582
+ const suffix = pathname.slice(slashTwo);
583
+ const localePart = pathname.slice(1, slashTwo);
584
+ const localeBucket = index.localeStatic.get(suffix);
585
+ if (localeBucket) for (const entry of localeBucket) {
586
+ if (entry.originalIndex >= localeMatchIndex) continue;
587
+ if (!entry.altRe.test(localePart)) continue;
588
+ const redirect = entry.redirect;
589
+ const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
590
+ if (!conditionParams) continue;
591
+ let dest = substituteDestinationParams(redirect.destination, {
592
+ [entry.paramName]: localePart,
593
+ ...conditionParams
594
+ });
595
+ dest = sanitizeDestination(dest);
596
+ localeMatch = {
597
+ destination: dest,
598
+ permanent: redirect.permanent
599
+ };
600
+ localeMatchIndex = entry.originalIndex;
601
+ break;
602
+ }
603
+ }
604
+ }
605
+ for (const [origIdx, redirect] of index.linear) {
606
+ if (origIdx >= localeMatchIndex) break;
607
+ const params = matchConfigPattern(pathname, redirect.source);
608
+ if (params) {
609
+ const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
610
+ if (!conditionParams) continue;
611
+ let dest = substituteDestinationParams(redirect.destination, {
612
+ ...params,
613
+ ...conditionParams
614
+ });
615
+ dest = sanitizeDestination(dest);
616
+ return {
617
+ destination: dest,
618
+ permanent: redirect.permanent
619
+ };
620
+ }
621
+ }
622
+ return localeMatch;
786
623
  }
787
624
  /**
788
- * Apply rewrite rules from next.config.js.
789
- * Returns the rewritten URL or null if no rewrite matched.
790
- *
791
- * `ctx` provides the request context (cookies, headers, query, host) used
792
- * to evaluate has/missing conditions. Next.js always has request context
793
- * when evaluating rewrites, so this parameter is required.
794
- */
795
- export function matchRewrite(pathname, rewrites, ctx) {
796
- for (const rewrite of rewrites) {
797
- const params = matchConfigPattern(pathname, rewrite.source);
798
- if (params) {
799
- if (rewrite.has || rewrite.missing) {
800
- if (!checkHasConditions(rewrite.has, rewrite.missing, ctx)) {
801
- continue;
802
- }
803
- }
804
- let dest = substituteDestinationParams(rewrite.destination, params);
805
- // Collapse protocol-relative URLs (e.g. //evil.com from decoded %2F in catch-all params).
806
- dest = sanitizeDestination(dest);
807
- return dest;
808
- }
809
- }
810
- return null;
625
+ * Apply rewrite rules from next.config.js.
626
+ * Returns the rewritten URL or null if no rewrite matched.
627
+ *
628
+ * `ctx` provides the request context (cookies, headers, query, host) used
629
+ * to evaluate has/missing conditions. Next.js always has request context
630
+ * when evaluating rewrites, so this parameter is required.
631
+ */
632
+ function matchRewrite(pathname, rewrites, ctx) {
633
+ for (const rewrite of rewrites) {
634
+ const params = matchConfigPattern(pathname, rewrite.source);
635
+ if (params) {
636
+ const conditionParams = rewrite.has || rewrite.missing ? collectConditionParams(rewrite.has, rewrite.missing, ctx) : _emptyParams();
637
+ if (!conditionParams) continue;
638
+ let dest = substituteDestinationParams(rewrite.destination, {
639
+ ...params,
640
+ ...conditionParams
641
+ });
642
+ dest = sanitizeDestination(dest);
643
+ return dest;
644
+ }
645
+ }
646
+ return null;
811
647
  }
812
648
  /**
813
- * Substitute all matched route params into a redirect/rewrite destination.
814
- *
815
- * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
816
- * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
817
- */
649
+ * Substitute all matched route params into a redirect/rewrite destination.
650
+ *
651
+ * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
652
+ * (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
653
+ */
818
654
  function substituteDestinationParams(destination, params) {
819
- const keys = Object.keys(params);
820
- if (keys.length === 0)
821
- return destination;
822
- // Match only the concrete param keys captured from the source pattern.
823
- // Sorting longest-first ensures hyphenated names like `auth-method`
824
- // win over shorter prefixes like `auth`. The negative lookahead keeps
825
- // alphanumeric/underscore suffixes attached, while allowing `-` to act
826
- // as a literal delimiter in destinations like `:year-:month`.
827
- const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
828
- const cacheKey = sortedKeys.join("\0");
829
- let paramRe = _compiledDestinationParamCache.get(cacheKey);
830
- if (!paramRe) {
831
- const paramAlternation = sortedKeys
832
- .map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
833
- .join("|");
834
- paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
835
- _compiledDestinationParamCache.set(cacheKey, paramRe);
836
- }
837
- return destination.replace(paramRe, (_token, key) => params[key]);
655
+ const keys = Object.keys(params);
656
+ if (keys.length === 0) return destination;
657
+ const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
658
+ const cacheKey = sortedKeys.join("\0");
659
+ let paramRe = _compiledDestinationParamCache.get(cacheKey);
660
+ if (!paramRe) {
661
+ const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
662
+ paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
663
+ _compiledDestinationParamCache.set(cacheKey, paramRe);
664
+ }
665
+ return destination.replace(paramRe, (_token, key) => params[key]);
838
666
  }
839
667
  /**
840
- * Sanitize a redirect/rewrite destination to collapse protocol-relative URLs.
841
- *
842
- * After parameter substitution, a destination like `/:path*` can become
843
- * `//evil.com` if the catch-all captured a decoded `%2F` (`/evil.com`).
844
- * Browsers interpret `//evil.com` as a protocol-relative URL, redirecting
845
- * users off-site.
846
- *
847
- * This function collapses any leading double (or more) slashes to a single
848
- * slash for non-external (relative) destinations.
849
- */
850
- export function sanitizeDestination(dest) {
851
- // External URLs (http://, https://) are intentional — don't touch them
852
- if (dest.startsWith("http://") || dest.startsWith("https://")) {
853
- return dest;
854
- }
855
- // Normalize leading backslashes to forward slashes. Browsers interpret
856
- // backslash as forward slash in URL contexts, so "\/evil.com" becomes
857
- // "//evil.com" (protocol-relative redirect). Replace any mix of leading
858
- // slashes and backslashes with a single forward slash.
859
- dest = dest.replace(/^[\\/]+/, "/");
860
- return dest;
668
+ * Sanitize a redirect/rewrite destination to collapse protocol-relative URLs.
669
+ *
670
+ * After parameter substitution, a destination like `/:path*` can become
671
+ * `//evil.com` if the catch-all captured a decoded `%2F` (`/evil.com`).
672
+ * Browsers interpret `//evil.com` as a protocol-relative URL, redirecting
673
+ * users off-site.
674
+ *
675
+ * This function collapses any leading double (or more) slashes to a single
676
+ * slash for non-external (relative) destinations.
677
+ */
678
+ function sanitizeDestination(dest) {
679
+ if (dest.startsWith("http://") || dest.startsWith("https://")) return dest;
680
+ dest = dest.replace(/^[\\/]+/, "/");
681
+ return dest;
861
682
  }
862
683
  /**
863
- * Check if a URL is external (absolute URL or protocol-relative).
864
- * Detects any URL scheme (http:, https:, data:, javascript:, blob:, etc.)
865
- * per RFC 3986, plus protocol-relative URLs (//).
866
- */
867
- export function isExternalUrl(url) {
868
- return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
684
+ * Check if a URL is external (absolute URL or protocol-relative).
685
+ * Detects any URL scheme (http:, https:, data:, javascript:, blob:, etc.)
686
+ * per RFC 3986, plus protocol-relative URLs (//).
687
+ */
688
+ function isExternalUrl(url) {
689
+ return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
869
690
  }
870
691
  /**
871
- * Proxy an incoming request to an external URL and return the upstream response.
872
- *
873
- * Used for external rewrites (e.g. `/ph/:path*` → `https://us.i.posthog.com/:path*`).
874
- * Next.js handles these as server-side reverse proxies, forwarding the request
875
- * method, headers, and body to the external destination.
876
- *
877
- * Works in all runtimes (Node.js, Cloudflare Workers) via the standard fetch() API.
878
- */
879
- export async function proxyExternalRequest(request, externalUrl) {
880
- // Build the full external URL, preserving query parameters from the original request
881
- const originalUrl = new URL(request.url);
882
- const targetUrl = new URL(externalUrl);
883
- const destinationKeys = new Set(targetUrl.searchParams.keys());
884
- // If the rewrite destination already has query params, merge them.
885
- // Destination params take precedence — original request params are only added
886
- // when the destination doesn't already specify that key.
887
- for (const [key, value] of originalUrl.searchParams) {
888
- if (!destinationKeys.has(key)) {
889
- targetUrl.searchParams.append(key, value);
890
- }
891
- }
892
- // Forward the request with appropriate headers
893
- const headers = new Headers(request.headers);
894
- // Set Host to the external target (required for correct routing)
895
- headers.set("host", targetUrl.host);
896
- // Remove headers that should not be forwarded to external services
897
- headers.delete("connection");
898
- const keysToDelete = [];
899
- for (const key of headers.keys()) {
900
- if (key.startsWith("x-middleware-")) {
901
- keysToDelete.push(key);
902
- }
903
- }
904
- for (const key of keysToDelete) {
905
- headers.delete(key);
906
- }
907
- const method = request.method;
908
- const hasBody = method !== "GET" && method !== "HEAD";
909
- const init = {
910
- method,
911
- headers,
912
- redirect: "manual", // Don't follow redirects — pass them through to the client
913
- };
914
- if (hasBody && request.body) {
915
- init.body = request.body;
916
- init.duplex = "half";
917
- }
918
- // Enforce a timeout so slow/unresponsive upstreams don't hold connections
919
- // open indefinitely (DoS amplification risk on Node.js dev/prod servers).
920
- const controller = new AbortController();
921
- const timeout = setTimeout(() => controller.abort(), 30_000);
922
- let upstreamResponse;
923
- try {
924
- upstreamResponse = await fetch(targetUrl.href, { ...init, signal: controller.signal });
925
- }
926
- catch (e) {
927
- if (e?.name === "AbortError") {
928
- console.error("[vinext] External rewrite proxy timeout:", targetUrl.href);
929
- return new Response("Gateway Timeout", { status: 504 });
930
- }
931
- console.error("[vinext] External rewrite proxy error:", e);
932
- return new Response("Bad Gateway", { status: 502 });
933
- }
934
- finally {
935
- clearTimeout(timeout);
936
- }
937
- // Build the response to return to the client.
938
- // Copy all upstream headers except hop-by-hop headers.
939
- // Node.js fetch() auto-decompresses responses (gzip, br, etc.), so the body
940
- // we receive is already plain text. Forwarding the original content-encoding
941
- // and content-length headers causes the browser to attempt a second
942
- // decompression on the already-decoded body, resulting in
943
- // ERR_CONTENT_DECODING_FAILED. Strip both headers on Node.js only.
944
- // On Workers, fetch() preserves wire encoding, so the headers stay accurate.
945
- const isNodeRuntime = typeof process !== "undefined" && !!process.versions?.node;
946
- const responseHeaders = new Headers();
947
- upstreamResponse.headers.forEach((value, key) => {
948
- const lower = key.toLowerCase();
949
- if (HOP_BY_HOP_HEADERS.has(lower))
950
- return;
951
- if (isNodeRuntime && (lower === "content-encoding" || lower === "content-length"))
952
- return;
953
- responseHeaders.append(key, value);
954
- });
955
- return new Response(upstreamResponse.body, {
956
- status: upstreamResponse.status,
957
- statusText: upstreamResponse.statusText,
958
- headers: responseHeaders,
959
- });
692
+ * Proxy an incoming request to an external URL and return the upstream response.
693
+ *
694
+ * Used for external rewrites (e.g. `/ph/:path*` → `https://us.i.posthog.com/:path*`).
695
+ * Next.js handles these as server-side reverse proxies, forwarding the request
696
+ * method, headers, and body to the external destination.
697
+ *
698
+ * Works in all runtimes (Node.js, Cloudflare Workers) via the standard fetch() API.
699
+ */
700
+ async function proxyExternalRequest(request, externalUrl) {
701
+ const originalUrl = new URL(request.url);
702
+ const targetUrl = new URL(externalUrl);
703
+ const destinationKeys = new Set(targetUrl.searchParams.keys());
704
+ for (const [key, value] of originalUrl.searchParams) if (!destinationKeys.has(key)) targetUrl.searchParams.append(key, value);
705
+ const headers = new Headers(request.headers);
706
+ headers.set("host", targetUrl.host);
707
+ stripHopByHopRequestHeaders(headers);
708
+ const keysToDelete = [];
709
+ for (const key of headers.keys()) if (key.startsWith("x-middleware-")) keysToDelete.push(key);
710
+ for (const key of keysToDelete) headers.delete(key);
711
+ const method = request.method;
712
+ const hasBody = method !== "GET" && method !== "HEAD";
713
+ const init = {
714
+ method,
715
+ headers,
716
+ redirect: "manual"
717
+ };
718
+ if (hasBody && request.body) {
719
+ init.body = request.body;
720
+ init.duplex = "half";
721
+ }
722
+ const controller = new AbortController();
723
+ const timeout = setTimeout(() => controller.abort(), 3e4);
724
+ let upstreamResponse;
725
+ try {
726
+ upstreamResponse = await fetch(targetUrl.href, {
727
+ ...init,
728
+ signal: controller.signal
729
+ });
730
+ } catch (e) {
731
+ if (e?.name === "AbortError") {
732
+ console.error("[vinext] External rewrite proxy timeout:", targetUrl.href);
733
+ return new Response("Gateway Timeout", { status: 504 });
734
+ }
735
+ console.error("[vinext] External rewrite proxy error:", e);
736
+ return new Response("Bad Gateway", { status: 502 });
737
+ } finally {
738
+ clearTimeout(timeout);
739
+ }
740
+ const isNodeRuntime = typeof process !== "undefined" && !!process.versions?.node;
741
+ const responseHeaders = new Headers();
742
+ upstreamResponse.headers.forEach((value, key) => {
743
+ const lower = key.toLowerCase();
744
+ if (HOP_BY_HOP_HEADERS.has(lower)) return;
745
+ if (isNodeRuntime && (lower === "content-encoding" || lower === "content-length")) return;
746
+ responseHeaders.append(key, value);
747
+ });
748
+ return new Response(upstreamResponse.body, {
749
+ status: upstreamResponse.status,
750
+ statusText: upstreamResponse.statusText,
751
+ headers: responseHeaders
752
+ });
960
753
  }
961
754
  /**
962
- * Apply custom header rules from next.config.js.
963
- * Returns an array of { key, value } pairs to set on the response.
964
- *
965
- * `ctx` provides the request context (cookies, headers, query, host) used
966
- * to evaluate has/missing conditions. Next.js always has request context
967
- * when evaluating headers, so this parameter is required.
968
- */
969
- export function matchHeaders(pathname, headers, ctx) {
970
- const result = [];
971
- for (const rule of headers) {
972
- // Cache the compiled source regex — escapeHeaderSource() + safeRegExp() are
973
- // pure functions of rule.source and the result never changes between requests.
974
- let sourceRegex = _compiledHeaderSourceCache.get(rule.source);
975
- if (sourceRegex === undefined) {
976
- const escaped = escapeHeaderSource(rule.source);
977
- sourceRegex = safeRegExp("^" + escaped + "$");
978
- _compiledHeaderSourceCache.set(rule.source, sourceRegex);
979
- }
980
- if (sourceRegex && sourceRegex.test(pathname)) {
981
- if (rule.has || rule.missing) {
982
- if (!checkHasConditions(rule.has, rule.missing, ctx)) {
983
- continue;
984
- }
985
- }
986
- result.push(...rule.headers);
987
- }
988
- }
989
- return result;
755
+ * Apply custom header rules from next.config.js.
756
+ * Returns an array of { key, value } pairs to set on the response.
757
+ *
758
+ * `ctx` provides the request context (cookies, headers, query, host) used
759
+ * to evaluate has/missing conditions. Next.js always has request context
760
+ * when evaluating headers, so this parameter is required.
761
+ */
762
+ function matchHeaders(pathname, headers, ctx) {
763
+ const result = [];
764
+ for (const rule of headers) {
765
+ let sourceRegex = _compiledHeaderSourceCache.get(rule.source);
766
+ if (sourceRegex === void 0) {
767
+ sourceRegex = safeRegExp("^" + escapeHeaderSource(rule.source) + "$");
768
+ _compiledHeaderSourceCache.set(rule.source, sourceRegex);
769
+ }
770
+ if (sourceRegex && sourceRegex.test(pathname)) {
771
+ if (rule.has || rule.missing) {
772
+ if (!checkHasConditions(rule.has, rule.missing, ctx)) continue;
773
+ }
774
+ result.push(...rule.headers);
775
+ }
776
+ }
777
+ return result;
990
778
  }
779
+ //#endregion
780
+ export { applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
781
+
991
782
  //# sourceMappingURL=config-matchers.js.map