Skip to content

Commit ce2c70a

Browse files
committed
fix(edge-runtime): match path with URI-encoded chars
Following the behaviour in vercel/next.js#78325, if a path matches a configured middleware matcher when URI-decoded, consider it a match. This doesn't seem ideal, but it aligns our behaviour with that of Next.js, and ensures that static asset matching behaviour aligns with edge middleware matching behaviour.
1 parent e96cd18 commit ce2c70a

File tree

10 files changed

+74
-5
lines changed

10 files changed

+74
-5
lines changed

edge-runtime/lib/routing.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -424,14 +424,24 @@ export interface MiddlewareMatcher {
424424
missing?: RouteHas[]
425425
}
426426

427+
const decodeMaybeEncodedPath = (path: string): string => {
428+
try {
429+
return decodeURIComponent(path)
430+
} catch {
431+
return path
432+
}
433+
}
434+
427435
export function getMiddlewareRouteMatcher(matchers: MiddlewareMatcher[]): MiddlewareRouteMatch {
428436
return (
429-
pathname: string | null | undefined,
437+
unsafePathname: string | null | undefined,
430438
req: Pick<Request, 'headers' | 'url'>,
431439
query: Params,
432440
) => {
441+
const pathname = decodeMaybeEncodedPath(unsafePathname ?? '')
442+
433443
for (const matcher of matchers) {
434-
const routeMatch = new RegExp(matcher.regexp).exec(pathname!)
444+
const routeMatch = new RegExp(matcher.regexp).exec(pathname)
435445
if (!routeMatch) {
436446
continue
437447
}

tests/e2e/edge-middleware.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ test("requests with x-middleware-subrequest don't skip middleware (GHSA-f82v-jwr
233233
expect(response.headers.get('x-test-used-next-version')).toBe('15.2.2')
234234
})
235235

236+
test('requests with different encoding than matcher match anyway', async ({
237+
middlewareStaticAssetMatcher,
238+
}) => {
239+
const response = await fetch(`${middlewareStaticAssetMatcher.url}/hello%2Fworld.txt`)
240+
241+
// middleware was not skipped
242+
expect(await response.text()).toBe('hello from middleware')
243+
})
244+
236245
test.describe('RSC cache poisoning', () => {
237246
test('Middleware rewrite', async ({ page, middleware }) => {
238247
const prefetchResponsePromise = new Promise<Response>((resolve) => {

tests/fixtures/middleware-og/package.json

-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
"build": "next build"
99
},
1010
"dependencies": {
11-
"@aws-amplify/adapter-nextjs": "^1.0.18",
12-
"aws-amplify": "^6.0.18",
1311
"next": "latest",
1412
"react": "18.2.0",
1513
"react-dom": "18.2.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const metadata = {
2+
title: 'Simple Next App',
3+
description: 'Description for Simple Next App',
4+
}
5+
6+
export default function RootLayout({ children }) {
7+
return (
8+
<html lang="en">
9+
<body>{children}</body>
10+
</html>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Home</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function middleware() {
2+
return new Response('hello from middleware')
3+
}
4+
5+
export const config = {
6+
matcher: '/hello/world.txt',
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
output: 'standalone',
4+
eslint: {
5+
ignoreDuringBuilds: true,
6+
},
7+
}
8+
9+
module.exports = nextConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "middleware",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"postinstall": "next build",
7+
"dev": "next dev",
8+
"build": "next build"
9+
},
10+
"dependencies": {
11+
"next": "latest",
12+
"react": "18.2.0",
13+
"react-dom": "18.2.0"
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello from a static asset

tests/utils/create-e2e-fixture.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,11 @@ export const fixtureFactories = {
333333
pnpm: () => createE2EFixture('pnpm', { packageManger: 'pnpm' }),
334334
bun: () => createE2EFixture('simple', { packageManger: 'bun' }),
335335
middleware: () => createE2EFixture('middleware'),
336-
middlewareSubrequestVuln: () => createE2EFixture('middleware-subrequest-vuln'),
337336
middlewareI18nExcludedPaths: () => createE2EFixture('middleware-i18n-excluded-paths'),
338337
middlewareOg: () => createE2EFixture('middleware-og'),
339338
middlewarePages: () => createE2EFixture('middleware-pages'),
339+
middlewareStaticAssetMatcher: () => createE2EFixture('middleware-static-asset-matcher'),
340+
middlewareSubrequestVuln: () => createE2EFixture('middleware-subrequest-vuln'),
340341
pageRouter: () => createE2EFixture('page-router'),
341342
pageRouterBasePathI18n: () => createE2EFixture('page-router-base-path-i18n'),
342343
turborepo: () =>

0 commit comments

Comments
 (0)