diff options
Diffstat (limited to 'cli/js/headers.ts')
-rw-r--r-- | cli/js/headers.ts | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/cli/js/headers.ts b/cli/js/headers.ts new file mode 100644 index 000000000..dc0de54dd --- /dev/null +++ b/cli/js/headers.ts @@ -0,0 +1,139 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import * as domTypes from "./dom_types.ts"; +import { DomIterableMixin } from "./mixins/dom_iterable.ts"; +import { requiredArguments } from "./util.ts"; + +// From node-fetch +// Copyright (c) 2016 David Frank. MIT License. +const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/; +const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isHeaders(value: any): value is domTypes.Headers { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + return value instanceof Headers; +} + +const headerMap = Symbol("header map"); + +// ref: https://fetch.spec.whatwg.org/#dom-headers +class HeadersBase { + private [headerMap]: Map<string, string>; + // TODO: headerGuard? Investigate if it is needed + // node-fetch did not implement this but it is in the spec + + private _normalizeParams(name: string, value?: string): string[] { + name = String(name).toLowerCase(); + value = String(value).trim(); + return [name, value]; + } + + // The following name/value validations are copied from + // https://github.com/bitinn/node-fetch/blob/master/src/headers.js + // Copyright (c) 2016 David Frank. MIT License. + private _validateName(name: string): void { + if (invalidTokenRegex.test(name) || name === "") { + throw new TypeError(`${name} is not a legal HTTP header name`); + } + } + + private _validateValue(value: string): void { + if (invalidHeaderCharRegex.test(value)) { + throw new TypeError(`${value} is not a legal HTTP header value`); + } + } + + constructor(init?: domTypes.HeadersInit) { + if (init === null) { + throw new TypeError( + "Failed to construct 'Headers'; The provided value was not valid" + ); + } else if (isHeaders(init)) { + this[headerMap] = new Map(init); + } else { + this[headerMap] = new Map(); + if (Array.isArray(init)) { + for (const tuple of init) { + // If header does not contain exactly two items, + // then throw a TypeError. + // ref: https://fetch.spec.whatwg.org/#concept-headers-fill + requiredArguments( + "Headers.constructor tuple array argument", + tuple.length, + 2 + ); + + const [name, value] = this._normalizeParams(tuple[0], tuple[1]); + this._validateName(name); + this._validateValue(value); + const existingValue = this[headerMap].get(name); + this[headerMap].set( + name, + existingValue ? `${existingValue}, ${value}` : value + ); + } + } else if (init) { + const names = Object.keys(init); + for (const rawName of names) { + const rawValue = init[rawName]; + const [name, value] = this._normalizeParams(rawName, rawValue); + this._validateName(name); + this._validateValue(value); + this[headerMap].set(name, value); + } + } + } + } + + // ref: https://fetch.spec.whatwg.org/#concept-headers-append + append(name: string, value: string): void { + requiredArguments("Headers.append", arguments.length, 2); + const [newname, newvalue] = this._normalizeParams(name, value); + this._validateName(newname); + this._validateValue(newvalue); + const v = this[headerMap].get(newname); + const str = v ? `${v}, ${newvalue}` : newvalue; + this[headerMap].set(newname, str); + } + + delete(name: string): void { + requiredArguments("Headers.delete", arguments.length, 1); + const [newname] = this._normalizeParams(name); + this._validateName(newname); + this[headerMap].delete(newname); + } + + get(name: string): string | null { + requiredArguments("Headers.get", arguments.length, 1); + const [newname] = this._normalizeParams(name); + this._validateName(newname); + const value = this[headerMap].get(newname); + return value || null; + } + + has(name: string): boolean { + requiredArguments("Headers.has", arguments.length, 1); + const [newname] = this._normalizeParams(name); + this._validateName(newname); + return this[headerMap].has(newname); + } + + set(name: string, value: string): void { + requiredArguments("Headers.set", arguments.length, 2); + const [newname, newvalue] = this._normalizeParams(name, value); + this._validateName(newname); + this._validateValue(newvalue); + this[headerMap].set(newname, newvalue); + } + + get [Symbol.toStringTag](): string { + return "Headers"; + } +} + +// @internal +export class Headers extends DomIterableMixin< + string, + string, + typeof HeadersBase +>(HeadersBase, headerMap) {} |