UNPKG

@mjackson/form-data-parser

Version:

A request.formData() wrapper with streaming file upload handling

4 lines 128 kB
{ "version": 3, "sources": ["../src/form-data-parser.ts", "../../headers/src/lib/param-values.ts", "../../headers/src/lib/utils.ts", "../../headers/src/lib/accept.ts", "../../headers/src/lib/accept-encoding.ts", "../../headers/src/lib/accept-language.ts", "../../headers/src/lib/cache-control.ts", "../../headers/src/lib/content-disposition.ts", "../../headers/src/lib/content-type.ts", "../../headers/src/lib/cookie.ts", "../../headers/src/lib/if-none-match.ts", "../../headers/src/lib/set-cookie.ts", "../../headers/src/lib/header-names.ts", "../../headers/src/lib/super-headers.ts", "../../multipart-parser/src/lib/read-stream.ts", "../../multipart-parser/src/lib/buffer-search.ts", "../../multipart-parser/src/lib/multipart.ts", "../../multipart-parser/src/lib/multipart-request.ts", "../src/lib/form-data.ts"], "sourcesContent": ["export type { FileUploadHandler } from './lib/form-data.ts';\nexport {\n FormDataParseError,\n MaxFilesExceededError,\n FileUpload,\n parseFormData,\n} from './lib/form-data.ts';\n\n// Re-export errors that may be thrown by the parser.\nexport {\n MultipartParseError,\n MaxHeaderSizeExceededError,\n MaxFileSizeExceededError,\n} from '@mjackson/multipart-parser';\n", "export function parseParams(\n input: string,\n delimiter: ';' | ',' = ';',\n): [string, string | undefined][] {\n // This parser splits on the delimiter and unquotes any quoted values\n // like `filename=\"the\\\\ filename.txt\"`.\n let parser =\n delimiter === ';'\n ? /(?:^|;)\\s*([^=;\\s]+)(\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|((?:[^;]|\\\\\\;)+))?)?/g\n : /(?:^|,)\\s*([^=,\\s]+)(\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|((?:[^,]|\\\\\\,)+))?)?/g;\n\n let params: [string, string | undefined][] = [];\n\n let match;\n while ((match = parser.exec(input)) !== null) {\n let key = match[1].trim();\n\n let value: string | undefined;\n if (match[2]) {\n value = (match[3] || match[4] || '').replace(/\\\\(.)/g, '$1').trim();\n }\n\n params.push([key, value]);\n }\n\n return params;\n}\n\nexport function quote(value: string): string {\n if (value.includes('\"') || value.includes(';') || value.includes(' ')) {\n return `\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n return value;\n}\n", "export function capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\n}\n\nexport function isIterable<T>(value: any): value is Iterable<T> {\n return value != null && typeof value[Symbol.iterator] === 'function';\n}\n\nexport function isValidDate(date: unknown): boolean {\n return date instanceof Date && !isNaN(date.getTime());\n}\n\nexport function quoteEtag(tag: string): string {\n return tag === '*' ? tag : /^(W\\/)?\".*\"$/.test(tag) ? tag : `\"${tag}\"`;\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams } from './param-values.ts';\nimport { isIterable } from './utils.ts';\n\nexport type AcceptInit = Iterable<string | [string, number]> | Record<string, number>;\n\n/**\n * The value of a `Accept` HTTP header.\n *\n * [MDN `Accept` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)\n */\nexport class Accept implements HeaderValue, Iterable<[string, number]> {\n #map: Map<string, number>;\n\n constructor(init?: string | AcceptInit) {\n this.#map = new Map();\n\n if (init) {\n if (typeof init === 'string') {\n for (let piece of init.split(/\\s*,\\s*/)) {\n let params = parseParams(piece);\n if (params.length < 1) continue;\n\n let mediaType = params[0][0];\n let weight = 1;\n\n for (let i = 1; i < params.length; i++) {\n let [key, value] = params[i];\n if (key === 'q') {\n weight = Number(value);\n break;\n }\n }\n\n this.#map.set(mediaType.toLowerCase(), weight);\n }\n } else if (isIterable(init)) {\n for (let mediaType of init) {\n if (Array.isArray(mediaType)) {\n this.#map.set(mediaType[0].toLowerCase(), mediaType[1]);\n } else {\n this.#map.set(mediaType.toLowerCase(), 1);\n }\n }\n } else {\n for (let mediaType of Object.getOwnPropertyNames(init)) {\n this.#map.set(mediaType.toLowerCase(), init[mediaType]);\n }\n }\n\n this.#sort();\n }\n }\n\n #sort() {\n this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));\n }\n\n /**\n * An array of all media types in the header.\n */\n get mediaTypes(): string[] {\n return Array.from(this.#map.keys());\n }\n\n /**\n * An array of all weights (q values) in the header.\n */\n get weights(): number[] {\n return Array.from(this.#map.values());\n }\n\n /**\n * The number of media types in the `Accept` header.\n */\n get size(): number {\n return this.#map.size;\n }\n\n /**\n * Returns `true` if the header matches the given media type (i.e. it is \"acceptable\").\n * @param mediaType The media type to check.\n * @returns `true` if the media type is acceptable, `false` otherwise.\n */\n accepts(mediaType: string): boolean {\n return this.getWeight(mediaType) > 0;\n }\n\n /**\n * Gets the weight of a given media type. Also supports wildcards, so e.g. `text/*` will match `text/html`.\n * @param mediaType The media type to get the weight of.\n * @returns The weight of the media type.\n */\n getWeight(mediaType: string): number {\n let [type, subtype] = mediaType.toLowerCase().split('/');\n\n for (let [key, value] of this) {\n let [t, s] = key.split('/');\n if (\n (t === type || t === '*' || type === '*') &&\n (s === subtype || s === '*' || subtype === '*')\n ) {\n return value;\n }\n }\n\n return 0;\n }\n\n /**\n * Returns the most preferred media type from the given list of media types.\n * @param mediaTypes The list of media types to choose from.\n * @returns The most preferred media type or `null` if none match.\n */\n getPreferred(mediaTypes: string[]): string | null {\n let sorted = mediaTypes\n .map((mediaType) => [mediaType, this.getWeight(mediaType)] as const)\n .sort((a, b) => b[1] - a[1]);\n\n let first = sorted[0];\n\n return first !== undefined && first[1] > 0 ? first[0] : null;\n }\n\n /**\n * Returns the weight of a media type. If it is not in the header verbatim, this returns `null`.\n * @param mediaType The media type to get the weight of.\n * @returns The weight of the media type, or `null` if it is not in the header.\n */\n get(mediaType: string): number | null {\n return this.#map.get(mediaType.toLowerCase()) ?? null;\n }\n\n /**\n * Sets a media type with the given weight.\n * @param mediaType The media type to set.\n * @param weight The weight of the media type. Defaults to 1.\n */\n set(mediaType: string, weight = 1): void {\n this.#map.set(mediaType.toLowerCase(), weight);\n this.#sort();\n }\n\n /**\n * Removes the given media type from the header.\n * @param mediaType The media type to remove.\n */\n delete(mediaType: string): void {\n this.#map.delete(mediaType.toLowerCase());\n }\n\n /**\n * Checks if a media type is in the header.\n * @param mediaType The media type to check.\n * @returns `true` if the media type is in the header (verbatim), `false` otherwise.\n */\n has(mediaType: string): boolean {\n return this.#map.has(mediaType.toLowerCase());\n }\n\n /**\n * Removes all media types from the header.\n */\n clear(): void {\n this.#map.clear();\n }\n\n entries(): IterableIterator<[string, number]> {\n return this.#map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[string, number]> {\n return this.entries();\n }\n\n forEach(\n callback: (mediaType: string, weight: number, header: Accept) => void,\n thisArg?: any,\n ): void {\n for (let [mediaType, weight] of this) {\n callback.call(thisArg, mediaType, weight, this);\n }\n }\n\n toString(): string {\n let pairs: string[] = [];\n\n for (let [mediaType, weight] of this.#map) {\n pairs.push(`${mediaType}${weight === 1 ? '' : `;q=${weight}`}`);\n }\n\n return pairs.join(',');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams } from './param-values.ts';\nimport { isIterable } from './utils.ts';\n\nexport type AcceptEncodingInit = Iterable<string | [string, number]> | Record<string, number>;\n\n/**\n * The value of a `Accept-Encoding` HTTP header.\n *\n * [MDN `Accept-Encoding` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4)\n */\nexport class AcceptEncoding implements HeaderValue, Iterable<[string, number]> {\n #map: Map<string, number>;\n\n constructor(init?: string | AcceptEncodingInit) {\n this.#map = new Map();\n\n if (init) {\n if (typeof init === 'string') {\n for (let piece of init.split(/\\s*,\\s*/)) {\n let params = parseParams(piece);\n if (params.length < 1) continue;\n\n let encoding = params[0][0];\n let weight = 1;\n\n for (let i = 1; i < params.length; i++) {\n let [key, value] = params[i];\n if (key === 'q') {\n weight = Number(value);\n break;\n }\n }\n\n this.#map.set(encoding.toLowerCase(), weight);\n }\n } else if (isIterable(init)) {\n for (let value of init) {\n if (Array.isArray(value)) {\n this.#map.set(value[0].toLowerCase(), value[1]);\n } else {\n this.#map.set(value.toLowerCase(), 1);\n }\n }\n } else {\n for (let encoding of Object.getOwnPropertyNames(init)) {\n this.#map.set(encoding.toLowerCase(), init[encoding]);\n }\n }\n\n this.#sort();\n }\n }\n\n #sort() {\n this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));\n }\n\n /**\n * An array of all encodings in the header.\n */\n get encodings(): string[] {\n return Array.from(this.#map.keys());\n }\n\n /**\n * An array of all weights (q values) in the header.\n */\n get weights(): number[] {\n return Array.from(this.#map.values());\n }\n\n /**\n * The number of encodings in the header.\n */\n get size(): number {\n return this.#map.size;\n }\n\n /**\n * Returns `true` if the header matches the given encoding (i.e. it is \"acceptable\").\n * @param encoding The encoding to check.\n * @returns `true` if the encoding is acceptable, `false` otherwise.\n */\n accepts(encoding: string): boolean {\n return encoding.toLowerCase() === 'identity' || this.getWeight(encoding) > 0;\n }\n\n /**\n * Gets the weight an encoding. Performs wildcard matching so `*` matches all encodings.\n * @param encoding The encoding to get.\n * @returns The weight of the encoding, or `0` if it is not in the header.\n */\n getWeight(encoding: string): number {\n let lower = encoding.toLowerCase();\n\n for (let [enc, weight] of this) {\n if (enc === lower || enc === '*' || lower === '*') {\n return weight;\n }\n }\n\n return 0;\n }\n\n /**\n * Returns the most preferred encoding from the given list of encodings.\n * @param encodings The encodings to choose from.\n * @returns The most preferred encoding or `null` if none match.\n */\n getPreferred(encodings: string[]): string | null {\n let sorted = encodings\n .map((encoding) => [encoding, this.getWeight(encoding)] as const)\n .sort((a, b) => b[1] - a[1]);\n\n let first = sorted[0];\n\n return first !== undefined && first[1] > 0 ? first[0] : null;\n }\n\n /**\n * Gets the weight of an encoding. If it is not in the header verbatim, this returns `null`.\n * @param encoding The encoding to get.\n * @returns The weight of the encoding, or `null` if it is not in the header.\n */\n get(encoding: string): number | null {\n return this.#map.get(encoding.toLowerCase()) ?? null;\n }\n\n /**\n * Sets an encoding with the given weight.\n * @param encoding The encoding to set.\n * @param weight The weight of the encoding. Defaults to 1.\n */\n set(encoding: string, weight = 1): void {\n this.#map.set(encoding.toLowerCase(), weight);\n this.#sort();\n }\n\n /**\n * Removes the given encoding from the header.\n * @param encoding The encoding to remove.\n */\n delete(encoding: string): void {\n this.#map.delete(encoding.toLowerCase());\n }\n\n /**\n * Checks if the header contains a given encoding.\n * @param encoding The encoding to check.\n * @returns `true` if the encoding is in the header, `false` otherwise.\n */\n has(encoding: string): boolean {\n return this.#map.has(encoding.toLowerCase());\n }\n\n /**\n * Removes all encodings from the header.\n */\n clear(): void {\n this.#map.clear();\n }\n\n entries(): IterableIterator<[string, number]> {\n return this.#map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[string, number]> {\n return this.entries();\n }\n\n forEach(\n callback: (encoding: string, weight: number, header: AcceptEncoding) => void,\n thisArg?: any,\n ): void {\n for (let [encoding, weight] of this) {\n callback.call(thisArg, encoding, weight, this);\n }\n }\n\n toString(): string {\n let pairs: string[] = [];\n\n for (let [encoding, weight] of this.#map) {\n pairs.push(`${encoding}${weight === 1 ? '' : `;q=${weight}`}`);\n }\n\n return pairs.join(',');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams } from './param-values.ts';\nimport { isIterable } from './utils.ts';\n\nexport type AcceptLanguageInit = Iterable<string | [string, number]> | Record<string, number>;\n\n/**\n * The value of a `Accept-Language` HTTP header.\n *\n * [MDN `Accept-Language` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5)\n */\nexport class AcceptLanguage implements HeaderValue, Iterable<[string, number]> {\n #map: Map<string, number>;\n\n constructor(init?: string | AcceptLanguageInit) {\n this.#map = new Map();\n\n if (init) {\n if (typeof init === 'string') {\n for (let piece of init.split(/\\s*,\\s*/)) {\n let params = parseParams(piece);\n if (params.length < 1) continue;\n\n let language = params[0][0];\n let weight = 1;\n\n for (let i = 1; i < params.length; i++) {\n let [key, value] = params[i];\n if (key === 'q') {\n weight = Number(value);\n break;\n }\n }\n\n this.#map.set(language.toLowerCase(), weight);\n }\n } else if (isIterable(init)) {\n for (let value of init) {\n if (Array.isArray(value)) {\n this.#map.set(value[0].toLowerCase(), value[1]);\n } else {\n this.#map.set(value.toLowerCase(), 1);\n }\n }\n } else {\n for (let language of Object.getOwnPropertyNames(init)) {\n this.#map.set(language.toLowerCase(), init[language]);\n }\n }\n\n this.#sort();\n }\n }\n\n #sort() {\n this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));\n }\n\n /**\n * An array of all languages in the header.\n */\n get languages(): string[] {\n return Array.from(this.#map.keys());\n }\n\n /**\n * An array of all weights (q values) in the header.\n */\n get weights(): number[] {\n return Array.from(this.#map.values());\n }\n\n /**\n * The number of languages in the header.\n */\n get size(): number {\n return this.#map.size;\n }\n\n /**\n * Returns `true` if the header matches the given language (i.e. it is \"acceptable\").\n * @param language The locale identifier of the language to check.\n * @returns `true` if the language is acceptable, `false` otherwise.\n */\n accepts(language: string): boolean {\n return this.getWeight(language) > 0;\n }\n\n /**\n * Gets the weight of a language with the given locale identifier. Performs wildcard and subtype\n * matching, so `en` matches `en-US` and `en-GB`, and `*` matches all languages.\n * @param language The locale identifier of the language to get.\n * @returns The weight of the language, or `0` if it is not in the header.\n */\n getWeight(language: string): number {\n let [base, subtype] = language.toLowerCase().split('-');\n\n for (let [key, value] of this) {\n let [b, s] = key.split('-');\n if (\n (b === base || b === '*' || base === '*') &&\n (s === subtype || s === undefined || subtype === undefined)\n ) {\n return value;\n }\n }\n\n return 0;\n }\n\n /**\n * Returns the most preferred language from the given list of languages.\n * @param languages The locale identifiers of the languages to choose from.\n * @returns The most preferred language or `null` if none match.\n */\n getPreferred(languages: string[]): string | null {\n let sorted = languages\n .map((language) => [language, this.getWeight(language)] as const)\n .sort((a, b) => b[1] - a[1]);\n\n let first = sorted[0];\n\n return first !== undefined && first[1] > 0 ? first[0] : null;\n }\n\n /**\n * Gets the weight of a language with the given locale identifier. If it is not in the header\n * verbatim, this returns `null`.\n * @param language The locale identifier of the language to get.\n * @returns The weight of the language, or `null` if it is not in the header.\n */\n get(language: string): number | null {\n return this.#map.get(language.toLowerCase()) ?? null;\n }\n\n /**\n * Sets a language with the given weight.\n * @param language The locale identifier of the language to set.\n * @param weight The weight of the language. Defaults to 1.\n */\n set(language: string, weight = 1): void {\n this.#map.set(language.toLowerCase(), weight);\n this.#sort();\n }\n\n /**\n * Removes a language with the given locale identifier.\n * @param language The locale identifier of the language to remove.\n */\n delete(language: string): void {\n this.#map.delete(language.toLowerCase());\n }\n\n /**\n * Checks if the header contains a language with the given locale identifier.\n * @param language The locale identifier of the language to check.\n * @returns `true` if the language is in the header, `false` otherwise.\n */\n has(language: string): boolean {\n return this.#map.has(language.toLowerCase());\n }\n\n /**\n * Removes all languages from the header.\n */\n clear(): void {\n this.#map.clear();\n }\n\n entries(): IterableIterator<[string, number]> {\n return this.#map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[string, number]> {\n return this.entries();\n }\n\n forEach(\n callback: (language: string, weight: number, header: AcceptLanguage) => void,\n thisArg?: any,\n ): void {\n for (let [language, weight] of this) {\n callback.call(thisArg, language, weight, this);\n }\n }\n\n toString(): string {\n let pairs: string[] = [];\n\n for (let [language, weight] of this.#map) {\n pairs.push(`${language}${weight === 1 ? '' : `;q=${weight}`}`);\n }\n\n return pairs.join(',');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams } from './param-values.ts';\n\n// Taken from https://github.com/jjenzz/pretty-cache-header by jjenzz\n// License: MIT https://github.com/jjenzz/pretty-cache-header/blob/main/LICENSE\nexport interface CacheControlInit {\n /**\n * The `max-age=N` **request directive** indicates that the client allows a stored response that\n * is generated on the origin server within _N_ seconds \u2014 where _N_ may be any non-negative\n * integer (including `0`).\n *\n * The `max-age=N` **response directive** indicates that the response remains\n * [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)\n * until _N_ seconds after the response is generated.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-age)\n */\n maxAge?: number;\n /**\n * The `max-stale=N` **request directive** indicates that the client allows a stored response\n * that is [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)\n * within _N_ seconds.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-stale)\n */\n maxStale?: number;\n /**\n * The `min-fresh=N` **request directive** indicates that the client allows a stored response\n * that is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)\n * for at least _N_ seconds.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#min-fresh)\n */\n minFresh?: number;\n /**\n * The `s-maxage` **response directive** also indicates how long the response is\n * [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age) for (similar to `max-age`) \u2014\n * but it is specific to shared caches, and they will ignore `max-age` when it is present.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#s-maxage)\n */\n sMaxage?: number;\n /**\n * The `no-cache` **request directive** asks caches to validate the response with the origin\n * server before reuse. If you want caches to always check for content updates while reusing\n * stored content, `no-cache` is the directive to use.\n *\n * The `no-cache` **response directive** indicates that the response can be stored in caches, but\n * the response must be validated with the origin server before each reuse, even when the cache\n * is disconnected from the origin server.\n *\n * `no-cache` allows clients to request the most up-to-date response even if the cache has a\n * [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)\n * response.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-cache)\n */\n noCache?: true;\n /**\n * The `no-store` **request directive** allows a client to request that caches refrain from\n * storing the request and corresponding response \u2014 even if the origin server's response could\n * be stored.\n *\n * The `no-store` **response directive** indicates that any caches of any kind (private or shared)\n * should not store this response.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-store)\n */\n noStore?: true;\n /**\n * `no-transform` indicates that any intermediary (regardless of whether it implements a cache)\n * shouldn't transform the response contents.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-transform)\n */\n noTransform?: true;\n /**\n * The client indicates that cache should obtain an already-cached response. If a cache has\n * stored a response, it's reused.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#only-if-cached)\n */\n onlyIfCached?: true;\n /**\n * The `must-revalidate` **response directive** indicates that the response can be stored in\n * caches and can be reused while [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age).\n * If the response becomes [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age),\n * it must be validated with the origin server before reuse.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#must-revalidate)\n */\n mustRevalidate?: true;\n /**\n * The `proxy-revalidate` **response directive** is the equivalent of `must-revalidate`, but\n * specifically for shared caches only.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#proxy-revalidate)\n */\n proxyRevalidate?: true;\n /**\n * The `must-understand` **response directive** indicates that a cache should store the response\n * only if it understands the requirements for caching based on status code.\n *\n * `must-understand` should be coupled with `no-store` for fallback behavior.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#must-understand)\n */\n mustUnderstand?: true;\n /**\n * The `private` **response directive** indicates that the response can be stored only in a\n * private cache (e.g. local caches in browsers).\n *\n * You should add the `private` directive for user-personalized content, especially for responses\n * received after login and for sessions managed via cookies.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#private)\n */\n private?: true;\n /**\n * The `public` **response directive** indicates that the response can be stored in a shared\n * cache. Responses for requests with `Authorization` header fields must not be stored in a\n * shared cache; however, the `public` directive will cause such responses to be stored in a\n * shared cache.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#public)\n */\n public?: true;\n /**\n * The `immutable` **response directive** indicates that the response will not be updated while\n * it's [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age).\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#public)\n */\n immutable?: true;\n /**\n * The `stale-while-revalidate` **response directive** indicates that the cache could reuse a\n * stale response while it revalidates it to a cache.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-while-revalidate)\n */\n staleWhileRevalidate?: number;\n /**\n * The `stale-if-error` **response directive** indicates that the cache can reuse a\n * [stale response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)\n * when an upstream server generates an error, or when the error is generated locally. Here, an\n * error is considered any response with a status code of 500, 502, 503, or 504.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-if-error)\n */\n staleIfError?: number;\n}\n\n/**\n * The value of a `Cache-Control` HTTP header.\n *\n * [MDN `Cache-Control` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2)\n */\nexport class CacheControl implements HeaderValue, CacheControlInit {\n maxAge?: number;\n maxStale?: number;\n minFresh?: number;\n sMaxage?: number;\n noCache?: true;\n noStore?: true;\n noTransform?: true;\n onlyIfCached?: true;\n mustRevalidate?: true;\n proxyRevalidate?: true;\n mustUnderstand?: true;\n private?: true;\n public?: true;\n immutable?: true;\n staleWhileRevalidate?: number;\n staleIfError?: number;\n\n constructor(init?: string | CacheControlInit) {\n if (init) {\n if (typeof init === 'string') {\n let params = parseParams(init, ',');\n if (params.length > 0) {\n for (let [name, value] of params) {\n switch (name) {\n case 'max-age':\n this.maxAge = Number(value);\n break;\n case 'max-stale':\n this.maxStale = Number(value);\n break;\n case 'min-fresh':\n this.minFresh = Number(value);\n break;\n case 's-maxage':\n this.sMaxage = Number(value);\n break;\n case 'no-cache':\n this.noCache = true;\n break;\n case 'no-store':\n this.noStore = true;\n break;\n case 'no-transform':\n this.noTransform = true;\n break;\n case 'only-if-cached':\n this.onlyIfCached = true;\n break;\n case 'must-revalidate':\n this.mustRevalidate = true;\n break;\n case 'proxy-revalidate':\n this.proxyRevalidate = true;\n break;\n case 'must-understand':\n this.mustUnderstand = true;\n break;\n case 'private':\n this.private = true;\n break;\n case 'public':\n this.public = true;\n break;\n case 'immutable':\n this.immutable = true;\n break;\n case 'stale-while-revalidate':\n this.staleWhileRevalidate = Number(value);\n break;\n case 'stale-if-error':\n this.staleIfError = Number(value);\n break;\n }\n }\n }\n } else {\n this.maxAge = init.maxAge;\n this.maxStale = init.maxStale;\n this.minFresh = init.minFresh;\n this.sMaxage = init.sMaxage;\n this.noCache = init.noCache;\n this.noStore = init.noStore;\n this.noTransform = init.noTransform;\n this.onlyIfCached = init.onlyIfCached;\n this.mustRevalidate = init.mustRevalidate;\n this.proxyRevalidate = init.proxyRevalidate;\n this.mustUnderstand = init.mustUnderstand;\n this.private = init.private;\n this.public = init.public;\n this.immutable = init.immutable;\n this.staleWhileRevalidate = init.staleWhileRevalidate;\n this.staleIfError = init.staleIfError;\n }\n }\n }\n\n toString(): string {\n let parts = [];\n\n if (this.public) {\n parts.push('public');\n }\n if (this.private) {\n parts.push('private');\n }\n if (typeof this.maxAge === 'number') {\n parts.push(`max-age=${this.maxAge}`);\n }\n if (typeof this.sMaxage === 'number') {\n parts.push(`s-maxage=${this.sMaxage}`);\n }\n if (this.noCache) {\n parts.push('no-cache');\n }\n if (this.noStore) {\n parts.push('no-store');\n }\n if (this.noTransform) {\n parts.push('no-transform');\n }\n if (this.onlyIfCached) {\n parts.push('only-if-cached');\n }\n if (this.mustRevalidate) {\n parts.push('must-revalidate');\n }\n if (this.proxyRevalidate) {\n parts.push('proxy-revalidate');\n }\n if (this.mustUnderstand) {\n parts.push('must-understand');\n }\n if (this.immutable) {\n parts.push('immutable');\n }\n if (typeof this.staleWhileRevalidate === 'number') {\n parts.push(`stale-while-revalidate=${this.staleWhileRevalidate}`);\n }\n if (typeof this.staleIfError === 'number') {\n parts.push(`stale-if-error=${this.staleIfError}`);\n }\n if (typeof this.maxStale === 'number') {\n parts.push(`max-stale=${this.maxStale}`);\n }\n if (typeof this.minFresh === 'number') {\n parts.push(`min-fresh=${this.minFresh}`);\n }\n\n return parts.join(', ');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams, quote } from './param-values.ts';\n\nexport interface ContentDispositionInit {\n /**\n * For file uploads, the name of the file that the user selected.\n */\n filename?: string;\n /**\n * For file uploads, the name of the file that the user selected, encoded as a [RFC 8187](https://tools.ietf.org/html/rfc8187) `filename*` parameter.\n * This parameter allows non-ASCII characters in filenames, and specifies the character encoding.\n */\n filenameSplat?: string;\n /**\n * For `multipart/form-data` requests, the name of the `<input>` field associated with this content.\n */\n name?: string;\n /**\n * The disposition type of the content, such as `attachment` or `inline`.\n */\n type?: string;\n}\n\n/**\n * The value of a `Content-Disposition` HTTP header.\n *\n * [MDN `Content-Disposition` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)\n *\n * [RFC 6266](https://tools.ietf.org/html/rfc6266)\n */\nexport class ContentDisposition implements HeaderValue, ContentDispositionInit {\n filename?: string;\n filenameSplat?: string;\n name?: string;\n type?: string;\n\n constructor(init?: string | ContentDispositionInit) {\n if (init) {\n if (typeof init === 'string') {\n let params = parseParams(init);\n if (params.length > 0) {\n this.type = params[0][0];\n for (let [name, value] of params.slice(1)) {\n if (name === 'filename') {\n this.filename = value;\n } else if (name === 'filename*') {\n this.filenameSplat = value;\n } else if (name === 'name') {\n this.name = value;\n }\n }\n }\n } else {\n this.filename = init.filename;\n this.filenameSplat = init.filenameSplat;\n this.name = init.name;\n this.type = init.type;\n }\n }\n }\n\n /**\n * The preferred filename for the content, using the `filename*` parameter if present, falling back to the `filename` parameter.\n *\n * From [RFC 6266](https://tools.ietf.org/html/rfc6266):\n *\n * Many user agent implementations predating this specification do not understand the \"filename*\" parameter.\n * Therefore, when both \"filename\" and \"filename*\" are present in a single header field value, recipients SHOULD\n * pick \"filename*\" and ignore \"filename\". This way, senders can avoid special-casing specific user agents by\n * sending both the more expressive \"filename*\" parameter, and the \"filename\" parameter as fallback for legacy recipients.\n */\n get preferredFilename(): string | undefined {\n let filenameSplat = this.filenameSplat;\n if (filenameSplat) {\n let decodedFilename = decodeFilenameSplat(filenameSplat);\n if (decodedFilename) return decodedFilename;\n }\n\n return this.filename;\n }\n\n toString(): string {\n if (!this.type) {\n return '';\n }\n\n let parts = [this.type];\n\n if (this.name) {\n parts.push(`name=${quote(this.name)}`);\n }\n if (this.filename) {\n parts.push(`filename=${quote(this.filename)}`);\n }\n if (this.filenameSplat) {\n parts.push(`filename*=${quote(this.filenameSplat)}`);\n }\n\n return parts.join('; ');\n }\n}\n\nfunction decodeFilenameSplat(value: string): string | null {\n let match = value.match(/^([\\w-]+)'([^']*)'(.+)$/);\n if (!match) return null;\n\n let [, charset, , encodedFilename] = match;\n\n let decodedFilename = percentDecode(encodedFilename);\n\n try {\n let decoder = new TextDecoder(charset);\n let bytes = new Uint8Array(decodedFilename.split('').map((char) => char.charCodeAt(0)));\n return decoder.decode(bytes);\n } catch (error) {\n console.warn(`Failed to decode filename from charset ${charset}:`, error);\n return decodedFilename;\n }\n}\n\nfunction percentDecode(value: string): string {\n return value.replace(/\\+/g, ' ').replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => {\n return String.fromCharCode(parseInt(hex, 16));\n });\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams, quote } from './param-values.ts';\n\nexport interface ContentTypeInit {\n /**\n * For multipart entities, the boundary that separates the different parts of the message.\n */\n boundary?: string;\n /**\n * Indicates the [character encoding](https://developer.mozilla.org/en-US/docs/Glossary/Character_encoding) of the content.\n *\n * For example, `utf-8`, `iso-8859-1`.\n */\n charset?: string;\n /**\n * The media type (or MIME type) of the content. This consists of a type and subtype, separated by a slash.\n *\n * For example, `text/html`, `application/json`, `image/png`.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)\n */\n mediaType?: string;\n}\n\n/**\n * The value of a `Content-Type` HTTP header.\n *\n * [MDN `Content-Type` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5)\n */\nexport class ContentType implements HeaderValue, ContentTypeInit {\n boundary?: string;\n charset?: string;\n mediaType?: string;\n\n constructor(init?: string | ContentTypeInit) {\n if (init) {\n if (typeof init === 'string') {\n let params = parseParams(init);\n if (params.length > 0) {\n this.mediaType = params[0][0];\n for (let [name, value] of params.slice(1)) {\n if (name === 'boundary') {\n this.boundary = value;\n } else if (name === 'charset') {\n this.charset = value;\n }\n }\n }\n } else {\n this.boundary = init.boundary;\n this.charset = init.charset;\n this.mediaType = init.mediaType;\n }\n }\n }\n\n toString(): string {\n if (!this.mediaType) {\n return '';\n }\n\n let parts = [this.mediaType];\n\n if (this.charset) {\n parts.push(`charset=${quote(this.charset)}`);\n }\n if (this.boundary) {\n parts.push(`boundary=${quote(this.boundary)}`);\n }\n\n return parts.join('; ');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams, quote } from './param-values.ts';\nimport { isIterable } from './utils.ts';\n\nexport type CookieInit = Iterable<[string, string]> | Record<string, string>;\n\n/**\n * The value of a `Cookie` HTTP header.\n *\n * [MDN `Cookie` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc6265#section-4.2)\n */\nexport class Cookie implements HeaderValue, Iterable<[string, string]> {\n #map: Map<string, string>;\n\n constructor(init?: string | CookieInit) {\n this.#map = new Map();\n if (init) {\n if (typeof init === 'string') {\n let params = parseParams(init);\n for (let [name, value] of params) {\n this.#map.set(name, value ?? '');\n }\n } else if (isIterable(init)) {\n for (let [name, value] of init) {\n this.#map.set(name, value);\n }\n } else {\n for (let name of Object.getOwnPropertyNames(init)) {\n this.#map.set(name, init[name]);\n }\n }\n }\n }\n\n /**\n * An array of the names of the cookies in the header.\n */\n get names(): string[] {\n return Array.from(this.#map.keys());\n }\n\n /**\n * An array of the values of the cookies in the header.\n */\n get values(): string[] {\n return Array.from(this.#map.values());\n }\n\n /**\n * The number of cookies in the header.\n */\n get size(): number {\n return this.#map.size;\n }\n\n /**\n * Gets the value of a cookie with the given name from the header.\n * @param name The name of the cookie.\n * @returns The value of the cookie, or `null` if the cookie does not exist.\n */\n get(name: string): string | null {\n return this.#map.get(name) ?? null;\n }\n\n /**\n * Sets a cookie with the given name and value in the header.\n * @param name The name of the cookie.\n * @param value The value of the cookie.\n */\n set(name: string, value: string): void {\n this.#map.set(name, value);\n }\n\n /**\n * Removes a cookie with the given name from the header.\n * @param name The name of the cookie.\n */\n delete(name: string): void {\n this.#map.delete(name);\n }\n\n /**\n * True if a cookie with the given name exists in the header.\n * @param name The name of the cookie.\n * @returns True if a cookie with the given name exists in the header.\n */\n has(name: string): boolean {\n return this.#map.has(name);\n }\n\n /**\n * Removes all cookies from the header.\n */\n clear(): void {\n this.#map.clear();\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.#map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[string, string]> {\n return this.entries();\n }\n\n forEach(callback: (name: string, value: string, header: Cookie) => void, thisArg?: any): void {\n for (let [name, value] of this) {\n callback.call(thisArg, name, value, this);\n }\n }\n\n toString(): string {\n let pairs: string[] = [];\n\n for (let [name, value] of this.#map) {\n pairs.push(`${name}=${quote(value)}`);\n }\n\n return pairs.join('; ');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { quoteEtag } from './utils.ts';\n\nexport interface IfNoneMatchInit {\n /**\n * The entity tags to compare against the current entity.\n */\n tags: string[];\n}\n\n/**\n * The value of an `If-None-Match` HTTP header.\n *\n * [MDN `If-None-Match` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-3.2)\n */\nexport class IfNoneMatch implements HeaderValue, IfNoneMatchInit {\n tags: string[] = [];\n\n constructor(init?: string | string[] | IfNoneMatchInit) {\n if (init) {\n if (typeof init === 'string') {\n this.tags.push(...init.split(/\\s*,\\s*/).map(quoteEtag));\n } else if (Array.isArray(init)) {\n this.tags.push(...init.map(quoteEtag));\n } else {\n this.tags.push(...init.tags.map(quoteEtag));\n }\n }\n }\n\n /**\n * Checks if the header contains the given entity tag.\n *\n * Note: This method checks only for exact matches and does not consider wildcards.\n *\n * @param tag The entity tag to check for.\n * @returns `true` if the tag is present in the header, `false` otherwise.\n */\n has(tag: string): boolean {\n return this.tags.includes(quoteEtag(tag));\n }\n\n /**\n * Checks if this header matches the given entity tag.\n *\n * @param tag The entity tag to check for.\n * @returns `true` if the tag is present in the header (or the header contains a wildcard), `false` otherwise.\n */\n matches(tag: string): boolean {\n return this.has(tag) || this.tags.includes('*');\n }\n\n toString() {\n return this.tags.join(', ');\n }\n}\n", "import { type HeaderValue } from './header-value.ts';\nimport { parseParams, quote } from './param-values.ts';\nimport { capitalize, isValidDate } from './utils.ts';\n\ntype SameSiteValue = 'Strict' | 'Lax' | 'None';\n\nexport interface SetCookieInit {\n /**\n * The domain of the cookie. For example, `example.com`.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value)\n */\n domain?: string;\n /**\n * The expiration date of the cookie. If not specified, the cookie is a session cookie.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate)\n */\n expires?: Date;\n /**\n * Indicates this cookie should not be accessible via JavaScript.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly)\n */\n httpOnly?: true;\n /**\n * The maximum age of the cookie in seconds.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-age)\n */\n maxAge?: number;\n /**\n * The name of the cookie.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie-namecookie-value)\n */\n name?: string;\n /**\n * The path of the cookie. For example, `/` or `/admin`.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)\n */\n path?: string;\n /**\n * The `SameSite` attribute of the cookie. This attribute lets servers require that a cookie shouldn't be sent with\n * cross-site requests, which provides some protection against cross-site request forgery attacks.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)\n */\n sameSite?: SameSiteValue;\n /**\n * Indicates the cookie should only be sent over HTTPS.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)\n */\n secure?: true;\n /**\n * The value of the cookie.\n *\n * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie-namecookie-value)\n */\n value?: string;\n}\n\n/**\n * The value of a `Set-Cookie` HTTP header.\n *\n * [MDN `Set-Cookie` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)\n *\n * [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1)\n */\nexport class SetCookie implements HeaderValue, SetCookieInit {\n domain?: string;\n expires?: Date;\n httpOnly?: true;\n maxAge?: number;\n name?: string;\n path?: string;\n sameSite?: SameSiteValue;\n secure?: true;\n value?: string;\n\n constructor(init?: string | SetCookieInit) {\n if (init) {\n if (typeof init === 'string') {\n let params = parseParams(init);\n if (params.length > 0) {\n this.name = params[0][0];\n this.value = params[0][1];\n\n for (let [key, value] of params.slice(1)) {\n switch (key.toLowerCase()) {\n case 'domain':\n this.domain = value;\n break;\n case 'expires': {\n if (typeof value === 'string') {\n let date = new Date(value);\n if (isValidDate(date)) {\n this.expires = date;\n }\n }\n break;\n }\n case 'httponly':\n this.httpOnly = true;\n break;\n case 'max-age': {\n if (typeof value === 'string') {\n let v = parseInt(value, 10);\n if (!isNaN(v)) this.maxAge = v;\n }\n break;\n }\n case 'path':\n this.path = value;\n break;\n case 'samesite':\n if (typeof value === 'string' && /strict|lax|none/i.test(value)) {\n this.sameSite = capitalize(value) as SameSiteValue;\n }\n break;\n case 'secure':\n this.secure = true;\n break;\n }\n }\n }\n } else {\n this.domain = init.domain;\n this.expires = init.expires;\n this.httpOnly = init.httpOnly;\n this.maxAge = init.maxAge;\n this.name = init.name;\n this.path = init.path;\n this.sameSite = init.sameSite;\n this.secure = init.secure;\n this.value = init.value;\n }\n }\n }\n\n toString(): string {\n if (!this.name) {\n return '';\n }\n\n let parts = [`${this.name}=${quote(this.value || '')}`];\n\n if (this.domain) {\n parts.push(`Domain=${this.domain}`);\n }\n if (this.path) {\n parts.push(`Path=${this.path}`);\n }\n if (this.expires) {\n parts.push(`Expires=${this.expires.toUTCString()}`);\n }\n if (this.maxAge) {\n parts.push(`Max-Age=${this.maxAge}`);\n }\n if (this.secure) {\n parts.push('Secure');\n }\n if (this.httpOnly) {\n parts.push('HttpOnly');\n }\n if (this.sameSite) {\n parts.push(`SameSite=${this.sameSite}`);\n }\n\n return parts.join('; ');\n }\n}\n", "const HeaderWordCasingExceptions: Record<string, string> = {\n ct: 'CT',\n etag: 'ETag',\n te: 'TE',\n www: 'WWW',\n x: 'X',\n xss: 'XSS',\n};\n\nexport function canonicalHeaderName(name: string): string {\n return name\n .toLowerCase()\n .split('-')\n .map((word) => HeaderWordCasingExceptions[word] || word.charAt(0).toUpperCase() + word.slice(1))\n .join('-');\n}\n", "import { type AcceptInit, Accept } from './accept.ts';\nimport { type AcceptEncodingInit, AcceptEncoding } from './accept-encoding.ts';\nimport { type AcceptLanguageInit, AcceptLanguage } from './accept-language.ts';\nimport { type CacheControlInit, CacheControl } from './cache-control.ts';\nimport { type ContentDispositionInit, ContentDisposition } from './content-disposition.ts';\nimport { type ContentTypeInit, ContentType } from './content-type.ts';\nimport { type CookieInit, Cookie } from './cookie.ts';\nimport { canonicalHeaderName } from './header-names.ts';\nimport { type HeaderValue } from './header-value.ts';\nimport { type IfNoneMatchInit, IfNoneMatch } from './if-none-match.ts';\nimport { type SetCookieInit, SetCookie } from './set-cookie.ts';\nimport { isIterable, quoteEtag } from './utils