summaryrefslogtreecommitdiff
path: root/op_crates/fetch/23_response.js
diff options
context:
space:
mode:
Diffstat (limited to 'op_crates/fetch/23_response.js')
-rw-r--r--op_crates/fetch/23_response.js415
1 files changed, 415 insertions, 0 deletions
diff --git a/op_crates/fetch/23_response.js b/op_crates/fetch/23_response.js
new file mode 100644
index 000000000..44b74f789
--- /dev/null
+++ b/op_crates/fetch/23_response.js
@@ -0,0 +1,415 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+// @ts-check
+/// <reference path="../webidl/internal.d.ts" />
+/// <reference path="../web/internal.d.ts" />
+/// <reference path="../url/internal.d.ts" />
+/// <reference path="../file/internal.d.ts" />
+/// <reference path="../file/lib.deno_file.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./11_streams_types.d.ts" />
+/// <reference path="./lib.deno_fetch.d.ts" />
+/// <reference lib="esnext" />
+"use strict";
+
+((window) => {
+ const webidl = window.__bootstrap.webidl;
+ const { HTTP_TAB_OR_SPACE, regexMatcher } = window.__bootstrap.infra;
+ const { InnerBody, extractBody, mixinBody } = window.__bootstrap.fetchBody;
+ const { getLocationHref } = window.__bootstrap.location;
+ const mimesniff = window.__bootstrap.mimesniff;
+ const { URL } = window.__bootstrap.url;
+ const {
+ getDecodeSplitHeader,
+ headerListFromHeaders,
+ headersFromHeaderList,
+ guardFromHeaders,
+ fillHeaders,
+ } = window.__bootstrap.headers;
+
+ const VCHAR = ["\x21-\x7E"];
+ const OBS_TEXT = ["\x80-\xFF"];
+
+ const REASON_PHRASE = [...HTTP_TAB_OR_SPACE, ...VCHAR, ...OBS_TEXT];
+ const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE);
+ const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`);
+
+ const _response = Symbol("response");
+ const _headers = Symbol("headers");
+ const _mimeType = Symbol("mime type");
+ const _body = Symbol("body");
+
+ /**
+ * @typedef InnerResponse
+ * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type
+ * @property {() => string | null} url
+ * @property {string[]} urlList
+ * @property {number} status
+ * @property {string} statusMessage
+ * @property {[string, string][]} headerList
+ * @property {null | InnerBody} body
+ * @property {string} [error]
+ */
+
+ /**
+ * @param {number} status
+ * @returns {boolean}
+ */
+ function nullBodyStatus(status) {
+ return status === 101 || status === 204 || status === 205 || status === 304;
+ }
+
+ /**
+ * @param {number} status
+ * @returns {boolean}
+ */
+ function redirectStatus(status) {
+ return status === 301 || status === 302 || status === 303 ||
+ status === 307 || status === 308;
+ }
+
+ /**
+ * https://fetch.spec.whatwg.org/#concept-response-clone
+ * @param {InnerResponse} response
+ * @returns {InnerResponse}
+ */
+ function cloneInnerResponse(response) {
+ const urlList = [...response.urlList];
+ const headerList = [...response.headerList.map((x) => [x[0], x[1]])];
+ let body = null;
+ if (response.body !== null) {
+ body = response.body.clone();
+ }
+
+ return {
+ type: response.type,
+ body,
+ headerList,
+ url() {
+ if (this.urlList.length == 0) return null;
+ return this.urlList[this.urlList.length - 1];
+ },
+ urlList,
+ status: response.status,
+ statusMessage: response.statusMessage,
+ };
+ }
+
+ const defaultInnerResponse = {
+ type: "default",
+ body: null,
+ url() {
+ if (this.urlList.length == 0) return null;
+ return this.urlList[this.urlList.length - 1];
+ },
+ };
+
+ /**
+ * @returns {InnerResponse}
+ */
+ function newInnerResponse(status = 200, statusMessage = "") {
+ return {
+ headerList: [],
+ urlList: [],
+ status,
+ statusMessage,
+ ...defaultInnerResponse,
+ };
+ }
+
+ /**
+ * @param {string} error
+ * @returns {InnerResponse}
+ */
+ function networkError(error) {
+ const resp = newInnerResponse(0);
+ resp.type = "error";
+ resp.error = error;
+ return resp;
+ }
+
+ class Response {
+ /** @type {InnerResponse} */
+ [_response];
+ /** @type {Headers} */
+ [_headers];
+ get [_mimeType]() {
+ let charset = null;
+ let essence = null;
+ let mimeType = null;
+ const values = getDecodeSplitHeader(
+ headerListFromHeaders(this[_headers]),
+ "Content-Type",
+ );
+ if (values === null) return null;
+ for (const value of values) {
+ const temporaryMimeType = mimesniff.parseMimeType(value);
+ if (
+ temporaryMimeType === null ||
+ mimesniff.essence(temporaryMimeType) == "*/*"
+ ) {
+ continue;
+ }
+ mimeType = temporaryMimeType;
+ if (mimesniff.essence(mimeType) !== essence) {
+ charset = null;
+ const newCharset = mimeType.parameters.get("charset");
+ if (newCharset !== undefined) {
+ charset = newCharset;
+ }
+ essence = mimesniff.essence(mimeType);
+ } else {
+ if (mimeType.parameters.has("charset") === null && charset !== null) {
+ mimeType.parameters.set("charset", charset);
+ }
+ }
+ }
+ if (mimeType === null) return null;
+ return mimeType;
+ }
+ get [_body]() {
+ return this[_response].body;
+ }
+
+ /**
+ * @returns {Response}
+ */
+ static error() {
+ const inner = newInnerResponse(0);
+ inner.type = "error";
+ const response = webidl.createBranded(Response);
+ response[_response] = inner;
+ response[_headers] = headersFromHeaderList(
+ response[_response].headerList,
+ "immutable",
+ );
+ return response;
+ }
+
+ /**
+ * @param {string} url
+ * @param {number} status
+ * @returns {Response}
+ */
+ static redirect(url, status = 302) {
+ const prefix = "Failed to call 'Response.redirect'";
+ url = webidl.converters["USVString"](url, {
+ prefix,
+ context: "Argument 1",
+ });
+ status = webidl.converters["unsigned short"](status, {
+ prefix,
+ context: "Argument 2",
+ });
+
+ const baseURL = getLocationHref();
+ const parsedURL = new URL(url, baseURL);
+ if (!redirectStatus(status)) {
+ throw new RangeError("Invalid redirect status code.");
+ }
+ const inner = newInnerResponse(status);
+ inner.type = "default";
+ inner.headerList.push(["Location", parsedURL.href]);
+ const response = webidl.createBranded(Response);
+ response[_response] = inner;
+ response[_headers] = headersFromHeaderList(
+ response[_response].headerList,
+ "immutable",
+ );
+ return response;
+ }
+
+ /**
+ * @param {BodyInit | null} body
+ * @param {ResponseInit} init
+ */
+ constructor(body = null, init = {}) {
+ const prefix = "Failed to construct 'Response'";
+ body = webidl.converters["BodyInit?"](body, {
+ prefix,
+ context: "Argument 1",
+ });
+ init = webidl.converters["ResponseInit"](init, {
+ prefix,
+ context: "Argument 2",
+ });
+
+ if (init.status < 200 || init.status > 599) {
+ throw new RangeError(
+ `The status provided (${init.status}) is outside the range [200, 599].`,
+ );
+ }
+
+ if (!REASON_PHRASE_RE.test(init.statusText)) {
+ throw new TypeError("Status text is not valid.");
+ }
+
+ this[webidl.brand] = webidl.brand;
+ const response = newInnerResponse(init.status, init.statusText);
+ this[_response] = response;
+ this[_headers] = headersFromHeaderList(response.headerList, "response");
+ if (init.headers !== undefined) {
+ fillHeaders(this[_headers], init.headers);
+ }
+ if (body !== null) {
+ if (nullBodyStatus(response.status)) {
+ throw new TypeError(
+ "Response with null body status cannot have body",
+ );
+ }
+ const res = extractBody(body);
+ response.body = res.body;
+ if (res.contentType !== null && !this[_headers].has("content-type")) {
+ this[_headers].append("Content-Type", res.contentType);
+ }
+ }
+ }
+
+ /**
+ * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"}
+ */
+ get type() {
+ webidl.assertBranded(this, Response);
+ return this[_response].type;
+ }
+
+ /**
+ * @returns {string}
+ */
+ get url() {
+ webidl.assertBranded(this, Response);
+ const url = this[_response].url();
+ if (url === null) return "";
+ const newUrl = new URL(url);
+ newUrl.hash = "";
+ return newUrl.href;
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ get redirected() {
+ webidl.assertBranded(this, Response);
+ return this[_response].urlList.length > 1;
+ }
+
+ /**
+ * @returns {number}
+ */
+ get status() {
+ webidl.assertBranded(this, Response);
+ return this[_response].status;
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ get ok() {
+ webidl.assertBranded(this, Response);
+ const status = this[_response].status;
+ return status >= 200 && status <= 299;
+ }
+
+ /**
+ * @returns {string}
+ */
+ get statusText() {
+ webidl.assertBranded(this, Response);
+ return this[_response].statusMessage;
+ }
+
+ /**
+ * @returns {Headers}
+ */
+ get headers() {
+ webidl.assertBranded(this, Response);
+ return this[_headers];
+ }
+
+ /**
+ * @returns {Response}
+ */
+ clone() {
+ webidl.assertBranded(this, Response);
+ if (this[_body] && this[_body].unusable()) {
+ throw new TypeError("Body is unusable.");
+ }
+ const second = webidl.createBranded(Response);
+ const newRes = cloneInnerResponse(this[_response]);
+ second[_response] = newRes;
+ second[_headers] = headersFromHeaderList(
+ newRes.headerList,
+ guardFromHeaders(this[_headers]),
+ );
+ return second;
+ }
+
+ get [Symbol.toStringTag]() {
+ return "Response";
+ }
+
+ [Symbol.for("Deno.customInspect")](inspect) {
+ const inner = {
+ body: this.body,
+ bodyUsed: this.bodyUsed,
+ headers: this.headers,
+ ok: this.ok,
+ redirected: this.redirected,
+ status: this.status,
+ statusText: this.statusText,
+ url: this.url(),
+ };
+ return `Response ${inspect(inner)}`;
+ }
+ }
+
+ mixinBody(Response, _body, _mimeType);
+
+ webidl.converters["Response"] = webidl.createInterfaceConverter(
+ "Response",
+ Response,
+ );
+ webidl.converters["ResponseInit"] = webidl.createDictionaryConverter(
+ "ResponseInit",
+ [{
+ key: "status",
+ defaultValue: 200,
+ converter: webidl.converters["unsigned short"],
+ }, {
+ key: "statusText",
+ defaultValue: "",
+ converter: webidl.converters["ByteString"],
+ }, {
+ key: "headers",
+ converter: webidl.converters["HeadersInit"],
+ }],
+ );
+
+ /**
+ * @param {Response} response
+ * @returns {InnerResponse}
+ */
+ function toInnerResponse(response) {
+ return response[_response];
+ }
+
+ /**
+ * @param {InnerResponse} inner
+ * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard
+ * @returns {Response}
+ */
+ function fromInnerResponse(inner, guard) {
+ const response = webidl.createBranded(Response);
+ response[_response] = inner;
+ response[_headers] = headersFromHeaderList(inner.headerList, guard);
+ return response;
+ }
+
+ window.__bootstrap.fetch ??= {};
+ window.__bootstrap.fetch.Response = Response;
+ window.__bootstrap.fetch.toInnerResponse = toInnerResponse;
+ window.__bootstrap.fetch.fromInnerResponse = fromInnerResponse;
+ window.__bootstrap.fetch.redirectStatus = redirectStatus;
+ window.__bootstrap.fetch.nullBodyStatus = nullBodyStatus;
+ window.__bootstrap.fetch.networkError = networkError;
+})(globalThis);