70 lines
3.0 KiB
TypeScript
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)
|
|
}
|