mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
baa093ba68
Preserve explicit referrer policy when the service worker reconstructs asset requests for cache-busted and redirected asset fetches.
For example, an application can load a script or image with referrerPolicy: 'same-origin' or 'origin' to limit referrer data. Dropping that policy can expose more of the current URL to that resource host.
(cherry picked from commit a7f52e5c30)
241 lines
6.1 KiB
TypeScript
241 lines
6.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC 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.dev/license
|
|
*/
|
|
|
|
export class MockBody implements Body {
|
|
readonly body!: ReadableStream;
|
|
bodyUsed: boolean = false;
|
|
|
|
constructor(public _body: string | null) {}
|
|
|
|
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
const body = this.getBody();
|
|
const buffer = new ArrayBuffer(body.length);
|
|
const view = new Uint8Array(buffer);
|
|
|
|
for (let i = 0; i < body.length; i++) {
|
|
view[i] = body.charCodeAt(i);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
async blob(): Promise<Blob> {
|
|
throw 'Not implemented';
|
|
}
|
|
|
|
async json(): Promise<any> {
|
|
return JSON.parse(this.getBody()) as any;
|
|
}
|
|
|
|
async text(): Promise<string> {
|
|
return this.getBody();
|
|
}
|
|
|
|
async formData(): Promise<FormData> {
|
|
throw 'Not implemented';
|
|
}
|
|
|
|
async bytes(): Promise<Uint8Array<ArrayBuffer>> {
|
|
throw 'Not implemented';
|
|
}
|
|
|
|
private getBody(): string {
|
|
if (this.bodyUsed === true) {
|
|
throw new Error('Cannot reuse body without cloning.');
|
|
}
|
|
this.bodyUsed = true;
|
|
|
|
// According to the spec, a `null` body results in an empty `ReadableStream` (which for our
|
|
// needs is equivalent to `''`). See https://fetch.spec.whatwg.org/#concept-body-consume-body.
|
|
return this._body || '';
|
|
}
|
|
}
|
|
|
|
export class MockHeaders implements Headers {
|
|
map = new Map<string, string>();
|
|
|
|
constructor(headers?: HeadersInit) {
|
|
if (headers === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(headers)) {
|
|
headers.forEach(([name, value]) => this.set(name, value));
|
|
} else if (headers instanceof MockHeaders || headers instanceof Headers) {
|
|
headers.forEach((value, name) => this.set(name, value));
|
|
} else {
|
|
Object.entries(headers).forEach(([name, value]) => this.set(name, value));
|
|
}
|
|
}
|
|
|
|
[Symbol.iterator]() {
|
|
return this.map[Symbol.iterator]();
|
|
}
|
|
|
|
append(name: string, value: string): void {
|
|
this.map.set(name.toLowerCase(), value);
|
|
}
|
|
|
|
delete(name: string): void {
|
|
this.map.delete(name.toLowerCase());
|
|
}
|
|
|
|
entries() {
|
|
return this.map.entries();
|
|
}
|
|
|
|
forEach(callback: Function): void {
|
|
this.map.forEach(callback as any);
|
|
}
|
|
|
|
get(name: string): string | null {
|
|
return this.map.get(name.toLowerCase()) || null;
|
|
}
|
|
|
|
has(name: string): boolean {
|
|
return this.map.has(name.toLowerCase());
|
|
}
|
|
|
|
keys() {
|
|
return this.map.keys();
|
|
}
|
|
|
|
set(name: string, value: string): void {
|
|
this.map.set(name.toLowerCase(), value);
|
|
}
|
|
|
|
values() {
|
|
return this.map.values();
|
|
}
|
|
|
|
getSetCookie(): string[] {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export class MockRequest extends MockBody implements Request {
|
|
readonly isHistoryNavigation: boolean = false;
|
|
readonly isReloadNavigation: boolean = false;
|
|
readonly cache: RequestCache = 'default';
|
|
readonly credentials: RequestCredentials = 'same-origin';
|
|
readonly destination: RequestDestination = 'document';
|
|
readonly headers: Headers = new MockHeaders();
|
|
readonly integrity: string = '';
|
|
readonly keepalive: boolean = true;
|
|
readonly method: string = 'GET';
|
|
readonly mode: RequestMode = 'cors';
|
|
readonly redirect: RequestRedirect = 'follow';
|
|
readonly referrer: string = 'about:client';
|
|
readonly referrerPolicy: ReferrerPolicy = '';
|
|
readonly signal: AbortSignal = null as any;
|
|
|
|
url: string;
|
|
|
|
constructor(
|
|
input: string | Request,
|
|
init: RequestInit & {destination?: RequestDestination} = {},
|
|
) {
|
|
super((init.body as string | null) ?? null);
|
|
if (typeof input !== 'string') {
|
|
throw 'Not implemented';
|
|
}
|
|
this.url = input;
|
|
if (init.headers !== undefined) {
|
|
this.headers = new MockHeaders(init.headers);
|
|
}
|
|
if (init.cache !== undefined) {
|
|
this.cache = init.cache;
|
|
}
|
|
if (init.mode !== undefined) {
|
|
this.mode = init.mode;
|
|
}
|
|
if (init.credentials !== undefined) {
|
|
this.credentials = init.credentials;
|
|
}
|
|
if (init.method !== undefined) {
|
|
this.method = init.method;
|
|
}
|
|
if (init.redirect !== undefined) {
|
|
this.redirect = init.redirect;
|
|
}
|
|
if (init.referrer !== undefined) {
|
|
this.referrer = init.referrer;
|
|
}
|
|
if (init.referrerPolicy !== undefined) {
|
|
this.referrerPolicy = init.referrerPolicy;
|
|
}
|
|
if (init.destination !== undefined) {
|
|
this.destination = init.destination;
|
|
}
|
|
}
|
|
|
|
clone(): Request {
|
|
if (this.bodyUsed) {
|
|
throw 'Body already consumed';
|
|
}
|
|
return new MockRequest(this.url, {
|
|
body: this._body,
|
|
cache: this.cache,
|
|
mode: this.mode,
|
|
credentials: this.credentials,
|
|
headers: this.headers,
|
|
redirect: this.redirect,
|
|
referrer: this.referrer,
|
|
referrerPolicy: this.referrerPolicy,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class MockResponse extends MockBody implements Response {
|
|
readonly trailer: Promise<Headers> = Promise.resolve(new MockHeaders());
|
|
readonly headers: Headers = new MockHeaders();
|
|
get ok(): boolean {
|
|
return this.status >= 200 && this.status < 300;
|
|
}
|
|
readonly status: number;
|
|
readonly statusText: string;
|
|
readonly type: ResponseType = 'basic';
|
|
readonly url: string = '';
|
|
readonly redirected: boolean = false;
|
|
|
|
constructor(
|
|
body?: any,
|
|
init: ResponseInit & {type?: ResponseType; redirected?: boolean; url?: string} = {},
|
|
) {
|
|
super(typeof body === 'string' ? body : null);
|
|
this.status = init.status !== undefined ? init.status : 200;
|
|
this.statusText = init.statusText !== undefined ? init.statusText : 'OK';
|
|
if (init.headers !== undefined) {
|
|
this.headers = new MockHeaders(init.headers);
|
|
}
|
|
if (init.type !== undefined) {
|
|
this.type = init.type;
|
|
}
|
|
if (init.redirected !== undefined) {
|
|
this.redirected = init.redirected;
|
|
}
|
|
if (init.url !== undefined) {
|
|
this.url = init.url;
|
|
}
|
|
}
|
|
|
|
clone(): Response {
|
|
if (this.bodyUsed) {
|
|
throw 'Body already consumed';
|
|
}
|
|
return new MockResponse(this._body, {
|
|
status: this.status,
|
|
statusText: this.statusText,
|
|
headers: this.headers,
|
|
type: this.type,
|
|
redirected: this.redirected,
|
|
url: this.url,
|
|
});
|
|
}
|
|
}
|