diff --git a/runtime_tests/bun/index.test.tsx b/runtime_tests/bun/index.test.tsx index 9aaaf26d0..db2f7edd1 100644 --- a/runtime_tests/bun/index.test.tsx +++ b/runtime_tests/bun/index.test.tsx @@ -145,6 +145,18 @@ describe('Serve Static Middleware', () => { expect(res.status).toBe(200) expect(await res.text()).toBe('Bun!!') }) + + it('Should return 200 response - /static/helloworld', async () => { + const res = await app.request('http://localhost/static/helloworld') + expect(res.status).toBe(200) + expect(await res.text()).toBe('Hi\n') + }) + + it('Should return 200 response - /static/hello.world', async () => { + const res = await app.request('http://localhost/static/hello.world') + expect(res.status).toBe(200) + expect(await res.text()).toBe('Hi\n') + }) }) // Bun support WebCrypto since v0.2.2 diff --git a/runtime_tests/bun/static/hello.world/index.html b/runtime_tests/bun/static/hello.world/index.html new file mode 100644 index 000000000..b14df6442 --- /dev/null +++ b/runtime_tests/bun/static/hello.world/index.html @@ -0,0 +1 @@ +Hi diff --git a/runtime_tests/bun/static/helloworld/index.html b/runtime_tests/bun/static/helloworld/index.html new file mode 100644 index 000000000..b14df6442 --- /dev/null +++ b/runtime_tests/bun/static/helloworld/index.html @@ -0,0 +1 @@ +Hi diff --git a/runtime_tests/deno/middleware.test.tsx b/runtime_tests/deno/middleware.test.tsx index 534160c54..aed22e28a 100644 --- a/runtime_tests/deno/middleware.test.tsx +++ b/runtime_tests/deno/middleware.test.tsx @@ -123,6 +123,14 @@ Deno.test('Serve Static middleware', async () => { } as Request) assertEquals(res.status, 404) assertEquals(await res.text(), '404 Not Found') + + res = await app.request('http://localhost/static/helloworld') + assertEquals(res.status, 200) + assertEquals(await res.text(), 'Hi\n') + + res = await app.request('http://localhost/static/hello.world') + assertEquals(res.status, 200) + assertEquals(await res.text(), 'Hi\n') }) Deno.test('JWT Authentication middleware', async () => { diff --git a/runtime_tests/deno/static/hello.world/index.html b/runtime_tests/deno/static/hello.world/index.html new file mode 100644 index 000000000..b14df6442 --- /dev/null +++ b/runtime_tests/deno/static/hello.world/index.html @@ -0,0 +1 @@ +Hi diff --git a/runtime_tests/deno/static/helloworld/index.html b/runtime_tests/deno/static/helloworld/index.html new file mode 100644 index 000000000..b14df6442 --- /dev/null +++ b/runtime_tests/deno/static/helloworld/index.html @@ -0,0 +1 @@ +Hi diff --git a/src/adapter/bun/serve-static.ts b/src/adapter/bun/serve-static.ts index 768dffef8..be9fa2cca 100644 --- a/src/adapter/bun/serve-static.ts +++ b/src/adapter/bun/serve-static.ts @@ -2,6 +2,7 @@ import { serveStatic as baseServeStatic } from '../../middleware/serve-static' import type { ServeStaticOptions } from '../../middleware/serve-static' import type { Env, MiddlewareHandler } from '../../types' +import { stat } from 'node:fs/promises' export const serveStatic = ( options: ServeStaticOptions @@ -16,10 +17,19 @@ export const serveStatic = ( const pathResolve = (path: string) => { return `./${path}` } + const isDir = async (path: string) => { + let isDir + try { + const stats = await stat(path) + isDir = stats.isDirectory() + } catch {} + return isDir + } return baseServeStatic({ ...options, getContent, pathResolve, + isDir, })(c, next) } } diff --git a/src/adapter/deno/serve-static.ts b/src/adapter/deno/serve-static.ts index b037466b6..056fae025 100644 --- a/src/adapter/deno/serve-static.ts +++ b/src/adapter/deno/serve-static.ts @@ -4,7 +4,7 @@ import type { Env, MiddlewareHandler } from '../../types' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -const { open } = Deno +const { open, lstatSync } = Deno export const serveStatic = ( options: ServeStaticOptions @@ -22,10 +22,19 @@ export const serveStatic = ( const pathResolve = (path: string) => { return `./${path}` } + const isDir = (path: string) => { + let isDir + try { + const stat = lstatSync(path) + isDir = stat.isDirectory + } catch {} + return isDir + } return baseServeStatic({ ...options, getContent, pathResolve, + isDir, })(c, next) } } diff --git a/src/middleware/serve-static/index.test.ts b/src/middleware/serve-static/index.test.ts index ca611b67b..efae1aeb5 100644 --- a/src/middleware/serve-static/index.test.ts +++ b/src/middleware/serve-static/index.test.ts @@ -15,6 +15,9 @@ describe('Serve Static Middleware', () => { pathResolve: (path) => { return `./${path}` }, + isDir: (path) => { + return path === 'static/hello.world' + }, }) app.get('/static/*', serveStatic) @@ -37,6 +40,20 @@ describe('Serve Static Middleware', () => { expect(await res.text()).toBe('Hello in ./static/sub/index.html') }) + it('Should return 200 response - /static/helloworld', async () => { + const res = await app.request('/static/helloworld') + expect(res.status).toBe(200) + expect(res.headers.get('Content-Type')).toMatch(/^text\/html/) + expect(await res.text()).toBe('Hello in ./static/helloworld/index.html') + }) + + it('Should return 200 response - /static/hello.world', async () => { + const res = await app.request('/static/hello.world') + expect(res.status).toBe(200) + expect(res.headers.get('Content-Type')).toMatch(/^text\/html/) + expect(await res.text()).toBe('Hello in ./static/hello.world/index.html') + }) + it('Should decode URI strings - /static/%E7%82%8E.txt', async () => { const res = await app.request('/static/%E7%82%8E.txt') expect(res.status).toBe(200) diff --git a/src/middleware/serve-static/index.ts b/src/middleware/serve-static/index.ts index 134f4c444..1e3e78569 100644 --- a/src/middleware/serve-static/index.ts +++ b/src/middleware/serve-static/index.ts @@ -26,6 +26,7 @@ export const serveStatic = ( options: ServeStaticOptions & { getContent: (path: string, c: Context) => Promise pathResolve?: (path: string) => string + isDir?: (path: string) => boolean | undefined | Promise } ): MiddlewareHandler => { return async (c, next) => { @@ -39,6 +40,17 @@ export const serveStatic = ( filename = options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename const root = options.root + // If it was Directory, force `/` on the end. + if (!filename.endsWith('/') && options.isDir) { + const path = getFilePathWithoutDefaultDocument({ + filename, + root, + }) + if (path && (await options.isDir(path))) { + filename = filename + '/' + } + } + let path = getFilePath({ filename, root,