feat(http): add size limits to server connections and listen lifecycle hooks
This commit is contained in:
@@ -11,6 +11,13 @@ export interface BodyReader extends Reader {
|
||||
): void
|
||||
}
|
||||
|
||||
export type BodyReaderOptions = {
|
||||
/**
|
||||
* @defaultValue Infinity
|
||||
*/
|
||||
maxBodyLength?: number
|
||||
}
|
||||
|
||||
abstract class BaseBodyReader implements Pick<BodyReader, "onFinish"> {
|
||||
#finishOkHandlers: (() => void | Promise<void>)[]
|
||||
#finishKoHandlers: ((err: unknown) => void | Promise<void>)[]
|
||||
@@ -105,11 +112,17 @@ export class ChunkedBodyReader extends BaseBodyReader implements BodyReader {
|
||||
#chunks: Uint8Array[]
|
||||
#finished: boolean
|
||||
|
||||
constructor(buffer: ReadBuffer) {
|
||||
#bodyLength: number
|
||||
#maxBodyLength: number
|
||||
|
||||
constructor(buffer: ReadBuffer, { maxBodyLength = Infinity }: BodyReaderOptions = {}) {
|
||||
super()
|
||||
this.#buffer = buffer
|
||||
this.#chunks = []
|
||||
this.#finished = false
|
||||
|
||||
this.#bodyLength = 0
|
||||
this.#maxBodyLength = maxBodyLength
|
||||
}
|
||||
|
||||
get closed(): boolean {
|
||||
@@ -124,6 +137,9 @@ export class ChunkedBodyReader extends BaseBodyReader implements BodyReader {
|
||||
const len = Number.parseInt(chunkLine, 16)
|
||||
if (!Number.isInteger(len) || len < 0)
|
||||
throw new Error(`Invalid chunk size: ${JSON.stringify(chunkLine)}`)
|
||||
this.#bodyLength += len
|
||||
if (this.#bodyLength > this.#maxBodyLength)
|
||||
throw new Error(`Body too large: ${this.#bodyLength} (max: ${this.#maxBodyLength})`)
|
||||
if (!len) {
|
||||
this.#finished = true
|
||||
await this.#buffer.read(2)
|
||||
@@ -145,13 +161,39 @@ export class ChunkedBodyReader extends BaseBodyReader implements BodyReader {
|
||||
}
|
||||
}
|
||||
|
||||
export async function readHeaders(buffer: ReadBuffer): Promise<Headers> {
|
||||
export type ReadHeadersOptions = {
|
||||
/**
|
||||
* @defaultValue 256
|
||||
*/
|
||||
maxHeaderCount?: number
|
||||
/**
|
||||
* @defaultValue Infinity
|
||||
*/
|
||||
maxTotalHeaderSize?: number
|
||||
}
|
||||
|
||||
export async function readHeaders(
|
||||
buffer: ReadBuffer,
|
||||
{ maxHeaderCount = 256, maxTotalHeaderSize = Infinity }: ReadHeadersOptions = {},
|
||||
): Promise<Headers> {
|
||||
const rawHeaders: Record<string, string[]> = {}
|
||||
let headerCount = 0
|
||||
let totalHeaderSize = 0
|
||||
while (true) {
|
||||
const headerLine = await buffer.readLine()
|
||||
if (!headerLine.length) {
|
||||
break
|
||||
}
|
||||
|
||||
headerCount++
|
||||
if (headerCount > maxHeaderCount)
|
||||
throw new Error(`Too many headers: ${headerCount} (max: ${maxHeaderCount})`)
|
||||
totalHeaderSize += headerLine.length
|
||||
if (totalHeaderSize > maxTotalHeaderSize)
|
||||
throw new Error(
|
||||
`Total max header size exceeded: ${totalHeaderSize} (max: ${maxTotalHeaderSize})`,
|
||||
)
|
||||
|
||||
const colon = headerLine.indexOf(":")
|
||||
if (colon === -1) throw new Error(`Invalid header line (no colon): ${headerLine.slice(0, 80)}`)
|
||||
const key = headerLine.slice(0, colon)
|
||||
@@ -164,13 +206,19 @@ export async function readHeaders(buffer: ReadBuffer): Promise<Headers> {
|
||||
)
|
||||
}
|
||||
|
||||
export function bodyReader(buffer: ReadBuffer, headers: Headers): BodyReader | null {
|
||||
export function bodyReader(
|
||||
buffer: ReadBuffer,
|
||||
headers: Headers,
|
||||
{ maxBodyLength = Infinity }: BodyReaderOptions = {},
|
||||
): BodyReader | null {
|
||||
if (headers.get("Transfer-Encoding") === "chunked") {
|
||||
return new ChunkedBodyReader(buffer)
|
||||
return new ChunkedBodyReader(buffer, { maxBodyLength })
|
||||
} else if (typeof headers.get("Content-Length") === "string") {
|
||||
const len = +(headers.get("Content-Length") as string)
|
||||
if (!Number.isInteger(len) || len < 0)
|
||||
throw new Error(`Content-Length is invalid: ${headers.get("Content-Length")}`)
|
||||
if (len > maxBodyLength)
|
||||
throw new Error(`Content-Length is too big: ${len} (max: ${maxBodyLength})`)
|
||||
return new BasicBodyReader(buffer, len)
|
||||
} else {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user