Files
webnet/packages/drive/src/server/methods/get_head.ts
T
codingetandClaude 7ce1a42fbe fix(drive): serve the requested window on a Range without readFileRange
The fallback branch discarded range.start and streamed the file from byte
zero while the response advertised the requested window, so a client asking
for bytes 5-9 got bytes 0-4 and stored them at the wrong offset.
readFileRangeFallback windows the stream and calls the native operation when
there is one.

Fixes #177

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:53:52 +00:00

82 lines
2.8 KiB
TypeScript

import type { Context } from "@webnet/http/server"
import type { AsyncVFS } from "@webnet/vfs"
import { readFileRangeFallback } from "@webnet/vfs/fallback"
import type { DAVServerOptions } from "../types.js"
import { parseRange, formatHttpDate } from "../../common/utils.js"
export async function handleGet(
ctx: Context,
vfs: AsyncVFS,
vfsPath: string,
opts: DAVServerOptions = {},
): Promise<void> {
const stat = await vfs.stat(vfsPath)
if (stat.isDirectory) {
if (opts.onCollectionGet) {
await opts.onCollectionGet(ctx, vfs, vfsPath, stat)
} else {
ctx.res.setStatus(405, "Method Not Allowed")
ctx.res.setHeader("Content-Length", "0")
ctx.res.body = null
}
return
}
const range = parseRange(ctx.req.getHeader("range"))
if (range) {
const end = range.end ?? stat.size - 1n
if (range.start >= stat.size || end >= stat.size) {
ctx.res.setStatus(416, "Range Not Satisfiable")
ctx.res.setHeader("Content-Range", `bytes */${stat.size}`)
ctx.res.body = null
return
}
const length = end - range.start + 1n
const stream = await readFileRangeFallback(vfs, vfsPath, range.start, end)
ctx.res.setStatus(206, "Partial Content")
ctx.res.setHeader("Content-Range", `bytes ${range.start}-${end}/${stat.size}`)
ctx.res.setHeader("Content-Length", String(length))
if (stat.contentType) ctx.res.setHeader("Content-Type", stat.contentType)
if (stat.etag) ctx.res.setHeader("ETag", stat.etag)
if (stat.modifiedAt) ctx.res.setHeader("Last-Modified", formatHttpDate(stat.modifiedAt))
ctx.res.body = stream
return
}
const stream = await vfs.readFile(vfsPath)
ctx.res.setStatus(200, "OK")
ctx.res.setHeader("Content-Length", String(stat.size))
ctx.res.setHeader("Accept-Ranges", "bytes")
if (stat.contentType) ctx.res.setHeader("Content-Type", stat.contentType)
if (stat.etag) ctx.res.setHeader("ETag", stat.etag)
if (stat.modifiedAt) ctx.res.setHeader("Last-Modified", formatHttpDate(stat.modifiedAt))
ctx.res.body = stream
}
export async function handleHead(
ctx: Context,
vfs: AsyncVFS,
vfsPath: string,
opts: DAVServerOptions = {},
): Promise<void> {
const stat = await vfs.stat(vfsPath)
if (stat.isDirectory) {
if (opts.onCollectionGet) {
await opts.onCollectionGet(ctx, vfs, vfsPath, stat)
} else {
ctx.res.setStatus(405, "Method Not Allowed")
ctx.res.body = null
}
return
}
ctx.res.setStatus(200, "OK")
ctx.res.setHeader("Content-Length", String(stat.size))
ctx.res.setHeader("Accept-Ranges", "bytes")
if (stat.contentType) ctx.res.setHeader("Content-Type", stat.contentType)
if (stat.etag) ctx.res.setHeader("ETag", stat.etag)
if (stat.modifiedAt) ctx.res.setHeader("Last-Modified", formatHttpDate(stat.modifiedAt))
ctx.res.body = null
}