Files

70 lines
3.0 KiB
TypeScript

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<string, string>) {
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<string, string>) {
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<string, string>) {
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)
}