repositories / will
will
owned by admin
src/service/static.ts
Rawimport { createReadStream } from 'node:fs'
import { readFile, stat } from 'node:fs/promises'
import type { ServerResponse } from 'node:http'
import { extname, resolve } from 'node:path'
import { HttpError, sendError } from '../shared/http.ts'
const CONTENT_TYPES: Record<string, string> = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.txt': 'text/plain; charset=utf-8',
'.woff2': 'font/woff2',
}
/** Serve a file under root with path-traversal protection. */
export async function serveFile(
res: ServerResponse,
root: string,
unsafePath: string,
options: { immutable?: boolean } = {},
): Promise<void> {
const rootResolved = resolve(root)
const target = resolve(rootResolved, `./${unsafePath.replace(/^\/+/, '')}`)
if (target !== rootResolved && !target.startsWith(`${rootResolved}/`)) {
throw new HttpError(403, 'forbidden')
}
let info: import('node:fs').Stats
try {
info = await stat(target)
} catch {
throw new HttpError(404, `not found: ${unsafePath}`)
}
if (!info.isFile()) throw new HttpError(404, `not a file: ${unsafePath}`)
const type = CONTENT_TYPES[extname(target)] ?? 'application/octet-stream'
res.writeHead(200, {
'content-type': type,
'content-length': info.size,
'cache-control': options.immutable ? 'public, max-age=31536000, immutable' : 'no-cache',
})
await new Promise<void>((done) => {
const stream = createReadStream(target)
stream.on('error', () => {
if (!res.headersSent) sendError(res, 500, 'read error')
else res.end()
done()
})
stream.on('end', () => done())
stream.pipe(res)
})
}
export async function readJsonFile(path: string): Promise<unknown> {
const raw = await readFile(path, 'utf8')
return JSON.parse(raw)
}