Skip to content

Commit 745dd0d

Browse files
authored
fix: improve serve-static function (#261)
* fix: enable to serve filename with double dots * refactor: simplify serve-static() function * test: add file foo..bar.txt
1 parent 35cda9e commit 745dd0d

File tree

3 files changed

+24
-33
lines changed

3 files changed

+24
-33
lines changed

src/serve-static.ts

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Context, Env, MiddlewareHandler } from 'hono'
22
import { getMimeType } from 'hono/utils/mime'
33
import type { ReadStream, Stats } from 'node:fs'
44
import { createReadStream, lstatSync } from 'node:fs'
5-
import { join, resolve } from 'node:path'
5+
import { join } from 'node:path'
66

77
export type ServeStaticOptions<E extends Env = Env> = {
88
/**
@@ -56,7 +56,7 @@ const getStats = (path: string) => {
5656
export const serveStatic = <E extends Env = any>(
5757
options: ServeStaticOptions<E> = { root: '' }
5858
): MiddlewareHandler<E> => {
59-
const root = resolve(options.root || '.')
59+
const root = options.root || ''
6060
const optionPath = options.path
6161

6262
return async (c, next) => {
@@ -67,44 +67,30 @@ export const serveStatic = <E extends Env = any>(
6767

6868
let filename: string
6969

70-
try {
71-
const rawPath = optionPath ?? c.req.path
72-
// Prevent encoded path traversal attacks
73-
if (!optionPath) {
74-
const decodedPath = decodeURIComponent(rawPath)
75-
if (decodedPath.includes('..')) {
76-
await options.onNotFound?.(rawPath, c)
77-
return next()
70+
if (optionPath) {
71+
filename = optionPath
72+
} else {
73+
try {
74+
filename = decodeURIComponent(c.req.path)
75+
if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
76+
throw new Error()
7877
}
78+
} catch {
79+
await options.onNotFound?.(c.req.path, c)
80+
return next()
7981
}
80-
filename = optionPath ?? decodeURIComponent(c.req.path)
81-
} catch {
82-
await options.onNotFound?.(c.req.path, c)
83-
return next()
8482
}
8583

86-
const requestPath = options.rewriteRequestPath
87-
? options.rewriteRequestPath(filename, c)
88-
: filename
89-
90-
let path = optionPath
91-
? options.root
92-
? resolve(join(root, optionPath))
93-
: optionPath
94-
: resolve(join(root, requestPath))
84+
let path = join(
85+
root,
86+
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
87+
)
9588

9689
let stats = getStats(path)
9790

9891
if (stats && stats.isDirectory()) {
9992
const indexFile = options.index ?? 'index.html'
100-
path = resolve(join(path, indexFile))
101-
102-
// Security check: prevent path traversal attacks
103-
if (!optionPath && !path.startsWith(root)) {
104-
await options.onNotFound?.(path, c)
105-
return next()
106-
}
107-
93+
path = join(path, indexFile)
10894
stats = getStats(path)
10995
}
11096

test/assets/static/foo..bar.txt

Whitespace-only changes.

test/serve-static.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('Serve Static Middleware', () => {
6969
expect(res.text).toBe('<h1>Hello Hono</h1>')
7070
expect(res.headers['content-type']).toBe('text/html; charset=utf-8')
7171
expect(res.headers['x-custom']).toMatch(
72-
/Found the file at .*[\/\\]test[\/\\]assets[\/\\]static[\/\\]index\.html$/
72+
/Found the file at test[\/\\]assets[\/\\]static[\/\\]index\.html$/
7373
)
7474
})
7575

@@ -170,7 +170,7 @@ describe('Serve Static Middleware', () => {
170170
const res = await request(server).get('/on-not-found/foo.txt')
171171
expect(res.status).toBe(404)
172172
expect(notFoundMessage).toMatch(
173-
/.*[\/\\]not-found[\/\\]on-not-found[\/\\]foo\.txt is not found, request to \/on-not-found\/foo\.txt$/
173+
/not-found[\/\\]on-not-found[\/\\]foo\.txt is not found, request to \/on-not-found\/foo\.txt$/
174174
)
175175
})
176176

@@ -318,5 +318,10 @@ describe('Serve Static Middleware', () => {
318318
const res = await request(server).get('/static/%2e%2e%2fsecret.txt')
319319
expect(res.status).toBe(404)
320320
})
321+
322+
it('Should accept filename with double dots', async () => {
323+
const res = await request(server).get('/static/foo..bar.txt')
324+
expect(res.status).toBe(200)
325+
})
321326
})
322327
})

0 commit comments

Comments
 (0)