diff --git a/edge-runtime/lib/routing.ts b/edge-runtime/lib/routing.ts index 4619fda369..e9fbaf137c 100644 --- a/edge-runtime/lib/routing.ts +++ b/edge-runtime/lib/routing.ts @@ -424,14 +424,24 @@ export interface MiddlewareMatcher { missing?: RouteHas[] } +const decodeMaybeEncodedPath = (path: string): string => { + try { + return decodeURIComponent(path) + } catch { + return path + } +} + export function getMiddlewareRouteMatcher(matchers: MiddlewareMatcher[]): MiddlewareRouteMatch { return ( - pathname: string | null | undefined, + unsafePathname: string | null | undefined, req: Pick, query: Params, ) => { + const pathname = decodeMaybeEncodedPath(unsafePathname ?? '') + for (const matcher of matchers) { - const routeMatch = new RegExp(matcher.regexp).exec(pathname!) + const routeMatch = new RegExp(matcher.regexp).exec(pathname) if (!routeMatch) { continue } diff --git a/tests/e2e/edge-middleware.test.ts b/tests/e2e/edge-middleware.test.ts index b1b9c4e5c8..c40eabc931 100644 --- a/tests/e2e/edge-middleware.test.ts +++ b/tests/e2e/edge-middleware.test.ts @@ -233,6 +233,15 @@ test("requests with x-middleware-subrequest don't skip middleware (GHSA-f82v-jwr expect(response.headers.get('x-test-used-next-version')).toBe('15.2.2') }) +test('requests with different encoding than matcher match anyway', async ({ + middlewareStaticAssetMatcher, +}) => { + const response = await fetch(`${middlewareStaticAssetMatcher.url}/hello%2Fworld.txt`) + + // middleware was not skipped + expect(await response.text()).toBe('hello from middleware') +}) + test.describe('RSC cache poisoning', () => { test('Middleware rewrite', async ({ page, middleware }) => { const prefetchResponsePromise = new Promise((resolve) => { diff --git a/tests/fixtures/middleware-og/package.json b/tests/fixtures/middleware-og/package.json index d93774b745..6ab7279134 100644 --- a/tests/fixtures/middleware-og/package.json +++ b/tests/fixtures/middleware-og/package.json @@ -8,8 +8,6 @@ "build": "next build" }, "dependencies": { - "@aws-amplify/adapter-nextjs": "^1.0.18", - "aws-amplify": "^6.0.18", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/tests/fixtures/middleware-static-asset-matcher/app/layout.js b/tests/fixtures/middleware-static-asset-matcher/app/layout.js new file mode 100644 index 0000000000..6565e7bafd --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Simple Next App', + description: 'Description for Simple Next App', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/tests/fixtures/middleware-static-asset-matcher/app/page.js b/tests/fixtures/middleware-static-asset-matcher/app/page.js new file mode 100644 index 0000000000..1a9fe06903 --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/app/page.js @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

Home

+
+ ) +} diff --git a/tests/fixtures/middleware-static-asset-matcher/middleware.ts b/tests/fixtures/middleware-static-asset-matcher/middleware.ts new file mode 100644 index 0000000000..26924f826d --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/middleware.ts @@ -0,0 +1,7 @@ +export default function middleware() { + return new Response('hello from middleware') +} + +export const config = { + matcher: '/hello/world.txt', +} diff --git a/tests/fixtures/middleware-static-asset-matcher/next.config.js b/tests/fixtures/middleware-static-asset-matcher/next.config.js new file mode 100644 index 0000000000..9d94510be1 --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/next.config.js @@ -0,0 +1,9 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', + eslint: { + ignoreDuringBuilds: true, + }, +} + +module.exports = nextConfig diff --git a/tests/fixtures/middleware-static-asset-matcher/package.json b/tests/fixtures/middleware-static-asset-matcher/package.json new file mode 100644 index 0000000000..b3f8a8dec2 --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/package.json @@ -0,0 +1,15 @@ +{ + "name": "middleware", + "version": "0.1.0", + "private": true, + "scripts": { + "postinstall": "next build", + "dev": "next dev", + "build": "next build" + }, + "dependencies": { + "next": "latest", + "react": "18.2.0", + "react-dom": "18.2.0" + } +} diff --git a/tests/fixtures/middleware-static-asset-matcher/public/hello/world.txt b/tests/fixtures/middleware-static-asset-matcher/public/hello/world.txt new file mode 100644 index 0000000000..00c7fa3e9d --- /dev/null +++ b/tests/fixtures/middleware-static-asset-matcher/public/hello/world.txt @@ -0,0 +1 @@ +hello from a static asset diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index a2b5c48f00..d9dd509708 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -333,10 +333,11 @@ export const fixtureFactories = { pnpm: () => createE2EFixture('pnpm', { packageManger: 'pnpm' }), bun: () => createE2EFixture('simple', { packageManger: 'bun' }), middleware: () => createE2EFixture('middleware'), - middlewareSubrequestVuln: () => createE2EFixture('middleware-subrequest-vuln'), middlewareI18nExcludedPaths: () => createE2EFixture('middleware-i18n-excluded-paths'), middlewareOg: () => createE2EFixture('middleware-og'), middlewarePages: () => createE2EFixture('middleware-pages'), + middlewareStaticAssetMatcher: () => createE2EFixture('middleware-static-asset-matcher'), + middlewareSubrequestVuln: () => createE2EFixture('middleware-subrequest-vuln'), pageRouter: () => createE2EFixture('page-router'), pageRouterBasePathI18n: () => createE2EFixture('page-router-base-path-i18n'), turborepo: () =>