diff --git a/src/middleware/serve-static/index.test.ts b/src/middleware/serve-static/index.test.ts index f8ba7416f..968140467 100644 --- a/src/middleware/serve-static/index.test.ts +++ b/src/middleware/serve-static/index.test.ts @@ -27,16 +27,6 @@ describe('Serve Static Middleware', () => { app.get('/static/*', serveStatic) - const serveStaticAbsoluteRoot = baseServeStatic({ - getContent, - pathResolve: (path) => { - return path - }, - root: '/home/hono/../foo', - }) - - app.get('/static-absolute/*', serveStaticAbsoluteRoot) - beforeEach(() => { getContent.mockClear() }) @@ -237,9 +227,61 @@ describe('Serve Static Middleware', () => { expect(res.body).toBe(body) }) - it('Should traverse directories with absolute root path', async () => { - const res = await app.request('/static-absolute/bar/hello.html') - expect(res.status).toBe(200) - expect(await res.text()).toBe('Hello in /home/foo/static-absolute/bar/hello.html') + describe('Changing root path', () => { + const pathResolve = (path: string) => { + return path.startsWith('/') ? path : `./${path}` + } + + it('Should return the content with absolute root path', async () => { + const app = new Hono() + const serveStatic = baseServeStatic({ + getContent, + pathResolve, + root: '/home/hono/child', + }) + app.get('/static/*', serveStatic) + + const res = await app.request('/static/html/hello.html') + expect(await res.text()).toBe('Hello in /home/hono/child/static/html/hello.html') + }) + + it('Should traverse the directories with absolute root path', async () => { + const app = new Hono() + const serveStatic = baseServeStatic({ + getContent, + pathResolve, + root: '/home/hono/../parent', + }) + app.get('/static/*', serveStatic) + + const res = await app.request('/static/html/hello.html') + expect(await res.text()).toBe('Hello in /home/parent/static/html/hello.html') + }) + + it('Should treat the root path includes .. as relative path', async () => { + const app = new Hono() + const serveStatic = baseServeStatic({ + getContent, + pathResolve, + root: '../home/hono', + }) + app.get('/static/*', serveStatic) + + const res = await app.request('/static/html/hello.html') + expect(await res.text()).toBe('Hello in ./../home/hono/static/html/hello.html') + }) + + it('Should not allow directory traversal with . as relative path', async () => { + const app = new Hono() + const serveStatic = baseServeStatic({ + getContent, + pathResolve, + root: '.', + }) + app.get('*', serveStatic) + + const res = await app.request('///etc/passwd') + expect(res.status).toBe(404) + }) }) }) diff --git a/src/middleware/serve-static/index.ts b/src/middleware/serve-static/index.ts index 8bcede7c9..0f1837f22 100644 --- a/src/middleware/serve-static/index.ts +++ b/src/middleware/serve-static/index.ts @@ -45,8 +45,10 @@ export const serveStatic = ( if (options.root) { if (options.root.startsWith('/')) { isAbsoluteRoot = true + root = new URL(`file://${options.root}`).pathname + } else { + root = options.root } - root = new URL(`file://${options.root}`).pathname } return async (c, next) => { diff --git a/src/utils/filepath.ts b/src/utils/filepath.ts index f3f2ea82e..614714831 100644 --- a/src/utils/filepath.ts +++ b/src/utils/filepath.ts @@ -52,5 +52,9 @@ export const getFilePathWithoutDefaultDocument = ( let path = root ? root + '/' + filename : filename path = path.replace(/^\.?\//, '') + if (root[0] !== '/' && path[0] === '/') { + return + } + return path }