Skip to content

Commit

Permalink
fix(serve-static): supports extension less files
Browse files Browse the repository at this point in the history
  • Loading branch information
yusukebe committed Jul 3, 2024
1 parent 87ecf60 commit d5c5291
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 13 deletions.
55 changes: 42 additions & 13 deletions src/serve-static.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReadStream } from 'fs'
import { createReadStream, existsSync, lstatSync } from 'fs'
import type { ReadStream, Stats } from 'fs'
import { createReadStream, lstatSync } from 'fs'
import type { Context, MiddlewareHandler } from 'hono'
import { getFilePath } from 'hono/utils/filepath'
import { getFilePath, getFilePathWithoutDefaultDocument } from 'hono/utils/filepath'
import { getMimeType } from 'hono/utils/mime'

export type ServeStaticOptions = {
Expand Down Expand Up @@ -33,6 +33,18 @@ const createStreamBody = (stream: ReadStream) => {
return body
}

const addCurrentDirPrefix = (path: string) => {
return `./${path}`
}

const getStats = (path: string) => {
let stats: Stats | undefined
try {
stats = lstatSync(path)
} catch {}
return stats
}

export const serveStatic = (options: ServeStaticOptions = { root: '' }): MiddlewareHandler => {
return async (c, next) => {
// Do nothing if Response is already set
Expand All @@ -41,19 +53,37 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
}

const filename = options.path ?? decodeURIComponent(c.req.path)
let path = getFilePath({

let path = getFilePathWithoutDefaultDocument({
filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
root: options.root,
defaultDocument: options.index ?? 'index.html',
})

if (!path) {
if (path) {
path = addCurrentDirPrefix(path)
} else {
return next()
}

path = `./${path}`
let stats = getStats(path)

if (stats && stats.isDirectory()) {
path = getFilePath({
filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
root: options.root,
defaultDocument: options.index ?? 'index.html',
})

if (path) {
path = addCurrentDirPrefix(path)
} else {
return next()
}

stats = getStats(path)
}

if (!existsSync(path)) {
if (!stats) {
await options.onNotFound?.(path, c)
return next()
}
Expand All @@ -63,8 +93,7 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
c.header('Content-Type', mimeType)
}

const stat = lstatSync(path)
const size = stat.size
const size = stats.size

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

c.header('Accept-Ranges', 'bytes')
c.header('Date', stat.birthtime.toUTCString())
c.header('Date', stats.birthtime.toUTCString())

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

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

return c.body(createStreamBody(stream), 206)
}
Expand Down
1 change: 1 addition & 0 deletions test/assets/static/extensionless
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extensionless
6 changes: 6 additions & 0 deletions test/serve-static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,10 @@ describe('Serve Static Middleware', () => {
const res = await request(server).get('/static/../secret.txt')
expect(res.status).toBe(404)
})

it('Should handle an extension less files', async () => {
const res = await request(server).get('/static/extensionless')
expect(res.status).toBe(200)
expect(res.text).toBe('Extensionless')
})
})

0 comments on commit d5c5291

Please sign in to comment.