diff options
Diffstat (limited to 'cli/js/web/fetch.ts')
-rw-r--r-- | cli/js/web/fetch.ts | 361 |
1 files changed, 0 insertions, 361 deletions
diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts deleted file mode 100644 index 4fe525cde..000000000 --- a/cli/js/web/fetch.ts +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -import { notImplemented } from "../util.ts"; -import { isTypedArray } from "./util.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { TextEncoder } from "./text_encoding.ts"; -import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob.ts"; -import { read } from "../ops/io.ts"; -import { close } from "../ops/resources.ts"; -import { fetch as opFetch } from "../ops/fetch.ts"; -import type { FetchResponse } from "../ops/fetch.ts"; -import * as Body from "./body.ts"; -import { getHeaderValueParams } from "./util.ts"; -import { ReadableStreamImpl } from "./streams/readable_stream.ts"; -import { MultipartBuilder } from "./fetch/multipart.ts"; - -const NULL_BODY_STATUS = [101, 204, 205, 304]; -const REDIRECT_STATUS = [301, 302, 303, 307, 308]; - -const responseData = new WeakMap(); -export class Response extends Body.Body implements domTypes.Response { - readonly type: ResponseType; - readonly redirected: boolean; - readonly url: string; - readonly status: number; - readonly statusText: string; - headers: Headers; - - constructor(body: BodyInit | null = null, init?: domTypes.ResponseInit) { - init = init ?? {}; - - if (typeof init !== "object") { - throw new TypeError(`'init' is not an object`); - } - - const extraInit = responseData.get(init) || {}; - let { type = "default", url = "" } = extraInit; - - let status = init.status === undefined ? 200 : Number(init.status || 0); - let statusText = init.statusText ?? ""; - let headers = init.headers instanceof Headers - ? init.headers - : new Headers(init.headers); - - if (init.status !== undefined && (status < 200 || status > 599)) { - throw new RangeError( - `The status provided (${init.status}) is outside the range [200, 599]`, - ); - } - - // null body status - if (body && NULL_BODY_STATUS.includes(status)) { - throw new TypeError("Response with null body status cannot have body"); - } - - if (!type) { - type = "default"; - } else { - if (type == "error") { - // spec: https://fetch.spec.whatwg.org/#concept-network-error - status = 0; - statusText = ""; - headers = new Headers(); - body = null; - /* spec for other Response types: - https://fetch.spec.whatwg.org/#concept-filtered-response-basic - Please note that type "basic" is not the same thing as "default".*/ - } else if (type == "basic") { - for (const h of headers) { - /* Forbidden Response-Header Names: - https://fetch.spec.whatwg.org/#forbidden-response-header-name */ - if (["set-cookie", "set-cookie2"].includes(h[0].toLowerCase())) { - headers.delete(h[0]); - } - } - } else if (type == "cors") { - /* CORS-safelisted Response-Header Names: - https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name */ - const allowedHeaders = [ - "Cache-Control", - "Content-Language", - "Content-Length", - "Content-Type", - "Expires", - "Last-Modified", - "Pragma", - ].map((c: string) => c.toLowerCase()); - for (const h of headers) { - /* Technically this is still not standards compliant because we are - supposed to allow headers allowed in the - 'Access-Control-Expose-Headers' header in the 'internal response' - However, this implementation of response doesn't seem to have an - easy way to access the internal response, so we ignore that - header. - TODO(serverhiccups): change how internal responses are handled - so we can do this properly. */ - if (!allowedHeaders.includes(h[0].toLowerCase())) { - headers.delete(h[0]); - } - } - /* TODO(serverhiccups): Once I fix the 'internal response' thing, - these actually need to treat the internal response differently */ - } else if (type == "opaque" || type == "opaqueredirect") { - url = ""; - status = 0; - statusText = ""; - headers = new Headers(); - body = null; - } - } - - const contentType = headers.get("content-type") || ""; - const size = Number(headers.get("content-length")) || undefined; - - super(body, { contentType, size }); - - this.url = url; - this.statusText = statusText; - this.status = extraInit.status || status; - this.headers = headers; - this.redirected = extraInit.redirected || false; - this.type = type; - } - - get ok(): boolean { - return 200 <= this.status && this.status < 300; - } - - public clone(): domTypes.Response { - if (this.bodyUsed) { - throw TypeError(Body.BodyUsedError); - } - - const iterators = this.headers.entries(); - const headersList: Array<[string, string]> = []; - for (const header of iterators) { - headersList.push(header); - } - - let resBody = this._bodySource; - - if (this._bodySource instanceof ReadableStreamImpl) { - const tees = this._bodySource.tee(); - this._stream = this._bodySource = tees[0]; - resBody = tees[1]; - } - - return new Response(resBody, { - status: this.status, - statusText: this.statusText, - headers: new Headers(headersList), - }); - } - - static redirect(url: URL | string, status: number): domTypes.Response { - if (![301, 302, 303, 307, 308].includes(status)) { - throw new RangeError( - "The redirection status must be one of 301, 302, 303, 307 and 308.", - ); - } - return new Response(null, { - status, - statusText: "", - headers: [["Location", typeof url === "string" ? url : url.toString()]], - }); - } -} - -function sendFetchReq( - url: string, - method: string | null, - headers: Headers | null, - body: ArrayBufferView | undefined, -): Promise<FetchResponse> { - let headerArray: Array<[string, string]> = []; - if (headers) { - headerArray = Array.from(headers.entries()); - } - - const args = { - method, - url, - headers: headerArray, - }; - - return opFetch(args, body); -} - -export async function fetch( - input: (domTypes.Request & { _bodySource?: unknown }) | URL | string, - init?: domTypes.RequestInit, -): Promise<Response> { - let url: string; - let method: string | null = null; - let headers: Headers | null = null; - let body: ArrayBufferView | undefined; - let redirected = false; - let remRedirectCount = 20; // TODO: use a better way to handle - - if (typeof input === "string" || input instanceof URL) { - url = typeof input === "string" ? (input as string) : (input as URL).href; - if (init != null) { - method = init.method || null; - if (init.headers) { - headers = init.headers instanceof Headers - ? init.headers - : new Headers(init.headers); - } else { - headers = null; - } - - // ref: https://fetch.spec.whatwg.org/#body-mixin - // Body should have been a mixin - // but we are treating it as a separate class - if (init.body) { - if (!headers) { - headers = new Headers(); - } - let contentType = ""; - if (typeof init.body === "string") { - body = new TextEncoder().encode(init.body); - contentType = "text/plain;charset=UTF-8"; - } else if (isTypedArray(init.body)) { - body = init.body; - } else if (init.body instanceof ArrayBuffer) { - body = new Uint8Array(init.body); - } else if (init.body instanceof URLSearchParams) { - body = new TextEncoder().encode(init.body.toString()); - contentType = "application/x-www-form-urlencoded;charset=UTF-8"; - } else if (init.body instanceof DenoBlob) { - body = init.body[blobBytesSymbol]; - contentType = init.body.type; - } else if (init.body instanceof FormData) { - let boundary; - if (headers.has("content-type")) { - const params = getHeaderValueParams("content-type"); - boundary = params.get("boundary")!; - } - const multipartBuilder = new MultipartBuilder(init.body, boundary); - body = multipartBuilder.getBody(); - contentType = multipartBuilder.getContentType(); - } else { - // TODO: ReadableStream - notImplemented(); - } - if (contentType && !headers.has("content-type")) { - headers.set("content-type", contentType); - } - } - } - } else { - url = input.url; - method = input.method; - headers = input.headers; - - if (input._bodySource) { - body = new DataView(await input.arrayBuffer()); - } - } - - let responseBody; - let responseInit: domTypes.ResponseInit = {}; - while (remRedirectCount) { - const fetchResponse = await sendFetchReq(url, method, headers, body); - - if ( - NULL_BODY_STATUS.includes(fetchResponse.status) || - REDIRECT_STATUS.includes(fetchResponse.status) - ) { - // We won't use body of received response, so close it now - // otherwise it will be kept in resource table. - close(fetchResponse.bodyRid); - responseBody = null; - } else { - responseBody = new ReadableStreamImpl({ - async pull(controller: ReadableStreamDefaultController): Promise<void> { - try { - const b = new Uint8Array(1024 * 32); - const result = await read(fetchResponse.bodyRid, b); - if (result === null) { - controller.close(); - return close(fetchResponse.bodyRid); - } - - controller.enqueue(b.subarray(0, result)); - } catch (e) { - controller.error(e); - controller.close(); - close(fetchResponse.bodyRid); - } - }, - cancel(): void { - // When reader.cancel() is called - close(fetchResponse.bodyRid); - }, - }); - } - - responseInit = { - status: 200, - statusText: fetchResponse.statusText, - headers: fetchResponse.headers, - }; - - responseData.set(responseInit, { - redirected, - rid: fetchResponse.bodyRid, - status: fetchResponse.status, - url, - }); - - const response = new Response(responseBody, responseInit); - - if (REDIRECT_STATUS.includes(fetchResponse.status)) { - // We're in a redirect status - switch ((init && init.redirect) || "follow") { - case "error": - responseInit = {}; - responseData.set(responseInit, { - type: "error", - redirected: false, - url: "", - }); - return new Response(null, responseInit); - case "manual": - responseInit = {}; - responseData.set(responseInit, { - type: "opaqueredirect", - redirected: false, - url: "", - }); - return new Response(null, responseInit); - case "follow": - default: - let redirectUrl = response.headers.get("Location"); - if (redirectUrl == null) { - return response; // Unspecified - } - if ( - !redirectUrl.startsWith("http://") && - !redirectUrl.startsWith("https://") - ) { - redirectUrl = new URL(redirectUrl, url).href; - } - url = redirectUrl; - redirected = true; - remRedirectCount--; - } - } else { - return response; - } - } - - responseData.set(responseInit, { - type: "error", - redirected: false, - url: "", - }); - - return new Response(null, responseInit); -} |