// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // @ts-check /// /// /// /// /// /// /// /// "use strict"; ((window) => { const { isProxy } = Deno.core; const webidl = window.__bootstrap.webidl; const consoleInternal = window.__bootstrap.console; const { HTTP_TAB_OR_SPACE, regexMatcher } = window.__bootstrap.infra; const { 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 { ArrayPrototypeMap, ArrayPrototypePush, MapPrototypeHas, MapPrototypeGet, MapPrototypeSet, RangeError, RegExp, RegExpPrototypeTest, Symbol, SymbolFor, TypeError, } = window.__bootstrap.primordials; 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 | typeof __window.bootstrap.fetchBody.InnerBody} body * @property {boolean} aborted * @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 = [ ...ArrayPrototypeMap(response.headerList, (x) => [x[0], x[1]]), ]; let body = null; if (response.body !== null) { body = response.body.clone(); } return { type: response.type, body, headerList, urlList, status: response.status, statusMessage: response.statusMessage, aborted: response.aborted, url() { if (this.urlList.length == 0) return null; return this.urlList[this.urlList.length - 1]; }, }; } /** * @returns {InnerResponse} */ function newInnerResponse(status = 200, statusMessage = "") { return { type: "default", body: null, headerList: [], urlList: [], status, statusMessage, aborted: false, url() { if (this.urlList.length == 0) return null; return this.urlList[this.urlList.length - 1]; }, }; } /** * @param {string} error * @returns {InnerResponse} */ function networkError(error) { const resp = newInnerResponse(0); resp.type = "error"; resp.error = error; return resp; } /** * @returns {InnerResponse} */ function abortedNetworkError() { const resp = networkError("aborted"); resp.aborted = true; return resp; } class Response { get [_mimeType]() { let charset = null; let essence = null; let mimeType = null; const headerList = headerListFromHeaders(this[_headers]); const values = getDecodeSplitHeader(headerList, "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 = MapPrototypeGet(mimeType.parameters, "charset"); if (newCharset !== undefined) { charset = newCharset; } essence = mimesniff.essence(mimeType); } else { if ( MapPrototypeHas(mimeType.parameters, "charset") === null && charset !== null ) { MapPrototypeSet(mimeType.parameters, "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"; ArrayPrototypePush(inner.headerList, ["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_DOMString?"](body, { prefix, context: "Argument 1", }); init = webidl.converters["ResponseInit_fast"](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 ( init.statusText && !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) ) { throw new TypeError("Status text is not valid."); } this[webidl.brand] = webidl.brand; const response = newInnerResponse(init.status, init.statusText); /** @type {InnerResponse} */ this[_response] = response; /** @type {Headers} */ this[_headers] = headersFromHeaderList(response.headerList, "response"); if (init.headers) { 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; } [SymbolFor("Deno.customInspect")](inspect) { return inspect(consoleInternal.createFilteredInspectProxy({ object: this, evaluate: this instanceof Response, keys: [ "body", "bodyUsed", "headers", "ok", "redirected", "status", "statusText", "url", ], })); } } mixinBody(Response, _body, _mimeType); webidl.configurePrototype(Response); 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"], }], ); webidl.converters["ResponseInit_fast"] = function (init, opts) { if (init === undefined || init === null) { return { status: 200, statusText: "", headers: undefined }; } // Fast path, if not a proxy if (typeof init === "object" && !isProxy(init)) { // Not a proxy fast path const status = init.status !== undefined ? webidl.converters["unsigned short"](init.status) : 200; const statusText = init.statusText !== undefined ? webidl.converters["ByteString"](init.statusText) : ""; const headers = init.headers !== undefined ? webidl.converters["HeadersInit"](init.headers) : undefined; return { status, statusText, headers }; } // Slow default path return webidl.converters["ResponseInit"](init, opts); }; /** * @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.newInnerResponse = newInnerResponse; 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; window.__bootstrap.fetch.abortedNetworkError = abortedNetworkError; })(globalThis);