Skip to content

Commit f73c0c8

Browse files
committed
fix: isolate overlapping static mount paths
1 parent c46f01e commit f73c0c8

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

packages/devframe/src/utils/serve-static.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { tmpdir } from 'node:os'
66
import { join } from 'node:path'
77
import { H3, toNodeHandler } from 'h3'
88
import { afterEach, describe, expect, it } from 'vitest'
9-
import { serveStaticHandler, serveStaticNodeMiddleware } from './serve-static'
9+
import { mountStaticHandler, serveStaticHandler, serveStaticNodeMiddleware } from './serve-static'
1010

1111
interface Fixture {
1212
dir: string
@@ -167,6 +167,36 @@ describe('serveStaticHandler', () => {
167167
})
168168
})
169169

170+
describe('mountStaticHandler', () => {
171+
it('keeps static bases with overlapping prefixes isolated', async () => {
172+
const viteDir = makeTmp('devframe-serve-vite-')
173+
const vitestDir = makeTmp('devframe-serve-vitest-')
174+
writeFileSync(join(viteDir, 'index.html'), 'vite-index', 'utf-8')
175+
writeFileSync(join(viteDir, 'favicon.svg'), 'vite', 'utf-8')
176+
writeFileSync(join(vitestDir, 'favicon.svg'), 'vitest', 'utf-8')
177+
178+
const app = new H3()
179+
mountStaticHandler(app, '/__devtools-vite/', viteDir)
180+
mountStaticHandler(app, '/__devtools-vitest/', vitestDir)
181+
182+
const viteBaseResponse = await app.request('/__devtools-vite')
183+
const viteBaseSlashResponse = await app.request('/__devtools-vite/')
184+
const viteResponse = await app.request('/__devtools-vite/favicon.svg')
185+
const vitestResponse = await app.request('/__devtools-vitest/favicon.svg')
186+
const adjacentResponse = await app.request('/__devtools-vite-extra/favicon.svg')
187+
188+
expect(viteBaseResponse.status).toBe(200)
189+
expect(await viteBaseResponse.text()).toBe('vite-index')
190+
expect(viteBaseSlashResponse.status).toBe(200)
191+
expect(await viteBaseSlashResponse.text()).toBe('vite-index')
192+
expect(viteResponse.status).toBe(200)
193+
expect(await viteResponse.text()).toBe('vite')
194+
expect(vitestResponse.status).toBe(200)
195+
expect(await vitestResponse.text()).toBe('vitest')
196+
expect(adjacentResponse.status).toBe(404)
197+
})
198+
})
199+
170200
describe('serveStaticNodeMiddleware', () => {
171201
let fx: Fixture | undefined
172202

packages/devframe/src/utils/serve-static.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ export function serveStaticHandler(
173173
*
174174
* h3 v2's `app.use(base, handler)` only matches the exact `base` path and
175175
* does not strip the prefix from `event.url.pathname`. Static serving
176-
* needs both subpath matching (`/base/**`) and the URL stripped so the
177-
* file resolver sees paths relative to `dir` — this helper bundles both.
176+
* needs an explicit segment-boundary match plus a stripped URL so the file
177+
* resolver sees paths relative to `dir` — this helper bundles both.
178178
*/
179179
export function mountStaticHandler(
180180
app: H3,
@@ -188,7 +188,9 @@ export function mountStaticHandler(
188188
app.use('/**', handler)
189189
return
190190
}
191-
app.use(`${trimmed}/**`, withBase(trimmed, handler))
191+
app.use(withBase(trimmed, handler), {
192+
match: event => event.url.pathname === trimmed || event.url.pathname.startsWith(`${trimmed}/`),
193+
})
192194
}
193195

194196
/**

0 commit comments

Comments
 (0)