Skip to content

Commit 92a728c

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 92a728c

File tree

18 files changed

+167
-3
lines changed

18 files changed

+167
-3
lines changed

edge-runtime/lib/routing.ts

Lines changed: 12 additions & 2 deletions
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

Lines changed: 9 additions & 0 deletions
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) => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default function CachingRedirect() {
2+
return (
3+
<main>
4+
<h1>Hello redirect target</h1>
5+
</main>
6+
)
7+
}
8+
9+
export const dynamic = 'force-static'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default function CachingRewrite() {
2+
return (
3+
<main>
4+
<h1>Hello rewrite target</h1>
5+
</main>
6+
)
7+
}
8+
9+
export const dynamic = 'force-static'
Lines changed: 12 additions & 0 deletions
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+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
3+
export default function LinksToRedirectedCachedPage() {
4+
return (
5+
<nav>
6+
<ul>
7+
<li>
8+
<Link href="/test/redirect-to-cached-page">NextResponse.redirect</Link>
9+
</li>
10+
</ul>
11+
</nav>
12+
)
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
3+
export default function LinksToRewrittenCachedPage() {
4+
return (
5+
<nav>
6+
<ul>
7+
<li>
8+
<Link href="/test/rewrite-to-cached-page">NextResponse.rewrite</Link>
9+
</li>
10+
</ul>
11+
</nav>
12+
)
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Page() {
2+
return (
3+
<main>
4+
<h1>Other</h1>
5+
</main>
6+
)
7+
}
Lines changed: 7 additions & 0 deletions
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+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { headers } from 'next/headers'
2+
3+
export default function Page() {
4+
const headersList = headers()
5+
const message = headersList.get('x-hello-from-middleware-req')
6+
7+
return (
8+
<main>
9+
<h1>Message from middleware: {message}</h1>
10+
</main>
11+
)
12+
}

0 commit comments

Comments
 (0)