mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 23:34:47 -05:00
967c236fa5
Worth noting due to implementation of the Headers class the contents of headersMap have lowercase keys, although this matches the specification as header keys are case agnostic it does seem to not match behaviour of other implementations in other languages I have seen, would require some rewriting of Headers.ts
152 lines
5 KiB
TypeScript
152 lines
5 KiB
TypeScript
// 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";
|
|
import { customInspect } from "./console.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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[customInspect](): string {
|
|
let headerSize = this[headerMap].size;
|
|
let output = "";
|
|
this[headerMap].forEach((value, key) => {
|
|
const prefix = headerSize === this[headerMap].size ? " " : "";
|
|
const postfix = headerSize === 1 ? " " : ", ";
|
|
output = output + `${prefix}${key}: ${value}${postfix}`;
|
|
headerSize--;
|
|
});
|
|
return `Headers {${output}}`;
|
|
}
|
|
|
|
// 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) {}
|