import { createReadStream, statSync } from 'node:fs' import { createError, getHeader, sendStream, setHeader, setResponseStatus, type H3Event } from 'h3' function parseBytesRange(header: string | undefined, size: number) { if (!header) return null const match = /^bytes=(\d*)-(\d*)$/i.exec(header.trim()) if (!match) return null const startToken = match[1] const endToken = match[2] if (!startToken && !endToken) return { unsatisfiable: true as const, start: 0, end: 0 } let start: number let end: number if (!startToken) { const suffix = Number(endToken) if (!Number.isFinite(suffix) || suffix <= 0) return { unsatisfiable: true as const, start: 0, end: 0 } start = Math.max(0, size - suffix) end = size - 1 } else { start = Number(startToken) end = endToken ? Number(endToken) : size - 1 } if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || start >= size || end < start) { return { unsatisfiable: true as const, start: 0, end: 0 } } return { start, end: Math.min(end, size - 1) } } function applyRangeHeaders(event: H3Event, contentType: string, extraHeaders?: Record) { setHeader(event, 'Accept-Ranges', 'bytes') setHeader(event, 'Content-Type', contentType) for (const [key, value] of Object.entries(extraHeaders || {})) { setHeader(event, key, value) } } function rejectUnsatisfiable(event: H3Event, size: number) { setHeader(event, 'Content-Range', `bytes */${size}`) throw createError({ statusCode: 416, statusMessage: 'Range Not Satisfiable' }) } export function sendPathWithRange(event: H3Event, path: string, contentType: string, extraHeaders?: Record) { const size = statSync(path).size applyRangeHeaders(event, contentType, extraHeaders) const range = parseBytesRange(getHeader(event, 'range'), size) if (!range) { setHeader(event, 'Content-Length', String(size)) return sendStream(event, createReadStream(path)) } if ('unsatisfiable' in range && range.unsatisfiable) rejectUnsatisfiable(event, size) setResponseStatus(event, 206) setHeader(event, 'Content-Range', `bytes ${range.start}-${range.end}/${size}`) setHeader(event, 'Content-Length', String(range.end - range.start + 1)) return sendStream(event, createReadStream(path, { start: range.start, end: range.end })) } export function sendBufferWithRange(event: H3Event, buf: Buffer, contentType: string, extraHeaders?: Record) { const size = buf.length applyRangeHeaders(event, contentType, extraHeaders) const range = parseBytesRange(getHeader(event, 'range'), size) if (!range) { setHeader(event, 'Content-Length', String(size)) return buf } if ('unsatisfiable' in range && range.unsatisfiable) rejectUnsatisfiable(event, size) setResponseStatus(event, 206) setHeader(event, 'Content-Range', `bytes ${range.start}-${range.end}/${size}`) setHeader(event, 'Content-Length', String(range.end - range.start + 1)) return buf.subarray(range.start, range.end + 1) }