259 lines
7.1 KiB
TypeScript
Executable File
259 lines
7.1 KiB
TypeScript
Executable File
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
interface Update {
|
|
name: string;
|
|
value?: string|string[];
|
|
op: 'a'|'s'|'d';
|
|
}
|
|
|
|
/**
|
|
* `HttpHeaders` class represents the header configuration options for an HTTP request.
|
|
* Instances should be assumed immutable with lazy parsing.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export class HttpHeaders {
|
|
/**
|
|
* Internal map of lowercase header names to values.
|
|
*/
|
|
// TODO(issue/24571): remove '!'.
|
|
private headers !: Map<string, string[]>;
|
|
|
|
|
|
/**
|
|
* Internal map of lowercased header names to the normalized
|
|
* form of the name (the form seen first).
|
|
*/
|
|
private normalizedNames: Map<string, string> = new Map();
|
|
|
|
/**
|
|
* Complete the lazy initialization of this object (needed before reading).
|
|
*/
|
|
// TODO(issue/24571): remove '!'.
|
|
private lazyInit !: HttpHeaders | Function | null;
|
|
|
|
/**
|
|
* Queued updates to be materialized the next initialization.
|
|
*/
|
|
private lazyUpdate: Update[]|null = null;
|
|
|
|
/** Constructs a new HTTP header object with the given values.*/
|
|
|
|
constructor(headers?: string|{[name: string]: string | string[]}) {
|
|
if (!headers) {
|
|
this.headers = new Map<string, string[]>();
|
|
} else if (typeof headers === 'string') {
|
|
this.lazyInit = () => {
|
|
this.headers = new Map<string, string[]>();
|
|
headers.split('\n').forEach(line => {
|
|
const index = line.indexOf(':');
|
|
if (index > 0) {
|
|
const name = line.slice(0, index);
|
|
const key = name.toLowerCase();
|
|
const value = line.slice(index + 1).trim();
|
|
this.maybeSetNormalizedName(name, key);
|
|
if (this.headers.has(key)) {
|
|
this.headers.get(key) !.push(value);
|
|
} else {
|
|
this.headers.set(key, [value]);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
} else {
|
|
this.lazyInit = () => {
|
|
this.headers = new Map<string, string[]>();
|
|
Object.keys(headers).forEach(name => {
|
|
let values: string|string[] = headers[name];
|
|
const key = name.toLowerCase();
|
|
if (typeof values === 'string') {
|
|
values = [values];
|
|
}
|
|
if (values.length > 0) {
|
|
this.headers.set(key, values);
|
|
this.maybeSetNormalizedName(name, key);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks for existence of a header by a given name.
|
|
*
|
|
* @param name The header name to check for existence.
|
|
*
|
|
* @returns Whether the header exits.
|
|
*/
|
|
has(name: string): boolean {
|
|
this.init();
|
|
|
|
return this.headers.has(name.toLowerCase());
|
|
}
|
|
|
|
/**
|
|
* Returns the first header value that matches a given name.
|
|
*
|
|
* @param name The header name to retrieve.
|
|
*
|
|
* @returns A string if the header exists, null otherwise
|
|
*/
|
|
get(name: string): string|null {
|
|
this.init();
|
|
|
|
const values = this.headers.get(name.toLowerCase());
|
|
return values && values.length > 0 ? values[0] : null;
|
|
}
|
|
|
|
/**
|
|
* Returns the names of the headers.
|
|
*
|
|
* @returns A list of header names.
|
|
*/
|
|
keys(): string[] {
|
|
this.init();
|
|
|
|
return Array.from(this.normalizedNames.values());
|
|
}
|
|
|
|
/**
|
|
* Returns a list of header values for a given header name.
|
|
*
|
|
* @param name The header name from which to retrieve the values.
|
|
*
|
|
* @returns A string of values if the header exists, null otherwise.
|
|
*/
|
|
getAll(name: string): string[]|null {
|
|
this.init();
|
|
|
|
return this.headers.get(name.toLowerCase()) || null;
|
|
}
|
|
|
|
/**
|
|
* Appends a new header value to the existing set of
|
|
* header values.
|
|
*
|
|
* @param name The header name for which to append the values.
|
|
*
|
|
* @returns A clone of the HTTP header object with the value appended.
|
|
*/
|
|
|
|
append(name: string, value: string|string[]): HttpHeaders {
|
|
return this.clone({name, value, op: 'a'});
|
|
}
|
|
/**
|
|
* Sets a header value for a given name. If the header name already exists,
|
|
* its value is replaced with the given value.
|
|
*
|
|
* @param name The header name.
|
|
* @param value Provides the value to set or overide for a given name.
|
|
*
|
|
* @returns A clone of the HTTP header object with the newly set header value.
|
|
*/
|
|
set(name: string, value: string|string[]): HttpHeaders {
|
|
return this.clone({name, value, op: 's'});
|
|
}
|
|
/**
|
|
* Deletes all header values for a given name.
|
|
*
|
|
* @param name The header name.
|
|
* @param value The header values to delete for a given name.
|
|
*
|
|
* @returns A clone of the HTTP header object.
|
|
*/
|
|
delete (name: string, value?: string|string[]): HttpHeaders {
|
|
return this.clone({name, value, op: 'd'});
|
|
}
|
|
|
|
private maybeSetNormalizedName(name: string, lcName: string): void {
|
|
if (!this.normalizedNames.has(lcName)) {
|
|
this.normalizedNames.set(lcName, name);
|
|
}
|
|
}
|
|
|
|
private init(): void {
|
|
if (!!this.lazyInit) {
|
|
if (this.lazyInit instanceof HttpHeaders) {
|
|
this.copyFrom(this.lazyInit);
|
|
} else {
|
|
this.lazyInit();
|
|
}
|
|
this.lazyInit = null;
|
|
if (!!this.lazyUpdate) {
|
|
this.lazyUpdate.forEach(update => this.applyUpdate(update));
|
|
this.lazyUpdate = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private copyFrom(other: HttpHeaders) {
|
|
other.init();
|
|
Array.from(other.headers.keys()).forEach(key => {
|
|
this.headers.set(key, other.headers.get(key) !);
|
|
this.normalizedNames.set(key, other.normalizedNames.get(key) !);
|
|
});
|
|
}
|
|
|
|
private clone(update: Update): HttpHeaders {
|
|
const clone = new HttpHeaders();
|
|
clone.lazyInit =
|
|
(!!this.lazyInit && this.lazyInit instanceof HttpHeaders) ? this.lazyInit : this;
|
|
clone.lazyUpdate = (this.lazyUpdate || []).concat([update]);
|
|
return clone;
|
|
}
|
|
|
|
private applyUpdate(update: Update): void {
|
|
const key = update.name.toLowerCase();
|
|
switch (update.op) {
|
|
case 'a':
|
|
case 's':
|
|
let value = update.value !;
|
|
if (typeof value === 'string') {
|
|
value = [value];
|
|
}
|
|
if (value.length === 0) {
|
|
return;
|
|
}
|
|
this.maybeSetNormalizedName(update.name, key);
|
|
const base = (update.op === 'a' ? this.headers.get(key) : undefined) || [];
|
|
base.push(...value);
|
|
this.headers.set(key, base);
|
|
break;
|
|
case 'd':
|
|
const toDelete = update.value as string | undefined;
|
|
if (!toDelete) {
|
|
this.headers.delete(key);
|
|
this.normalizedNames.delete(key);
|
|
} else {
|
|
let existing = this.headers.get(key);
|
|
if (!existing) {
|
|
return;
|
|
}
|
|
existing = existing.filter(value => toDelete.indexOf(value) === -1);
|
|
if (existing.length === 0) {
|
|
this.headers.delete(key);
|
|
this.normalizedNames.delete(key);
|
|
} else {
|
|
this.headers.set(key, existing);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
forEach(fn: (name: string, values: string[]) => void) {
|
|
this.init();
|
|
Array.from(this.normalizedNames.keys())
|
|
.forEach(key => fn(this.normalizedNames.get(key) !, this.headers.get(key) !));
|
|
}
|
|
}
|