Skip to content

Commit fa8cb6b

Browse files
authored
fix(serve-static): supports extension less files (#183)
1 parent 87ecf60 commit fa8cb6b

File tree

3 files changed

+49
-13
lines changed

3 files changed

+49
-13
lines changed

src/serve-static.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { ReadStream } from 'fs'
2-
import { createReadStream, existsSync, lstatSync } from 'fs'
1+
import type { ReadStream, Stats } from 'fs'
2+
import { createReadStream, lstatSync } from 'fs'
33
import type { Context, MiddlewareHandler } from 'hono'
4-
import { getFilePath } from 'hono/utils/filepath'
4+
import { getFilePath, getFilePathWithoutDefaultDocument } from 'hono/utils/filepath'
55
import { getMimeType } from 'hono/utils/mime'
66

77
export type ServeStaticOptions = {
@@ -33,6 +33,18 @@ const createStreamBody = (stream: ReadStream) => {
3333
return body
3434
}
3535

36+
const addCurrentDirPrefix = (path: string) => {
37+
return `./${path}`
38+
}
39+
40+
const getStats = (path: string) => {
41+
let stats: Stats | undefined
42+
try {
43+
stats = lstatSync(path)
44+
} catch {}
45+
return stats
46+
}
47+
3648
export const serveStatic = (options: ServeStaticOptions = { root: '' }): MiddlewareHandler => {
3749
return async (c, next) => {
3850
// Do nothing if Response is already set
@@ -41,19 +53,37 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
4153
}
4254

4355
const filename = options.path ?? decodeURIComponent(c.req.path)
44-
let path = getFilePath({
56+
57+
let path = getFilePathWithoutDefaultDocument({
4558
filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
4659
root: options.root,
47-
defaultDocument: options.index ?? 'index.html',
4860
})
4961

50-
if (!path) {
62+
if (path) {
63+
path = addCurrentDirPrefix(path)
64+
} else {
5165
return next()
5266
}
5367

54-
path = `./${path}`
68+
let stats = getStats(path)
69+
70+
if (stats && stats.isDirectory()) {
71+
path = getFilePath({
72+
filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
73+
root: options.root,
74+
defaultDocument: options.index ?? 'index.html',
75+
})
76+
77+
if (path) {
78+
path = addCurrentDirPrefix(path)
79+
} else {
80+
return next()
81+
}
82+
83+
stats = getStats(path)
84+
}
5585

56-
if (!existsSync(path)) {
86+
if (!stats) {
5787
await options.onNotFound?.(path, c)
5888
return next()
5989
}
@@ -63,8 +93,7 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
6393
c.header('Content-Type', mimeType)
6494
}
6595

66-
const stat = lstatSync(path)
67-
const size = stat.size
96+
const size = stats.size
6897

6998
if (c.req.method == 'HEAD' || c.req.method == 'OPTIONS') {
7099
c.header('Content-Length', size.toString())
@@ -80,11 +109,11 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
80109
}
81110

82111
c.header('Accept-Ranges', 'bytes')
83-
c.header('Date', stat.birthtime.toUTCString())
112+
c.header('Date', stats.birthtime.toUTCString())
84113

85114
const parts = range.replace(/bytes=/, '').split('-', 2)
86115
const start = parts[0] ? parseInt(parts[0], 10) : 0
87-
let end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1
116+
let end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1
88117
if (size < end - start + 1) {
89118
end = size - 1
90119
}
@@ -93,7 +122,7 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
93122
const stream = createReadStream(path, { start, end })
94123

95124
c.header('Content-Length', chunksize.toString())
96-
c.header('Content-Range', `bytes ${start}-${end}/${stat.size}`)
125+
c.header('Content-Range', `bytes ${start}-${end}/${stats.size}`)
97126

98127
return c.body(createStreamBody(stream), 206)
99128
}

test/assets/static/extensionless

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Extensionless

test/serve-static.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,10 @@ describe('Serve Static Middleware', () => {
134134
const res = await request(server).get('/static/../secret.txt')
135135
expect(res.status).toBe(404)
136136
})
137+
138+
it('Should handle an extension less files', async () => {
139+
const res = await request(server).get('/static/extensionless')
140+
expect(res.status).toBe(200)
141+
expect(res.text).toBe('Extensionless')
142+
})
137143
})

0 commit comments

Comments
 (0)