File
Blob: src/node/internal/internal_http_util.ts
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | // Copyright Joyent and Node contributors. All rights reserved. MIT license. |
| 5 | import type { IncomingMessage } from 'node:http'; |
| 6 | |
| 7 | export type IncomingMessageCallback = (req: IncomingMessage) => void; |
| 8 | |
| 9 | interface OnceOptions { |
| 10 | preserveReturnValue?: boolean; |
| 11 | } |
| 12 | |
| 13 | export function once<RT>( |
| 14 | this: unknown, |
| 15 | callback: (...allArgs: unknown[]) => RT, |
| 16 | options?: OnceOptions |
| 17 | ): (...all: unknown[]) => RT { |
| 18 | const preserveReturnValue = options?.preserveReturnValue ?? false; |
| 19 | let called = false; |
| 20 | let returnValue: RT; |
| 21 | return function (this: unknown, ...args: unknown[]): RT { |
| 22 | if (called) return returnValue; |
| 23 | called = true; |
| 24 | const result = Reflect.apply(callback, this, args); |
| 25 | returnValue = preserveReturnValue ? result : (undefined as RT); |
| 26 | return result; |
| 27 | }; |
| 28 | } |
| 29 | |
| 30 | export const kServerResponse = Symbol('ServerResponse'); |
| 31 | export const kIncomingMessage = Symbol('IncomingMessage'); |
| 32 | |
| 33 | // RFC 7230 compliant header value splitting that respects quoted-string constructions |
| 34 | // Returns only the first value up to the first comma (outside of quoted strings) |
| 35 | // Spec: https://www.rfc-editor.org/rfc/rfc7230.html#appendix-B |
| 36 | // Edge cases handled: |
| 37 | // - Quoted strings with commas: 'text/plain; param="a, b"' -> 'text/plain; param="a, b"' |
| 38 | // - Escaped quotes in strings: 'text/plain; param="a \"quoted\" value"' -> 'text/plain; param="a \"quoted\" value"' |
| 39 | // - Multiple values: 'text/plain; f="a, b", text/html' -> 'text/plain; f="a, b"' |
| 40 | // - Whitespace before commas: 'value1 \t , value2' -> 'value1' |
| 41 | // - No commas: 'single-value' -> 'single-value' |
| 42 | export function splitHeaderValue(value: string): string { |
| 43 | let inQuotes = false; |
| 44 | |
| 45 | for (let i = 0; i < value.length; i++) { |
| 46 | const char = value[i]; |
| 47 | |
| 48 | if (char === '"') { |
| 49 | inQuotes = !inQuotes; |
| 50 | } else if (char === '\\' && inQuotes) { |
| 51 | i++; // Skip next character (it's escaped) |
| 52 | } else if (!inQuotes && char === ',') { |
| 53 | // Found unquoted comma, return |
| 54 | return value.slice(0, i); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | return value; |
| 59 | } |