import type { RequestHeaders } from '../../routes/types.ts'; import { ApplicationError } from '../../errors/index.ts'; import { HTTP_STATUS_CODE_BAD_REQUEST } from '../../constants.ts'; /** RFC 9110 field-name token grammar. */ const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; /** * Validate and normalize request headers at a transport boundary. * * Lowercase names give HTTP and WebSocket routes identical lookup semantics. * Case-insensitive duplicates are rejected instead of selecting an ambiguous * authentication value. Header values may not contain line breaks. */ export const normalizeRequestHeaders = (headers: Readonly>): RequestHeaders => { const normalizedEntries: Array<[string, string]> = []; const names = new Set(); for (const [ name, value ] of Object.entries(headers)) { if (!HEADER_NAME_PATTERN.test(name)) { throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid request header name'); } if (value.includes('\r') || value.includes('\n')) { throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid request header value'); } const normalizedName = name.toLowerCase(); if (names.has(normalizedName)) { throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Duplicate request header name'); } names.add(normalizedName); normalizedEntries.push([ normalizedName, value ]); } return Object.freeze(Object.fromEntries(normalizedEntries)); };