import { Continue, FailureReason, ReturnValue } from "../HeadersParser.js" const constMaxPairs = 100 const constMaxSize = 16 * 1024 const enum State { key, whitespace, value, } const constContinue: Continue = { _tag: "Continue" } const constNameChars = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, ] const constValueChars = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ] export function make() { const decoder = new TextDecoder() const state = { state: State.key, headers: Object.create(null) as Record>, key: "", value: undefined as undefined | Uint8Array, crlf: 0, previousChunk: undefined as undefined | Uint8Array, pairs: 0, size: 0, } function reset(value: ReturnValue): ReturnValue { state.state = State.key state.headers = Object.create(null) state.key = "" state.value = undefined state.crlf = 0 state.previousChunk = undefined state.pairs = 0 state.size = 0 return value } function concatUint8Array(a: Uint8Array, b: Uint8Array): Uint8Array { const newUint8Array = new Uint8Array(a.length + b.length) newUint8Array.set(a) newUint8Array.set(b, a.length) return newUint8Array } function error(reason: FailureReason) { return reset({ _tag: "Failure", reason, headers: state.headers }) } return function write(chunk: Uint8Array, start: number): ReturnValue { let endOffset = 0 let previousCursor: number | undefined if (state.previousChunk !== undefined) { endOffset = state.previousChunk.length previousCursor = endOffset const newChunk = new Uint8Array(chunk.length + endOffset) newChunk.set(state.previousChunk) newChunk.set(chunk, endOffset) state.previousChunk = undefined chunk = newChunk } const end = chunk.length outer: while (start < end) { if (state.state === State.key) { let i = start for (; i < end; i++) { if (state.size++ > constMaxSize) { return error("HeaderTooLarge") } if (chunk[i] === 58) { state.key += decoder.decode(chunk.subarray(start, i)).toLowerCase() if (state.key.length === 0) { return error("InvalidHeaderName") } if ( chunk[i + 1] === 32 && chunk[i + 2] !== 32 && chunk[i + 2] !== 9 ) { start = i + 2 state.state = State.value state.size++ } else if (chunk[i + 1] !== 32 && chunk[i + 1] !== 9) { start = i + 1 state.state = State.value } else { start = i + 1 state.state = State.whitespace } break } else if (constNameChars[chunk[i]] !== 1) { return error("InvalidHeaderName") } } if (i === end) { state.key += decoder.decode(chunk.subarray(start, end)).toLowerCase() return constContinue } } if (state.state === State.whitespace) { for (; start < end; start++) { if (state.size++ > constMaxSize) { return error("HeaderTooLarge") } if (chunk[start] !== 32 && chunk[start] !== 9) { state.state = State.value break } } if (start === end) { return constContinue } } if (state.state === State.value) { let i = start if (previousCursor !== undefined) { i = previousCursor previousCursor = undefined } for (; i < end; i++) { if (state.size++ > constMaxSize) { return error("HeaderTooLarge") } if (chunk[i] === 13 || state.crlf > 0) { let byte = chunk[i] if (byte === 13 && state.crlf === 0) { state.crlf = 1 i++ state.size++ byte = chunk[i] } if (byte === 10 && state.crlf === 1) { state.crlf = 2 i++ state.size++ byte = chunk[i] } if (byte === 13 && state.crlf === 2) { state.crlf = 3 i++ state.size++ byte = chunk[i] } if (byte === 10 && state.crlf === 3) { state.crlf = 4 i++ state.size++ } if (state.crlf < 4 && i >= end) { state.previousChunk = chunk.subarray(start) return constContinue } else if (state.crlf >= 2) { state.value = state.value === undefined ? chunk.subarray(start, i - state.crlf) : concatUint8Array( state.value, chunk.subarray(start, i - state.crlf), ) const value = decoder.decode(state.value) if (state.headers[state.key] === undefined) { state.headers[state.key] = value } else if (typeof state.headers[state.key] === "string") { state.headers[state.key] = [ state.headers[state.key] as string, value, ] } else { ;(state.headers[state.key] as Array).push(value) } start = i state.size-- if (state.crlf !== 4 && state.pairs === constMaxPairs) { return error("TooManyHeaders") } else if (state.crlf === 3) { return error("InvalidHeaderValue") } else if (state.crlf === 4) { return reset({ _tag: "Headers", headers: state.headers, endPosition: start - endOffset, }) } state.pairs++ state.key = "" state.value = undefined state.crlf = 0 state.state = State.key continue outer } } else if (constValueChars[chunk[i]] !== 1) { return error("InvalidHeaderValue") } } if (i === end) { state.value = state.value === undefined ? chunk.subarray(start, end) : concatUint8Array(state.value, chunk.subarray(start, end)) return constContinue } } } if (start > end) { state.size += end - start } return constContinue } }