diff options
Diffstat (limited to 'extensions/fetch')
-rw-r--r-- | extensions/fetch/01_fetch_util.js | 22 | ||||
-rw-r--r-- | extensions/fetch/20_headers.js | 479 | ||||
-rw-r--r-- | extensions/fetch/21_formdata.js | 507 | ||||
-rw-r--r-- | extensions/fetch/22_body.js | 403 | ||||
-rw-r--r-- | extensions/fetch/22_http_client.js | 40 | ||||
-rw-r--r-- | extensions/fetch/23_request.js | 484 | ||||
-rw-r--r-- | extensions/fetch/23_response.js | 451 | ||||
-rw-r--r-- | extensions/fetch/26_fetch.js | 542 | ||||
-rw-r--r-- | extensions/fetch/Cargo.toml | 28 | ||||
-rw-r--r-- | extensions/fetch/README.md | 5 | ||||
-rw-r--r-- | extensions/fetch/internal.d.ts | 108 | ||||
-rw-r--r-- | extensions/fetch/lib.deno_fetch.d.ts | 437 | ||||
-rw-r--r-- | extensions/fetch/lib.rs | 567 |
13 files changed, 0 insertions, 4073 deletions
diff --git a/extensions/fetch/01_fetch_util.js b/extensions/fetch/01_fetch_util.js deleted file mode 100644 index 9cf19588b..000000000 --- a/extensions/fetch/01_fetch_util.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { TypeError } = window.__bootstrap.primordials; - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - window.__bootstrap.fetchUtil = { - requiredArguments, - }; -})(this); diff --git a/extensions/fetch/20_headers.js b/extensions/fetch/20_headers.js deleted file mode 100644 index 91154d958..000000000 --- a/extensions/fetch/20_headers.js +++ /dev/null @@ -1,479 +0,0 @@ -// 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="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_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_PREFIX_RE, - HTTP_TAB_OR_SPACE_SUFFIX_RE, - HTTP_WHITESPACE_PREFIX_RE, - HTTP_WHITESPACE_SUFFIX_RE, - HTTP_TOKEN_CODE_POINT_RE, - byteLowerCase, - collectSequenceOfCodepoints, - collectHttpQuotedString, - } = window.__bootstrap.infra; - const { - ArrayIsArray, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSort, - ArrayPrototypeJoin, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ObjectKeys, - ObjectEntries, - RegExpPrototypeTest, - Symbol, - SymbolFor, - SymbolIterator, - SymbolToStringTag, - StringPrototypeReplaceAll, - StringPrototypeIncludes, - TypeError, - } = window.__bootstrap.primordials; - - const _headerList = Symbol("header list"); - const _iterableHeaders = Symbol("iterable headers"); - const _guard = Symbol("guard"); - - /** - * @typedef Header - * @type {[string, string]} - */ - - /** - * @typedef HeaderList - * @type {Header[]} - */ - - /** - * @param {string} potentialValue - * @returns {string} - */ - function normalizeHeaderValue(potentialValue) { - potentialValue = StringPrototypeReplaceAll( - potentialValue, - HTTP_WHITESPACE_PREFIX_RE, - "", - ); - potentialValue = StringPrototypeReplaceAll( - potentialValue, - HTTP_WHITESPACE_SUFFIX_RE, - "", - ); - return potentialValue; - } - - /** - * @param {Headers} headers - * @param {HeadersInit} object - */ - function fillHeaders(headers, object) { - if (ArrayIsArray(object)) { - for (const header of object) { - if (header.length !== 2) { - throw new TypeError( - `Invalid header. Length must be 2, but is ${header.length}`, - ); - } - appendHeader(headers, header[0], header[1]); - } - } else { - for (const key of ObjectKeys(object)) { - appendHeader(headers, key, object[key]); - } - } - } - - /** - * https://fetch.spec.whatwg.org/#concept-headers-append - * @param {Headers} headers - * @param {string} name - * @param {string} value - */ - function appendHeader(headers, name, value) { - // 1. - value = normalizeHeaderValue(value); - - // 2. - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if ( - StringPrototypeIncludes(value, "\x00") || - StringPrototypeIncludes(value, "\x0A") || - StringPrototypeIncludes(value, "\x0D") - ) { - throw new TypeError("Header value is not valid."); - } - - // 3. - if (headers[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - // 7. - const list = headers[_headerList]; - name = byteLowerCase(name); - ArrayPrototypePush(list, [name, value]); - } - - /** - * https://fetch.spec.whatwg.org/#concept-header-list-get - * @param {HeaderList} list - * @param {string} name - */ - function getHeader(list, name) { - const lowercaseName = byteLowerCase(name); - const entries = ArrayPrototypeMap( - ArrayPrototypeFilter(list, (entry) => entry[0] === lowercaseName), - (entry) => entry[1], - ); - if (entries.length === 0) { - return null; - } else { - return ArrayPrototypeJoin(entries, "\x2C\x20"); - } - } - - /** - * https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split - * @param {HeaderList} list - * @param {string} name - * @returns {string[] | null} - */ - function getDecodeSplitHeader(list, name) { - const initialValue = getHeader(list, name); - if (initialValue === null) return null; - const input = initialValue; - let position = 0; - const values = []; - let value = ""; - while (position < initialValue.length) { - // 7.1. collect up to " or , - const res = collectSequenceOfCodepoints( - initialValue, - position, - (c) => c !== "\u0022" && c !== "\u002C", - ); - value += res.result; - position = res.position; - - if (position < initialValue.length) { - if (input[position] === "\u0022") { - const res = collectHttpQuotedString(input, position, false); - value += res.result; - position = res.position; - if (position < initialValue.length) { - continue; - } - } else { - if (input[position] !== "\u002C") throw new TypeError("Unreachable"); - position += 1; - } - } - - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, ""); - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, ""); - - ArrayPrototypePush(values, value); - value = ""; - } - return values; - } - - class Headers { - /** @type {HeaderList} */ - [_headerList] = []; - /** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */ - [_guard]; - - get [_iterableHeaders]() { - const list = this[_headerList]; - - // The order of steps are not similar to the ones suggested by the - // spec but produce the same result. - const headers = {}; - const cookies = []; - for (const entry of list) { - const name = entry[0]; - const value = entry[1]; - if (value === null) throw new TypeError("Unreachable"); - // The following if statement is not spec compliant. - // `set-cookie` is the only header that can not be concatentated, - // so must be given to the user as multiple headers. - // The else block of the if statement is spec compliant again. - if (name === "set-cookie") { - ArrayPrototypePush(cookies, [name, value]); - } else { - // The following code has the same behaviour as getHeader() - // at the end of loop. But it avoids looping through the entire - // list to combine multiple values with same header name. It - // instead gradually combines them as they are found. - let header = headers[name]; - if (header && header.length > 0) { - header += "\x2C\x20" + value; - } else { - header = value; - } - headers[name] = header; - } - } - - return ArrayPrototypeSort( - [...ObjectEntries(headers), ...cookies], - (a, b) => { - const akey = a[0]; - const bkey = b[0]; - if (akey > bkey) return 1; - if (akey < bkey) return -1; - return 0; - }, - ); - } - - /** @param {HeadersInit} [init] */ - constructor(init = undefined) { - const prefix = "Failed to construct 'Event'"; - if (init !== undefined) { - init = webidl.converters["HeadersInit"](init, { - prefix, - context: "Argument 1", - }); - } - - this[webidl.brand] = webidl.brand; - this[_guard] = "none"; - if (init !== undefined) { - fillHeaders(this, init); - } - } - - /** - * @param {string} name - * @param {string} value - */ - append(name, value) { - webidl.assertBranded(this, Headers); - const prefix = "Failed to execute 'append' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - appendHeader(this, name, value); - } - - /** - * @param {string} name - */ - delete(name) { - const prefix = "Failed to execute 'delete' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - name = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (list[i][0] === name) { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - - /** - * @param {string} name - */ - get(name) { - const prefix = "Failed to execute 'get' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - return getHeader(list, name); - } - - /** - * @param {string} name - */ - has(name) { - const prefix = "Failed to execute 'has' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - name = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (list[i][0] === name) { - return true; - } - } - return false; - } - - /** - * @param {string} name - * @param {string} value - */ - set(name, value) { - webidl.assertBranded(this, Headers); - const prefix = "Failed to execute 'set' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - - value = normalizeHeaderValue(value); - - // 2. - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if ( - StringPrototypeIncludes(value, "\x00") || - StringPrototypeIncludes(value, "\x0A") || - StringPrototypeIncludes(value, "\x0D") - ) { - throw new TypeError("Header value is not valid."); - } - - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - name = byteLowerCase(name); - let added = false; - for (let i = 0; i < list.length; i++) { - if (list[i][0] === name) { - if (!added) { - list[i][1] = value; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - if (!added) { - ArrayPrototypePush(list, [name, value]); - } - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - const headers = {}; - for (const header of this) { - headers[header[0]] = header[1]; - } - return `Headers ${inspect(headers)}`; - } - - get [SymbolToStringTag]() { - return "Headers"; - } - } - - webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); - - webidl.configurePrototype(Headers); - - webidl.converters["HeadersInit"] = (V, opts) => { - // Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence<sequence<ByteString>>"](V, opts); - } - return webidl.converters["record<ByteString, ByteString>"](V, opts); - } - throw webidl.makeException( - TypeError, - "The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'", - opts, - ); - }; - webidl.converters["Headers"] = webidl.createInterfaceConverter( - "Headers", - Headers, - ); - - /** - * @param {HeaderList} list - * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard - * @returns {Headers} - */ - function headersFromHeaderList(list, guard) { - const headers = webidl.createBranded(Headers); - headers[_headerList] = list; - headers[_guard] = guard; - return headers; - } - - /** - * @param {Headers} - * @returns {HeaderList} - */ - function headerListFromHeaders(headers) { - return headers[_headerList]; - } - - /** - * @param {Headers} - * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} - */ - function guardFromHeaders(headers) { - return headers[_guard]; - } - - window.__bootstrap.headers = { - Headers, - headersFromHeaderList, - headerListFromHeaders, - fillHeaders, - getDecodeSplitHeader, - guardFromHeaders, - }; -})(this); diff --git a/extensions/fetch/21_formdata.js b/extensions/fetch/21_formdata.js deleted file mode 100644 index 25ed32c2d..000000000 --- a/extensions/fetch/21_formdata.js +++ /dev/null @@ -1,507 +0,0 @@ -// 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="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { Blob, File } = globalThis.__bootstrap.file; - const { - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ArrayPrototypeForEach, - Map, - MapPrototypeGet, - MapPrototypeSet, - MathRandom, - Symbol, - SymbolToStringTag, - StringFromCharCode, - StringPrototypeTrim, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeReplace, - StringPrototypeIndexOf, - StringPrototypePadStart, - StringPrototypeCodePointAt, - StringPrototypeReplaceAll, - TypeError, - TypedArrayPrototypeSubarray, - } = window.__bootstrap.primordials; - - const entryList = Symbol("entry list"); - - /** - * @param {string} name - * @param {string | Blob} value - * @param {string | undefined} filename - * @returns {FormDataEntry} - */ - function createEntry(name, value, filename) { - if (value instanceof Blob && !(value instanceof File)) { - value = new File([value], "blob", { type: value.type }); - } - if (value instanceof File && filename !== undefined) { - value = new File([value], filename, { - type: value.type, - lastModified: value.lastModified, - }); - } - return { - name, - // @ts-expect-error because TS is not smart enough - value, - }; - } - - /** - * @typedef FormDataEntry - * @property {string} name - * @property {FormDataEntryValue} value - */ - - class FormData { - get [SymbolToStringTag]() { - return "FormData"; - } - - /** @type {FormDataEntry[]} */ - [entryList] = []; - - /** @param {void} form */ - constructor(form) { - if (form !== undefined) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; - } - - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - append(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'append' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - if (valueOrBlobValue instanceof Blob) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - } - - const entry = createEntry(name, valueOrBlobValue, filename); - - ArrayPrototypePush(this[entryList], entry); - } - - /** - * @param {string} name - * @returns {void} - */ - delete(name) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'name' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - const list = this[entryList]; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - - /** - * @param {string} name - * @returns {FormDataEntryValue | null} - */ - get(name) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'get' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - for (const entry of this[entryList]) { - if (entry.name === name) return entry.value; - } - return null; - } - - /** - * @param {string} name - * @returns {FormDataEntryValue[]} - */ - getAll(name) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'getAll' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - const returnList = []; - for (const entry of this[entryList]) { - if (entry.name === name) ArrayPrototypePush(returnList, entry.value); - } - return returnList; - } - - /** - * @param {string} name - * @returns {boolean} - */ - has(name) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'has' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - - for (const entry of this[entryList]) { - if (entry.name === name) return true; - } - return false; - } - - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - set(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormData); - const prefix = "Failed to execute 'set' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); - if (valueOrBlobValue instanceof Blob) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - } - - const entry = createEntry(name, valueOrBlobValue, filename); - - const list = this[entryList]; - let added = false; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { - if (!added) { - list[i] = entry; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - if (!added) { - ArrayPrototypePush(list, entry); - } - } - } - - webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); - - webidl.configurePrototype(FormData); - - const escape = (str, isFilename) => - StringPrototypeReplace( - StringPrototypeReplace( - StringPrototypeReplace( - (isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n")), - /\n/g, - "%0A", - ), - /\r/g, - "%0D", - ), - /"/g, - "%22", - ); - - /** - * convert FormData to a Blob synchronous without reading all of the files - * @param {globalThis.FormData} formData - */ - function formDataToBlob(formData) { - const boundary = StringPrototypePadStart( - StringPrototypeSlice( - StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""), - -28, - ), - 32, - "-", - ); - const chunks = []; - const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`; - - for (const [name, value] of formData) { - if (typeof value === "string") { - ArrayPrototypePush( - chunks, - prefix + escape(name) + '"' + CRLF + CRLF + - StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF, - ); - } else { - ArrayPrototypePush( - chunks, - prefix + escape(name) + `"; filename="${escape(value.name, true)}"` + - CRLF + - `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`, - value, - CRLF, - ); - } - } - - ArrayPrototypePush(chunks, `--${boundary}--`); - - return new Blob(chunks, { - type: "multipart/form-data; boundary=" + boundary, - }); - } - - /** - * @param {string} value - * @returns {Map<string, string>} - */ - function parseContentDisposition(value) { - /** @type {Map<string, string>} */ - const params = new Map(); - // Forced to do so for some Map constructor param mismatch - ArrayPrototypeForEach( - ArrayPrototypeMap( - ArrayPrototypeFilter( - ArrayPrototypeMap( - ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1), - (s) => StringPrototypeSplit(StringPrototypeTrim(s), "="), - ), - (arr) => arr.length > 1, - ), - ([k, v]) => [k, StringPrototypeReplace(v, /^"([^"]*)"$/, "$1")], - ), - ([k, v]) => MapPrototypeSet(params, k, v), - ); - - return params; - } - - const CRLF = "\r\n"; - const LF = StringPrototypeCodePointAt(CRLF, 1); - const CR = StringPrototypeCodePointAt(CRLF, 0); - - class MultipartParser { - /** - * @param {Uint8Array} body - * @param {string | undefined} boundary - */ - constructor(body, boundary) { - if (!boundary) { - throw new TypeError("multipart/form-data must provide a boundary"); - } - - this.boundary = `--${boundary}`; - this.body = body; - this.boundaryChars = core.encode(this.boundary); - } - - /** - * @param {string} headersText - * @returns {{ headers: Headers, disposition: Map<string, string> }} - */ - #parseHeaders(headersText) { - const headers = new Headers(); - const rawHeaders = StringPrototypeSplit(headersText, "\r\n"); - for (const rawHeader of rawHeaders) { - const sepIndex = StringPrototypeIndexOf(rawHeader, ":"); - if (sepIndex < 0) { - continue; // Skip this header - } - const key = StringPrototypeSlice(rawHeader, 0, sepIndex); - const value = StringPrototypeSlice(rawHeader, sepIndex + 1); - headers.set(key, value); - } - - const disposition = parseContentDisposition( - headers.get("Content-Disposition") ?? "", - ); - - return { headers, disposition }; - } - - /** - * @returns {FormData} - */ - parse() { - // Body must be at least 2 boundaries + \r\n + -- on the last boundary. - if (this.body.length < (this.boundary.length * 2) + 4) { - throw new TypeError("Form data too short to be valid."); - } - - const formData = new FormData(); - let headerText = ""; - let boundaryIndex = 0; - let state = 0; - let fileStart = 0; - - for (let i = 0; i < this.body.length; i++) { - const byte = this.body[i]; - const prevByte = this.body[i - 1]; - const isNewLine = byte === LF && prevByte === CR; - - if (state === 1 || state === 2 || state == 3) { - headerText += StringFromCharCode(byte); - } - if (state === 0 && isNewLine) { - state = 1; - } else if (state === 1 && isNewLine) { - state = 2; - const headersDone = this.body[i + 1] === CR && - this.body[i + 2] === LF; - - if (headersDone) { - state = 3; - } - } else if (state === 2 && isNewLine) { - state = 3; - } else if (state === 3 && isNewLine) { - state = 4; - fileStart = i + 1; - } else if (state === 4) { - if (this.boundaryChars[boundaryIndex] !== byte) { - boundaryIndex = 0; - } else { - boundaryIndex++; - } - - if (boundaryIndex >= this.boundary.length) { - const { headers, disposition } = this.#parseHeaders(headerText); - const content = TypedArrayPrototypeSubarray( - this.body, - fileStart, - i - boundaryIndex - 1, - ); - // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata - const filename = MapPrototypeGet(disposition, "filename"); - const name = MapPrototypeGet(disposition, "name"); - - state = 5; - // Reset - boundaryIndex = 0; - headerText = ""; - - if (!name) { - continue; // Skip, unknown name - } - - if (filename) { - const blob = new Blob([content], { - type: headers.get("Content-Type") || "application/octet-stream", - }); - formData.append(name, blob, filename); - } else { - formData.append(name, core.decode(content)); - } - } - } else if (state === 5 && isNewLine) { - state = 1; - } - } - - return formData; - } - } - - /** - * @param {Uint8Array} body - * @param {string | undefined} boundary - * @returns {FormData} - */ - function parseFormData(body, boundary) { - const parser = new MultipartParser(body, boundary); - return parser.parse(); - } - - /** - * @param {FormDataEntry[]} entries - * @returns {FormData} - */ - function formDataFromEntries(entries) { - const fd = new FormData(); - fd[entryList] = entries; - return fd; - } - - webidl.converters["FormData"] = webidl - .createInterfaceConverter("FormData", FormData); - - globalThis.__bootstrap.formData = { - FormData, - formDataToBlob, - parseFormData, - formDataFromEntries, - }; -})(globalThis); diff --git a/extensions/fetch/22_body.js b/extensions/fetch/22_body.js deleted file mode 100644 index 49da149c2..000000000 --- a/extensions/fetch/22_body.js +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// @ts-check -/// <reference path="../webidl/internal.d.ts" /> -/// <reference path="../url/internal.d.ts" /> -/// <reference path="../url/lib.deno_url.d.ts" /> -/// <reference path="../web/internal.d.ts" /> -/// <reference path="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { parseUrlEncoded } = globalThis.__bootstrap.url; - const { parseFormData, formDataFromEntries, formDataToBlob } = - globalThis.__bootstrap.formData; - const mimesniff = globalThis.__bootstrap.mimesniff; - const { isReadableStreamDisturbed, errorReadableStream, createProxy } = - globalThis.__bootstrap.streams; - const { - ArrayBuffer, - ArrayBufferIsView, - ArrayPrototypePush, - ArrayPrototypeMap, - JSONParse, - ObjectDefineProperties, - PromiseResolve, - TypedArrayPrototypeSet, - TypedArrayPrototypeSlice, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - - class InnerBody { - /** @type {ReadableStream<Uint8Array> | { body: Uint8Array, consumed: boolean }} */ - streamOrStatic; - /** @type {null | Uint8Array | Blob | FormData} */ - source = null; - /** @type {null | number} */ - length = null; - - /** - * @param {ReadableStream<Uint8Array> | { body: Uint8Array, consumed: boolean }} stream - */ - constructor(stream) { - this.streamOrStatic = stream ?? - { body: new Uint8Array(), consumed: false }; - } - - get stream() { - if (!(this.streamOrStatic instanceof ReadableStream)) { - const { body, consumed } = this.streamOrStatic; - if (consumed) { - this.streamOrStatic = new ReadableStream(); - this.streamOrStatic.getReader(); - } else { - this.streamOrStatic = new ReadableStream({ - start(controller) { - controller.enqueue(body); - controller.close(); - }, - }); - } - } - return this.streamOrStatic; - } - - /** - * https://fetch.spec.whatwg.org/#body-unusable - * @returns {boolean} - */ - unusable() { - if (this.streamOrStatic instanceof ReadableStream) { - return this.streamOrStatic.locked || - isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; - } - - /** - * @returns {boolean} - */ - consumed() { - if (this.streamOrStatic instanceof ReadableStream) { - return isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; - } - - /** - * https://fetch.spec.whatwg.org/#concept-body-consume-body - * @returns {Promise<Uint8Array>} - */ - async consume() { - if (this.unusable()) throw new TypeError("Body already consumed."); - if (this.streamOrStatic instanceof ReadableStream) { - const reader = this.stream.getReader(); - /** @type {Uint8Array[]} */ - const chunks = []; - let totalLength = 0; - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) break; - ArrayPrototypePush(chunks, chunk); - totalLength += chunk.byteLength; - } - const finalBuffer = new Uint8Array(totalLength); - let i = 0; - for (const chunk of chunks) { - TypedArrayPrototypeSet(finalBuffer, chunk, i); - i += chunk.byteLength; - } - return finalBuffer; - } else { - this.streamOrStatic.consumed = true; - return this.streamOrStatic.body; - } - } - - cancel(error) { - if (this.streamOrStatic instanceof ReadableStream) { - this.streamOrStatic.cancel(error); - } else { - this.streamOrStatic.consumed = true; - } - } - - error(error) { - if (this.streamOrStatic instanceof ReadableStream) { - errorReadableStream(this.streamOrStatic, error); - } else { - this.streamOrStatic.consumed = true; - } - } - - /** - * @returns {InnerBody} - */ - clone() { - const [out1, out2] = this.stream.tee(); - this.streamOrStatic = out1; - const second = new InnerBody(out2); - second.source = core.deserialize(core.serialize(this.source)); - second.length = this.length; - return second; - } - - /** - * @returns {InnerBody} - */ - createProxy() { - let proxyStreamOrStatic; - if (this.streamOrStatic instanceof ReadableStream) { - proxyStreamOrStatic = createProxy(this.streamOrStatic); - } else { - proxyStreamOrStatic = { ...this.streamOrStatic }; - this.streamOrStatic.consumed = true; - } - const proxy = new InnerBody(proxyStreamOrStatic); - proxy.source = this.source; - proxy.length = this.length; - return proxy; - } - } - - /** - * @param {any} prototype - * @param {symbol} bodySymbol - * @param {symbol} mimeTypeSymbol - * @returns {void} - */ - function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { - function consumeBody(object) { - if (object[bodySymbol] !== null) { - return object[bodySymbol].consume(); - } - return PromiseResolve(new Uint8Array()); - } - - /** @type {PropertyDescriptorMap} */ - const mixin = { - body: { - /** - * @returns {ReadableStream<Uint8Array> | null} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] === null) { - return null; - } else { - return this[bodySymbol].stream; - } - }, - configurable: true, - enumerable: true, - }, - bodyUsed: { - /** - * @returns {boolean} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] !== null) { - return this[bodySymbol].consumed(); - } - return false; - }, - configurable: true, - enumerable: true, - }, - arrayBuffer: { - /** @returns {Promise<ArrayBuffer>} */ - value: async function arrayBuffer() { - webidl.assertBranded(this, prototype); - const body = await consumeBody(this); - return packageData(body, "ArrayBuffer"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - blob: { - /** @returns {Promise<Blob>} */ - value: async function blob() { - webidl.assertBranded(this, prototype); - const body = await consumeBody(this); - return packageData(body, "Blob", this[mimeTypeSymbol]); - }, - writable: true, - configurable: true, - enumerable: true, - }, - formData: { - /** @returns {Promise<FormData>} */ - value: async function formData() { - webidl.assertBranded(this, prototype); - const body = await consumeBody(this); - return packageData(body, "FormData", this[mimeTypeSymbol]); - }, - writable: true, - configurable: true, - enumerable: true, - }, - json: { - /** @returns {Promise<any>} */ - value: async function json() { - webidl.assertBranded(this, prototype); - const body = await consumeBody(this); - return packageData(body, "JSON"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - text: { - /** @returns {Promise<string>} */ - value: async function text() { - webidl.assertBranded(this, prototype); - const body = await consumeBody(this); - return packageData(body, "text"); - }, - writable: true, - configurable: true, - enumerable: true, - }, - }; - return ObjectDefineProperties(prototype.prototype, mixin); - } - - /** - * https://fetch.spec.whatwg.org/#concept-body-package-data - * @param {Uint8Array} bytes - * @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type - * @param {MimeType | null} [mimeType] - */ - function packageData(bytes, type, mimeType) { - switch (type) { - case "ArrayBuffer": - return bytes.buffer; - case "Blob": - return new Blob([bytes], { - type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "", - }); - case "FormData": { - if (mimeType !== null) { - const essence = mimesniff.essence(mimeType); - if (essence === "multipart/form-data") { - const boundary = mimeType.parameters.get("boundary"); - if (boundary === null) { - throw new TypeError( - "Missing boundary parameter in mime type of multipart formdata.", - ); - } - return parseFormData(bytes, boundary); - } else if (essence === "application/x-www-form-urlencoded") { - const entries = parseUrlEncoded(bytes); - return formDataFromEntries( - ArrayPrototypeMap( - entries, - (x) => ({ name: x[0], value: x[1] }), - ), - ); - } - throw new TypeError("Body can not be decoded as form data"); - } - throw new TypeError("Missing content type"); - } - case "JSON": - return JSONParse(core.decode(bytes)); - case "text": - return core.decode(bytes); - } - } - - /** - * @param {BodyInit} object - * @returns {{body: InnerBody, contentType: string | null}} - */ - function extractBody(object) { - /** @type {ReadableStream<Uint8Array> | { body: Uint8Array, consumed: boolean }} */ - let stream; - let source = null; - let length = null; - let contentType = null; - if (object instanceof Blob) { - stream = object.stream(); - source = object; - length = object.size; - if (object.type.length !== 0) { - contentType = object.type; - } - } else if (ArrayBufferIsView(object) || object instanceof ArrayBuffer) { - const u8 = ArrayBufferIsView(object) - ? new Uint8Array( - object.buffer, - object.byteOffset, - object.byteLength, - ) - : new Uint8Array(object); - const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength); - source = copy; - } else if (object instanceof FormData) { - const res = formDataToBlob(object); - stream = res.stream(); - source = res; - length = res.size; - contentType = res.type; - } else if (object instanceof URLSearchParams) { - // TODO(@satyarohith): not sure what primordial here. - source = core.encode(object.toString()); - contentType = "application/x-www-form-urlencoded;charset=UTF-8"; - } else if (typeof object === "string") { - source = core.encode(object); - contentType = "text/plain;charset=UTF-8"; - } else if (object instanceof ReadableStream) { - stream = object; - if (object.locked || isReadableStreamDisturbed(object)) { - throw new TypeError("ReadableStream is locked or disturbed"); - } - } - if (source instanceof Uint8Array) { - stream = { body: source, consumed: false }; - length = source.byteLength; - } - const body = new InnerBody(stream); - body.source = source; - body.length = length; - return { body, contentType }; - } - - webidl.converters["BodyInit"] = (V, opts) => { - // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) - if (V instanceof ReadableStream) { - // TODO(lucacasonato): ReadableStream is not branded - return V; - } else if (V instanceof Blob) { - return webidl.converters["Blob"](V, opts); - } else if (V instanceof FormData) { - return webidl.converters["FormData"](V, opts); - } else if (V instanceof URLSearchParams) { - // TODO(lucacasonato): URLSearchParams is not branded - return V; - } - if (typeof V === "object") { - if (V instanceof ArrayBuffer || V instanceof SharedArrayBuffer) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } - } - return webidl.converters["USVString"](V, opts); - }; - webidl.converters["BodyInit?"] = webidl.createNullableConverter( - webidl.converters["BodyInit"], - ); - - window.__bootstrap.fetchBody = { mixinBody, InnerBody, extractBody }; -})(globalThis); diff --git a/extensions/fetch/22_http_client.js b/extensions/fetch/22_http_client.js deleted file mode 100644 index 60b069aa7..000000000 --- a/extensions/fetch/22_http_client.js +++ /dev/null @@ -1,40 +0,0 @@ -// 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="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - const core = window.Deno.core; - - /** - * @param {Deno.CreateHttpClientOptions} options - * @returns {HttpClient} - */ - function createHttpClient(options) { - return new HttpClient(core.opSync("op_create_http_client", options)); - } - - class HttpClient { - /** - * @param {number} rid - */ - constructor(rid) { - this.rid = rid; - } - close() { - core.close(this.rid); - } - } - - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.createHttpClient = createHttpClient; - window.__bootstrap.fetch.HttpClient = HttpClient; -})(globalThis); diff --git a/extensions/fetch/23_request.js b/extensions/fetch/23_request.js deleted file mode 100644 index 1372125c1..000000000 --- a/extensions/fetch/23_request.js +++ /dev/null @@ -1,484 +0,0 @@ -// 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="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - const { HTTP_TOKEN_CODE_POINT_RE, byteUpperCase } = window.__bootstrap.infra; - const { URL } = window.__bootstrap.url; - const { guardFromHeaders } = window.__bootstrap.headers; - const { mixinBody, extractBody } = window.__bootstrap.fetchBody; - const { getLocationHref } = window.__bootstrap.location; - const mimesniff = window.__bootstrap.mimesniff; - const { - headersFromHeaderList, - headerListFromHeaders, - fillHeaders, - getDecodeSplitHeader, - } = window.__bootstrap.headers; - const { HttpClient } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { - ArrayPrototypeMap, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - MapPrototypeHas, - MapPrototypeGet, - MapPrototypeSet, - ObjectKeys, - RegExpPrototypeTest, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; - - const _request = Symbol("request"); - const _headers = Symbol("headers"); - const _signal = Symbol("signal"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); - - /** - * @typedef InnerRequest - * @property {string} method - * @property {() => string} url - * @property {() => string} currentUrl - * @property {[string, string][]} headerList - * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body - * @property {"follow" | "error" | "manual"} redirectMode - * @property {number} redirectCount - * @property {string[]} urlList - * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. - */ - - const defaultInnerRequest = { - url() { - return this.urlList[0]; - }, - currentUrl() { - return this.urlList[this.urlList.length - 1]; - }, - redirectMode: "follow", - redirectCount: 0, - clientRid: null, - }; - - /** - * @param {string} method - * @param {string} url - * @param {[string, string][]} headerList - * @param {typeof __window.bootstrap.fetchBody.InnerBody} body - * @returns - */ - function newInnerRequest(method, url, headerList = [], body = null) { - return { - method: method, - headerList, - body, - urlList: [url], - ...defaultInnerRequest, - }; - } - - /** - * https://fetch.spec.whatwg.org/#concept-request-clone - * @param {InnerRequest} request - * @returns {InnerRequest} - */ - function cloneInnerRequest(request) { - const headerList = [ - ...ArrayPrototypeMap(request.headerList, (x) => [x[0], x[1]]), - ]; - let body = null; - if (request.body !== null) { - body = request.body.clone(); - } - - return { - method: request.method, - url() { - return this.urlList[0]; - }, - currentUrl() { - return this.urlList[this.urlList.length - 1]; - }, - headerList, - body, - redirectMode: request.redirectMode, - redirectCount: request.redirectCount, - urlList: request.urlList, - clientRid: request.clientRid, - }; - } - - /** - * @param {string} m - * @returns {boolean} - */ - function isKnownMethod(m) { - return ( - m === "DELETE" || - m === "GET" || - m === "HEAD" || - m === "OPTIONS" || - m === "POST" || - m === "PUT" - ); - } - /** - * @param {string} m - * @returns {string} - */ - function validateAndNormalizeMethod(m) { - // Fast path for well-known methods - if (isKnownMethod(m)) { - return m; - } - - // Regular path - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, m)) { - throw new TypeError("Method is not valid."); - } - const upperCase = byteUpperCase(m); - if ( - upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK" - ) { - throw new TypeError("Method is forbidden."); - } - return upperCase; - } - - class Request { - /** @type {InnerRequest} */ - [_request]; - /** @type {Headers} */ - [_headers]; - /** @type {AbortSignal} */ - [_signal]; - 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[_request].body; - } - - /** - * https://fetch.spec.whatwg.org/#dom-request - * @param {RequestInfo} input - * @param {RequestInit} init - */ - constructor(input, init = {}) { - const prefix = "Failed to construct 'Request'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters["RequestInfo"](input, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["RequestInit"](init, { - prefix, - context: "Argument 2", - }); - - this[webidl.brand] = webidl.brand; - - /** @type {InnerRequest} */ - let request; - const baseURL = getLocationHref(); - - // 4. - let signal = null; - - // 5. - if (typeof input === "string") { - const parsedURL = new URL(input, baseURL); - request = newInnerRequest("GET", parsedURL.href, [], null); - } else { // 6. - if (!(input instanceof Request)) throw new TypeError("Unreachable"); - request = input[_request]; - signal = input[_signal]; - } - - // 12. - // TODO(lucacasonato): create a copy of `request` - - // 22. - if (init.redirect !== undefined) { - request.redirectMode = init.redirect; - } - - // 25. - if (init.method !== undefined) { - let method = init.method; - method = validateAndNormalizeMethod(method); - request.method = method; - } - - // 26. - if (init.signal !== undefined) { - signal = init.signal; - } - - // NOTE: non standard extension. This handles Deno.HttpClient parameter - if (init.client !== undefined) { - if (init.client !== null && !(init.client instanceof HttpClient)) { - throw webidl.makeException( - TypeError, - "`client` must be a Deno.HttpClient", - { prefix, context: "Argument 2" }, - ); - } - request.clientRid = init.client?.rid ?? null; - } - - // 27. - this[_request] = request; - - // 28. - this[_signal] = abortSignal.newSignal(); - - // 29. - if (signal !== null) { - abortSignal.follow(this[_signal], signal); - } - - // 30. - this[_headers] = headersFromHeaderList(request.headerList, "request"); - - // 32. - if (ObjectKeys(init).length > 0) { - let headers = ArrayPrototypeSlice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - if (init.headers !== undefined) { - headers = init.headers; - } - ArrayPrototypeSplice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - fillHeaders(this[_headers], headers); - } - - // 33. - let inputBody = null; - if (input instanceof Request) { - inputBody = input[_body]; - } - - // 34. - if ( - (request.method === "GET" || request.method === "HEAD") && - ((init.body !== undefined && init.body !== null) || - inputBody !== null) - ) { - throw new TypeError("Request with GET/HEAD method cannot have body."); - } - - // 35. - let initBody = null; - - // 36. - if (init.body !== undefined && init.body !== null) { - const res = extractBody(init.body); - initBody = res.body; - if (res.contentType !== null && !this[_headers].has("content-type")) { - this[_headers].append("Content-Type", res.contentType); - } - } - - // 37. - const inputOrInitBody = initBody ?? inputBody; - - // 39. - let finalBody = inputOrInitBody; - - // 40. - if (initBody === null && inputBody !== null) { - if (input[_body] && input[_body].unusable()) { - throw new TypeError("Input request's body is unusable."); - } - finalBody = inputBody.createProxy(); - } - - // 41. - request.body = finalBody; - } - - get method() { - webidl.assertBranded(this, Request); - return this[_request].method; - } - - get url() { - webidl.assertBranded(this, Request); - return this[_request].url(); - } - - get headers() { - webidl.assertBranded(this, Request); - return this[_headers]; - } - - get redirect() { - webidl.assertBranded(this, Request); - return this[_request].redirectMode; - } - - get signal() { - webidl.assertBranded(this, Request); - return this[_signal]; - } - - clone() { - webidl.assertBranded(this, Request); - if (this[_body] && this[_body].unusable()) { - throw new TypeError("Body is unusable."); - } - const newReq = cloneInnerRequest(this[_request]); - const newSignal = abortSignal.newSignal(); - abortSignal.follow(newSignal, this[_signal]); - return fromInnerRequest( - newReq, - newSignal, - guardFromHeaders(this[_headers]), - ); - } - - get [SymbolToStringTag]() { - return "Request"; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: this instanceof Request, - keys: [ - "bodyUsed", - "headers", - "method", - "redirect", - "url", - ], - })); - } - } - - mixinBody(Request, _body, _mimeType); - - webidl.configurePrototype(Request); - - webidl.converters["Request"] = webidl.createInterfaceConverter( - "Request", - Request, - ); - webidl.converters["RequestInfo"] = (V, opts) => { - // Union for (Request or USVString) - if (typeof V == "object") { - if (V instanceof Request) { - return webidl.converters["Request"](V, opts); - } - } - return webidl.converters["USVString"](V, opts); - }; - webidl.converters["RequestRedirect"] = webidl.createEnumConverter( - "RequestRedirect", - [ - "follow", - "error", - "manual", - ], - ); - webidl.converters["RequestInit"] = webidl.createDictionaryConverter( - "RequestInit", - [ - { key: "method", converter: webidl.converters["ByteString"] }, - { key: "headers", converter: webidl.converters["HeadersInit"] }, - { - key: "body", - converter: webidl.createNullableConverter( - webidl.converters["BodyInit"], - ), - }, - { key: "redirect", converter: webidl.converters["RequestRedirect"] }, - { - key: "signal", - converter: webidl.createNullableConverter( - webidl.converters["AbortSignal"], - ), - }, - { key: "client", converter: webidl.converters.any }, - ], - ); - - /** - * @param {Request} request - * @returns {InnerRequest} - */ - function toInnerRequest(request) { - return request[_request]; - } - - /** - * @param {InnerRequest} inner - * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard - * @returns {Request} - */ - function fromInnerRequest(inner, signal, guard) { - const request = webidl.createBranded(Request); - request[_request] = inner; - request[_signal] = signal; - request[_headers] = headersFromHeaderList(inner.headerList, guard); - return request; - } - - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.Request = Request; - window.__bootstrap.fetch.toInnerRequest = toInnerRequest; - window.__bootstrap.fetch.fromInnerRequest = fromInnerRequest; - window.__bootstrap.fetch.newInnerRequest = newInnerRequest; -})(globalThis); diff --git a/extensions/fetch/23_response.js b/extensions/fetch/23_response.js deleted file mode 100644 index 0db20e90e..000000000 --- a/extensions/fetch/23_response.js +++ /dev/null @@ -1,451 +0,0 @@ -// 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="../web/lib.deno_web.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - 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, - SymbolToStringTag, - 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, - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - urlList, - status: response.status, - statusMessage: response.statusMessage, - aborted: response.aborted, - }; - } - - const defaultInnerResponse = { - type: "default", - body: null, - aborted: false, - 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; - } - - /** - * @returns {InnerResponse} - */ - function abortedNetworkError() { - const resp = networkError("aborted"); - resp.aborted = true; - return resp; - } - - class Response { - /** @type {InnerResponse} */ - [_response]; - /** @type {Headers} */ - [_headers]; - 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?"](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 (!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); - 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 [SymbolToStringTag]() { - return "Response"; - } - - [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"], - }], - ); - - /** - * @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); diff --git a/extensions/fetch/26_fetch.js b/extensions/fetch/26_fetch.js deleted file mode 100644 index f7166001e..000000000 --- a/extensions/fetch/26_fetch.js +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// @ts-check -/// <reference path="../../core/lib.deno_core.d.ts" /> -/// <reference path="../web/internal.d.ts" /> -/// <reference path="../url/internal.d.ts" /> -/// <reference path="../web/lib.deno_web.d.ts" /> -/// <reference path="../web/06_streams_types.d.ts" /> -/// <reference path="./internal.d.ts" /> -/// <reference path="./lib.deno_fetch.d.ts" /> -/// <reference lib="esnext" /> -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = window.__bootstrap.webidl; - const { errorReadableStream } = window.__bootstrap.streams; - const { InnerBody, extractBody } = window.__bootstrap.fetchBody; - const { - toInnerRequest, - toInnerResponse, - fromInnerResponse, - redirectStatus, - nullBodyStatus, - networkError, - abortedNetworkError, - } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayPrototypePush, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - Promise, - PromisePrototypeThen, - PromisePrototypeCatch, - StringPrototypeToLowerCase, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - - const REQUEST_BODY_HEADER_NAMES = [ - "content-encoding", - "content-language", - "content-location", - "content-type", - ]; - - /** - * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args - * @param {Uint8Array | null} body - * @returns {{ requestRid: number, requestBodyRid: number | null }} - */ - function opFetch(args, body) { - return core.opSync("op_fetch", args, body); - } - - /** - * @param {number} rid - * @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>} - */ - function opFetchSend(rid) { - return core.opAsync("op_fetch_send", rid); - } - - /** - * @param {number} rid - * @param {Uint8Array} body - * @returns {Promise<void>} - */ - function opFetchRequestWrite(rid, body) { - return core.opAsync("op_fetch_request_write", rid, body); - } - - /** - * @param {number} rid - * @param {Uint8Array} body - * @returns {Promise<number>} - */ - function opFetchResponseRead(rid, body) { - return core.opAsync("op_fetch_response_read", rid, body); - } - - // A finalization registry to clean up underlying fetch resources that are GC'ed. - const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { - try { - core.close(rid); - } catch { - // might have already been closed - } - }); - - /** - * @param {number} responseBodyRid - * @param {AbortSignal} [terminator] - * @returns {ReadableStream<Uint8Array>} - */ - function createResponseBodyStream(responseBodyRid, terminator) { - function onAbort() { - if (readable) { - errorReadableStream( - readable, - new DOMException("Ongoing fetch was aborted.", "AbortError"), - ); - } - try { - core.close(responseBodyRid); - } catch (_) { - // might have already been closed - } - } - // TODO(lucacasonato): clean up registration - terminator[abortSignal.add](onAbort); - const readable = new ReadableStream({ - type: "bytes", - async pull(controller) { - try { - // This is the largest possible size for a single packet on a TLS - // stream. - const chunk = new Uint8Array(16 * 1024 + 256); - const read = await opFetchResponseRead( - responseBodyRid, - chunk, - ); - if (read > 0) { - // We read some data. Enqueue it onto the stream. - controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); - } else { - RESOURCE_REGISTRY.unregister(readable); - // We have reached the end of the body, so we close the stream. - controller.close(); - try { - core.close(responseBodyRid); - } catch (_) { - // might have already been closed - } - } - } catch (err) { - RESOURCE_REGISTRY.unregister(readable); - if (terminator.aborted) { - controller.error( - new DOMException("Ongoing fetch was aborted.", "AbortError"), - ); - } else { - // There was an error while reading a chunk of the body, so we - // error. - controller.error(err); - } - try { - core.close(responseBodyRid); - } catch (_) { - // might have already been closed - } - } - }, - cancel() { - if (!terminator.aborted) { - terminator[abortSignal.signalAbort](); - } - }, - }); - RESOURCE_REGISTRY.register(readable, responseBodyRid, readable); - return readable; - } - - /** - * @param {InnerRequest} req - * @param {boolean} recursive - * @param {AbortSignal} terminator - * @returns {Promise<InnerResponse>} - */ - async function mainFetch(req, recursive, terminator) { - /** @type {ReadableStream<Uint8Array> | Uint8Array | null} */ - let reqBody = null; - - if (req.body !== null) { - if (req.body.streamOrStatic instanceof ReadableStream) { - if (req.body.length === null || req.body.source instanceof Blob) { - reqBody = req.body.stream; - } else { - const reader = req.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - reqBody = new Uint8Array(0); - } else { - reqBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - } - } else { - req.body.streamOrStatic.consumed = true; - reqBody = req.body.streamOrStatic.body; - } - } - - const { requestRid, requestBodyRid, cancelHandleRid } = opFetch({ - method: req.method, - url: req.currentUrl(), - headers: req.headerList, - clientRid: req.clientRid, - hasBody: reqBody !== null, - bodyLength: req.body?.length, - }, reqBody instanceof Uint8Array ? reqBody : null); - - function onAbort() { - try { - core.close(cancelHandleRid); - } catch (_) { - // might have already been closed - } - try { - core.close(requestBodyRid); - } catch (_) { - // might have already been closed - } - } - terminator[abortSignal.add](onAbort); - - if (requestBodyRid !== null) { - if (reqBody === null || !(reqBody instanceof ReadableStream)) { - throw new TypeError("Unreachable"); - } - const reader = reqBody.getReader(); - (async () => { - while (true) { - const { value, done } = await PromisePrototypeCatch( - reader.read(), - (err) => { - if (terminator.aborted) return { done: true, value: undefined }; - throw err; - }, - ); - if (done) break; - if (!(value instanceof Uint8Array)) { - await reader.cancel("value not a Uint8Array"); - break; - } - try { - await PromisePrototypeCatch( - opFetchRequestWrite(requestBodyRid, value), - (err) => { - if (terminator.aborted) return; - throw err; - }, - ); - if (terminator.aborted) break; - } catch (err) { - await reader.cancel(err); - break; - } - } - try { - core.close(requestBodyRid); - } catch (_) { - // might have already been closed - } - })(); - } - - let resp; - try { - resp = await PromisePrototypeCatch(opFetchSend(requestRid), (err) => { - if (terminator.aborted) return; - throw err; - }); - } finally { - try { - core.close(cancelHandleRid); - } catch (_) { - // might have already been closed - } - } - if (terminator.aborted) return abortedNetworkError(); - - /** @type {InnerResponse} */ - const response = { - headerList: resp.headers, - status: resp.status, - body: null, - statusMessage: resp.statusText, - type: "basic", - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - urlList: req.urlList, - }; - if (redirectStatus(resp.status)) { - switch (req.redirectMode) { - case "error": - core.close(resp.responseRid); - return networkError( - "Encountered redirect while redirect mode is set to 'error'", - ); - case "follow": - core.close(resp.responseRid); - return httpRedirectFetch(req, response, terminator); - case "manual": - break; - } - } - - if (nullBodyStatus(response.status)) { - core.close(resp.responseRid); - } else { - if (req.method === "HEAD" || req.method === "CONNECT") { - response.body = null; - core.close(resp.responseRid); - } else { - response.body = new InnerBody( - createResponseBodyStream(resp.responseRid, terminator), - ); - } - } - - if (recursive) return response; - - if (response.urlList.length === 0) { - response.urlList = [...req.urlList]; - } - - return response; - } - - /** - * @param {InnerRequest} request - * @param {InnerResponse} response - * @returns {Promise<InnerResponse>} - */ - function httpRedirectFetch(request, response, terminator) { - const locationHeaders = ArrayPrototypeFilter( - response.headerList, - (entry) => entry[0] === "location", - ); - if (locationHeaders.length === 0) { - return response; - } - const locationURL = new URL( - locationHeaders[0][1], - response.url() ?? undefined, - ); - if (locationURL.hash === "") { - locationURL.hash = request.currentUrl().hash; - } - if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") { - return networkError("Can not redirect to a non HTTP(s) url"); - } - if (request.redirectCount === 20) { - return networkError("Maximum number of redirects (20) reached"); - } - request.redirectCount++; - if ( - response.status !== 303 && - request.body !== null && - request.body.source === null - ) { - return networkError( - "Can not redeliver a streaming request body after a redirect", - ); - } - if ( - ((response.status === 301 || response.status === 302) && - request.method === "POST") || - (response.status === 303 && - request.method !== "GET" && - request.method !== "HEAD") - ) { - request.method = "GET"; - request.body = null; - for (let i = 0; i < request.headerList.length; i++) { - if ( - ArrayPrototypeIncludes( - REQUEST_BODY_HEADER_NAMES, - request.headerList[i][0], - ) - ) { - ArrayPrototypeSplice(request.headerList, i, 1); - i--; - } - } - } - if (request.body !== null) { - const res = extractBody(request.body.source); - request.body = res.body; - } - ArrayPrototypePush(request.urlList, locationURL.href); - return mainFetch(request, true, terminator); - } - - /** - * @param {RequestInfo} input - * @param {RequestInit} init - */ - function fetch(input, init = {}) { - // 1. - const p = new Promise((resolve, reject) => { - const prefix = "Failed to call 'fetch'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters["RequestInfo"](input, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["RequestInit"](init, { - prefix, - context: "Argument 2", - }); - - // 2. - const requestObject = new Request(input, init); - // 3. - const request = toInnerRequest(requestObject); - // 4. - if (requestObject.signal.aborted) { - reject(abortFetch(request, null)); - return; - } - - // 7. - let responseObject = null; - // 9. - let locallyAborted = false; - // 10. - function onabort() { - locallyAborted = true; - reject(abortFetch(request, responseObject)); - } - requestObject.signal[abortSignal.add](onabort); - - if (!requestObject.headers.has("accept")) { - ArrayPrototypePush(request.headerList, ["accept", "*/*"]); - } - - // 12. - PromisePrototypeCatch( - PromisePrototypeThen( - mainFetch(request, false, requestObject.signal), - (response) => { - // 12.1. - if (locallyAborted) return; - // 12.2. - if (response.aborted) { - reject(request, responseObject); - requestObject.signal[abortSignal.remove](onabort); - return; - } - // 12.3. - if (response.type === "error") { - const err = new TypeError( - "Fetch failed: " + (response.error ?? "unknown error"), - ); - reject(err); - requestObject.signal[abortSignal.remove](onabort); - return; - } - responseObject = fromInnerResponse(response, "immutable"); - resolve(responseObject); - requestObject.signal[abortSignal.remove](onabort); - }, - ), - (err) => { - reject(err); - requestObject.signal[abortSignal.remove](onabort); - }, - ); - }); - return p; - } - - function abortFetch(request, responseObject) { - const error = new DOMException("Ongoing fetch was aborted.", "AbortError"); - if (request.body !== null) request.body.cancel(error); - if (responseObject !== null) { - const response = toInnerResponse(responseObject); - if (response.body !== null) response.body.error(error); - } - return error; - } - - /** - * Handle the Promise<Response> argument to the WebAssembly streaming - * APIs. This function should be registered through - * `Deno.core.setWasmStreamingCallback`. - * - * @param {any} source The source parameter that the WebAssembly - * streaming API was called with. - * @param {number} rid An rid that can be used with - * `Deno.core.wasmStreamingFeed`. - */ - function handleWasmStreaming(source, rid) { - // This implements part of - // https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response - (async () => { - try { - const res = webidl.converters["Response"](await source, { - prefix: "Failed to call 'WebAssembly.compileStreaming'", - context: "Argument 1", - }); - - // 2.3. - // The spec is ambiguous here, see - // https://github.com/WebAssembly/spec/issues/1138. The WPT tests - // expect the raw value of the Content-Type attribute lowercased. - if ( - StringPrototypeToLowerCase(res.headers.get("Content-Type")) !== - "application/wasm" - ) { - throw new TypeError("Invalid WebAssembly content type."); - } - - // 2.5. - if (!res.ok) { - throw new TypeError(`HTTP status code ${res.status}`); - } - - // 2.6. - // Rather than consuming the body as an ArrayBuffer, this passes each - // chunk to the feed as soon as it's available. - if (res.body !== null) { - const reader = res.body.getReader(); - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) break; - Deno.core.wasmStreamingFeed(rid, "bytes", chunk); - } - } - - // 2.7. - Deno.core.wasmStreamingFeed(rid, "finish"); - } catch (err) { - // 2.8 and 3 - Deno.core.wasmStreamingFeed(rid, "abort", err); - } - })(); - } - - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.fetch = fetch; - window.__bootstrap.fetch.handleWasmStreaming = handleWasmStreaming; -})(this); diff --git a/extensions/fetch/Cargo.toml b/extensions/fetch/Cargo.toml deleted file mode 100644 index 80d0cb2e1..000000000 --- a/extensions/fetch/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -[package] -name = "deno_fetch" -version = "0.37.0" -authors = ["the Deno authors"] -edition = "2018" -license = "MIT" -readme = "README.md" -repository = "https://github.com/denoland/deno" -description = "Fetch API implementation for Deno" - -[lib] -path = "lib.rs" - -[dependencies] -bytes = "1.0.1" -data-url = "0.1.0" -deno_core = { version = "0.96.0", path = "../../core" } -deno_tls = { version = "0.1.0", path = "../tls" } -deno_web = { version = "0.45.0", path = "../web" } -http = "0.2.4" -lazy_static = "1.4.0" -reqwest = { version = "0.11.4", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } -serde = { version = "1.0.126", features = ["derive"] } -tokio = { version = "1.8.1", features = ["full"] } -tokio-stream = "0.1.7" -tokio-util = "0.6.7" diff --git a/extensions/fetch/README.md b/extensions/fetch/README.md deleted file mode 100644 index 2c946197e..000000000 --- a/extensions/fetch/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# deno_fetch - -This crate implements the Fetch API. - -Spec: https://fetch.spec.whatwg.org/ diff --git a/extensions/fetch/internal.d.ts b/extensions/fetch/internal.d.ts deleted file mode 100644 index a84e0bcce..000000000 --- a/extensions/fetch/internal.d.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file no-explicit-any - -/// <reference no-default-lib="true" /> -/// <reference lib="esnext" /> - -declare namespace globalThis { - declare namespace __bootstrap { - declare var fetchUtil: { - requiredArguments(name: string, length: number, required: number): void; - }; - - declare var domIterable: { - DomIterableMixin(base: any, dataSymbol: symbol): any; - }; - - declare namespace headers { - class Headers { - } - type HeaderList = [string, string][]; - function headersFromHeaderList( - list: HeaderList, - guard: - | "immutable" - | "request" - | "request-no-cors" - | "response" - | "none", - ): Headers; - function headerListFromHeaders(headers: Headers): HeaderList; - function fillHeaders(headers: Headers, object: HeadersInit): void; - function getDecodeSplitHeader( - list: HeaderList, - name: string, - ): string[] | null; - function guardFromHeaders( - headers: Headers, - ): "immutable" | "request" | "request-no-cors" | "response" | "none"; - } - - declare namespace formData { - declare type FormData = typeof FormData; - declare function formDataToBlob( - formData: globalThis.FormData, - ): Blob; - declare function parseFormData( - body: Uint8Array, - boundary: string | undefined, - ): FormData; - declare function formDataFromEntries(entries: FormDataEntry[]): FormData; - } - - declare namespace fetchBody { - function mixinBody( - prototype: any, - bodySymbol: symbol, - mimeTypeSymbol: symbol, - ): void; - class InnerBody { - constructor(stream?: ReadableStream<Uint8Array>); - stream: ReadableStream<Uint8Array>; - source: null | Uint8Array | Blob | FormData; - length: null | number; - unusable(): boolean; - consume(): Promise<Uint8Array>; - clone(): InnerBody; - } - function extractBody(object: BodyInit): { - body: InnerBody; - contentType: string | null; - }; - } - - declare namespace fetch { - function toInnerRequest(request: Request): InnerRequest; - function fromInnerRequest( - inner: InnerRequest, - signal: AbortSignal | null, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - ): Request; - function redirectStatus(status: number): boolean; - function nullBodyStatus(status: number): boolean; - function newInnerRequest( - method: string, - url: any, - headerList?: [string, string][], - body?: globalThis.__bootstrap.fetchBody.InnerBody, - ): InnerResponse; - function toInnerResponse(response: Response): InnerResponse; - function fromInnerResponse( - inner: InnerResponse, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - ): Response; - function networkError(error: string): InnerResponse; - } - } -} diff --git a/extensions/fetch/lib.deno_fetch.d.ts b/extensions/fetch/lib.deno_fetch.d.ts deleted file mode 100644 index 7fe7d9453..000000000 --- a/extensions/fetch/lib.deno_fetch.d.ts +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file no-explicit-any - -/// <reference no-default-lib="true" /> -/// <reference lib="esnext" /> - -interface DomIterable<K, V> { - keys(): IterableIterator<K>; - values(): IterableIterator<V>; - entries(): IterableIterator<[K, V]>; - [Symbol.iterator](): IterableIterator<[K, V]>; - forEach( - callback: (value: V, key: K, parent: this) => void, - thisArg?: any, - ): void; -} - -type FormDataEntryValue = File | string; - -/** Provides a way to easily construct a set of key/value pairs representing - * form fields and their values, which can then be easily sent using the - * XMLHttpRequest.send() method. It uses the same format a form would use if the - * encoding type were set to "multipart/form-data". */ -declare class FormData implements DomIterable<string, FormDataEntryValue> { - // TODO(ry) FormData constructor is non-standard. - // new(form?: HTMLFormElement): FormData; - constructor(); - - append(name: string, value: string | Blob, fileName?: string): void; - delete(name: string): void; - get(name: string): FormDataEntryValue | null; - getAll(name: string): FormDataEntryValue[]; - has(name: string): boolean; - set(name: string, value: string | Blob, fileName?: string): void; - keys(): IterableIterator<string>; - values(): IterableIterator<string>; - entries(): IterableIterator<[string, FormDataEntryValue]>; - [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; - forEach( - callback: (value: FormDataEntryValue, key: string, parent: this) => void, - thisArg?: any, - ): void; -} - -interface Body { - /** A simple getter used to expose a `ReadableStream` of the body contents. */ - readonly body: ReadableStream<Uint8Array> | null; - /** Stores a `Boolean` that declares whether the body has been used in a - * response yet. - */ - readonly bodyUsed: boolean; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with an `ArrayBuffer`. - */ - arrayBuffer(): Promise<ArrayBuffer>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `Blob`. - */ - blob(): Promise<Blob>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `FormData` object. - */ - formData(): Promise<FormData>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with the result of parsing the body text as JSON. - */ - json(): Promise<any>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `USVString` (text). - */ - text(): Promise<string>; -} - -type HeadersInit = Headers | string[][] | Record<string, string>; - -/** This Fetch API interface allows you to perform various actions on HTTP - * request and response headers. These actions include retrieving, setting, - * adding to, and removing. A Headers object has an associated header list, - * which is initially empty and consists of zero or more name and value pairs. - * You can add to this using methods like append() (see Examples). In all - * methods of this interface, header names are matched by case-insensitive byte - * sequence. */ -interface Headers { - append(name: string, value: string): void; - delete(name: string): void; - get(name: string): string | null; - has(name: string): boolean; - set(name: string, value: string): void; - forEach( - callbackfn: (value: string, key: string, parent: Headers) => void, - thisArg?: any, - ): void; -} - -declare class Headers implements DomIterable<string, string> { - constructor(init?: HeadersInit); - - /** Appends a new value onto an existing header inside a `Headers` object, or - * adds the header if it does not already exist. - */ - append(name: string, value: string): void; - /** Deletes a header from a `Headers` object. */ - delete(name: string): void; - /** Returns an iterator allowing to go through all key/value pairs - * contained in this Headers object. The both the key and value of each pairs - * are ByteString objects. - */ - entries(): IterableIterator<[string, string]>; - /** Returns a `ByteString` sequence of all the values of a header within a - * `Headers` object with a given name. - */ - get(name: string): string | null; - /** Returns a boolean stating whether a `Headers` object contains a certain - * header. - */ - has(name: string): boolean; - /** Returns an iterator allowing to go through all keys contained in - * this Headers object. The keys are ByteString objects. - */ - keys(): IterableIterator<string>; - /** Sets a new value for an existing header inside a Headers object, or adds - * the header if it does not already exist. - */ - set(name: string, value: string): void; - /** Returns an iterator allowing to go through all values contained in - * this Headers object. The values are ByteString objects. - */ - values(): IterableIterator<string>; - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any, - ): void; - /** The Symbol.iterator well-known symbol specifies the default - * iterator for this Headers object - */ - [Symbol.iterator](): IterableIterator<[string, string]>; -} - -type RequestInfo = Request | string; -type RequestCache = - | "default" - | "force-cache" - | "no-cache" - | "no-store" - | "only-if-cached" - | "reload"; -type RequestCredentials = "include" | "omit" | "same-origin"; -type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin"; -type RequestRedirect = "error" | "follow" | "manual"; -type ReferrerPolicy = - | "" - | "no-referrer" - | "no-referrer-when-downgrade" - | "origin" - | "origin-when-cross-origin" - | "same-origin" - | "strict-origin" - | "strict-origin-when-cross-origin" - | "unsafe-url"; -type BodyInit = - | Blob - | BufferSource - | FormData - | URLSearchParams - | ReadableStream<Uint8Array> - | string; -type RequestDestination = - | "" - | "audio" - | "audioworklet" - | "document" - | "embed" - | "font" - | "image" - | "manifest" - | "object" - | "paintworklet" - | "report" - | "script" - | "sharedworker" - | "style" - | "track" - | "video" - | "worker" - | "xslt"; - -interface RequestInit { - /** - * A BodyInit object or null to set request's body. - */ - body?: BodyInit | null; - /** - * A string indicating how the request will interact with the browser's cache - * to set request's cache. - */ - cache?: RequestCache; - /** - * A string indicating whether credentials will be sent with the request - * always, never, or only when sent to a same-origin URL. Sets request's - * credentials. - */ - credentials?: RequestCredentials; - /** - * A Headers object, an object literal, or an array of two-item arrays to set - * request's headers. - */ - headers?: HeadersInit; - /** - * A cryptographic hash of the resource to be fetched by request. Sets - * request's integrity. - */ - integrity?: string; - /** - * A boolean to set request's keepalive. - */ - keepalive?: boolean; - /** - * A string to set request's method. - */ - method?: string; - /** - * A string to indicate whether the request will use CORS, or will be - * restricted to same-origin URLs. Sets request's mode. - */ - mode?: RequestMode; - /** - * A string indicating whether request follows redirects, results in an error - * upon encountering a redirect, or returns the redirect (in an opaque - * fashion). Sets request's redirect. - */ - redirect?: RequestRedirect; - /** - * A string whose value is a same-origin URL, "about:client", or the empty - * string, to set request's referrer. - */ - referrer?: string; - /** - * A referrer policy to set request's referrerPolicy. - */ - referrerPolicy?: ReferrerPolicy; - /** - * An AbortSignal to set request's signal. - */ - signal?: AbortSignal | null; - /** - * Can only be null. Used to disassociate request from any Window. - */ - window?: any; -} - -/** This Fetch API interface represents a resource request. */ -declare class Request implements Body { - constructor(input: RequestInfo, init?: RequestInit); - - /** - * Returns the cache mode associated with request, which is a string - * indicating how the request will interact with the browser's cache when - * fetching. - */ - readonly cache: RequestCache; - /** - * Returns the credentials mode associated with request, which is a string - * indicating whether credentials will be sent with the request always, never, - * or only when sent to a same-origin URL. - */ - readonly credentials: RequestCredentials; - /** - * Returns the kind of resource requested by request, e.g., "document" or "script". - */ - readonly destination: RequestDestination; - /** - * Returns a Headers object consisting of the headers associated with request. - * Note that headers added in the network layer by the user agent will not be - * accounted for in this object, e.g., the "Host" header. - */ - readonly headers: Headers; - /** - * Returns request's subresource integrity metadata, which is a cryptographic - * hash of the resource being fetched. Its value consists of multiple hashes - * separated by whitespace. [SRI] - */ - readonly integrity: string; - /** - * Returns a boolean indicating whether or not request is for a history - * navigation (a.k.a. back-forward navigation). - */ - readonly isHistoryNavigation: boolean; - /** - * Returns a boolean indicating whether or not request is for a reload - * navigation. - */ - readonly isReloadNavigation: boolean; - /** - * Returns a boolean indicating whether or not request can outlive the global - * in which it was created. - */ - readonly keepalive: boolean; - /** - * Returns request's HTTP method, which is "GET" by default. - */ - readonly method: string; - /** - * Returns the mode associated with request, which is a string indicating - * whether the request will use CORS, or will be restricted to same-origin - * URLs. - */ - readonly mode: RequestMode; - /** - * Returns the redirect mode associated with request, which is a string - * indicating how redirects for the request will be handled during fetching. A - * request will follow redirects by default. - */ - readonly redirect: RequestRedirect; - /** - * Returns the referrer of request. Its value can be a same-origin URL if - * explicitly set in init, the empty string to indicate no referrer, and - * "about:client" when defaulting to the global's default. This is used during - * fetching to determine the value of the `Referer` header of the request - * being made. - */ - readonly referrer: string; - /** - * Returns the referrer policy associated with request. This is used during - * fetching to compute the value of the request's referrer. - */ - readonly referrerPolicy: ReferrerPolicy; - /** - * Returns the signal associated with request, which is an AbortSignal object - * indicating whether or not request has been aborted, and its abort event - * handler. - */ - readonly signal: AbortSignal; - /** - * Returns the URL of request as a string. - */ - readonly url: string; - clone(): Request; - - /** A simple getter used to expose a `ReadableStream` of the body contents. */ - readonly body: ReadableStream<Uint8Array> | null; - /** Stores a `Boolean` that declares whether the body has been used in a - * response yet. - */ - readonly bodyUsed: boolean; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with an `ArrayBuffer`. - */ - arrayBuffer(): Promise<ArrayBuffer>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `Blob`. - */ - blob(): Promise<Blob>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `FormData` object. - */ - formData(): Promise<FormData>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with the result of parsing the body text as JSON. - */ - json(): Promise<any>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `USVString` (text). - */ - text(): Promise<string>; -} - -interface ResponseInit { - headers?: HeadersInit; - status?: number; - statusText?: string; -} - -type ResponseType = - | "basic" - | "cors" - | "default" - | "error" - | "opaque" - | "opaqueredirect"; - -/** This Fetch API interface represents the response to a request. */ -declare class Response implements Body { - constructor(body?: BodyInit | null, init?: ResponseInit); - static error(): Response; - static redirect(url: string, status?: number): Response; - - readonly headers: Headers; - readonly ok: boolean; - readonly redirected: boolean; - readonly status: number; - readonly statusText: string; - readonly trailer: Promise<Headers>; - readonly type: ResponseType; - readonly url: string; - clone(): Response; - - /** A simple getter used to expose a `ReadableStream` of the body contents. */ - readonly body: ReadableStream<Uint8Array> | null; - /** Stores a `Boolean` that declares whether the body has been used in a - * response yet. - */ - readonly bodyUsed: boolean; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with an `ArrayBuffer`. - */ - arrayBuffer(): Promise<ArrayBuffer>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `Blob`. - */ - blob(): Promise<Blob>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `FormData` object. - */ - formData(): Promise<FormData>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with the result of parsing the body text as JSON. - */ - json(): Promise<any>; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `USVString` (text). - */ - text(): Promise<string>; -} - -/** Fetch a resource from the network. It returns a Promise that resolves to the - * Response to that request, whether it is successful or not. - * - * const response = await fetch("http://my.json.host/data.json"); - * console.log(response.status); // e.g. 200 - * console.log(response.statusText); // e.g. "OK" - * const jsonData = await response.json(); - */ -declare function fetch( - input: Request | URL | string, - init?: RequestInit, -): Promise<Response>; diff --git a/extensions/fetch/lib.rs b/extensions/fetch/lib.rs deleted file mode 100644 index d9e016b0d..000000000 --- a/extensions/fetch/lib.rs +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use data_url::DataUrl; -use deno_core::error::bad_resource_id; -use deno_core::error::null_opbuf; -use deno_core::error::type_error; -use deno_core::error::AnyError; -use deno_core::futures::Future; -use deno_core::futures::Stream; -use deno_core::futures::StreamExt; -use deno_core::include_js_files; -use deno_core::op_async; -use deno_core::op_sync; -use deno_core::url::Url; -use deno_core::AsyncRefCell; -use deno_core::ByteString; -use deno_core::CancelFuture; -use deno_core::CancelHandle; -use deno_core::CancelTryFuture; -use deno_core::Canceled; -use deno_core::Extension; -use deno_core::OpState; -use deno_core::RcRef; -use deno_core::Resource; -use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; -use deno_tls::create_http_client; -use deno_tls::rustls::RootCertStore; -use deno_tls::Proxy; -use deno_web::BlobStore; -use http::header::CONTENT_LENGTH; -use reqwest::header::HeaderName; -use reqwest::header::HeaderValue; -use reqwest::header::HOST; -use reqwest::Body; -use reqwest::Client; -use reqwest::Method; -use reqwest::RequestBuilder; -use reqwest::Response; -use serde::Deserialize; -use serde::Serialize; -use std::borrow::Cow; -use std::cell::RefCell; -use std::convert::From; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; -use std::pin::Pin; -use std::rc::Rc; -use tokio::io::AsyncReadExt; -use tokio::sync::mpsc; -use tokio_stream::wrappers::ReceiverStream; -use tokio_util::io::StreamReader; - -pub use reqwest; // Re-export reqwest - -pub fn init<P: FetchPermissions + 'static>( - user_agent: String, - root_cert_store: Option<RootCertStore>, - proxy: Option<Proxy>, - request_builder_hook: Option<fn(RequestBuilder) -> RequestBuilder>, - unsafely_ignore_certificate_errors: Option<Vec<String>>, -) -> Extension { - Extension::builder() - .js(include_js_files!( - prefix "deno:extensions/fetch", - "01_fetch_util.js", - "20_headers.js", - "21_formdata.js", - "22_body.js", - "22_http_client.js", - "23_request.js", - "23_response.js", - "26_fetch.js", - )) - .ops(vec![ - ("op_fetch", op_sync(op_fetch::<P>)), - ("op_fetch_send", op_async(op_fetch_send)), - ("op_fetch_request_write", op_async(op_fetch_request_write)), - ("op_fetch_response_read", op_async(op_fetch_response_read)), - ("op_create_http_client", op_sync(op_create_http_client::<P>)), - ]) - .state(move |state| { - state.put::<reqwest::Client>({ - create_http_client( - user_agent.clone(), - root_cert_store.clone(), - None, - proxy.clone(), - unsafely_ignore_certificate_errors.clone(), - ) - .unwrap() - }); - state.put::<HttpClientDefaults>(HttpClientDefaults { - user_agent: user_agent.clone(), - root_cert_store: root_cert_store.clone(), - proxy: proxy.clone(), - request_builder_hook, - unsafely_ignore_certificate_errors: unsafely_ignore_certificate_errors - .clone(), - }); - Ok(()) - }) - .build() -} - -pub struct HttpClientDefaults { - pub user_agent: String, - pub root_cert_store: Option<RootCertStore>, - pub proxy: Option<Proxy>, - pub request_builder_hook: Option<fn(RequestBuilder) -> RequestBuilder>, - pub unsafely_ignore_certificate_errors: Option<Vec<String>>, -} - -pub trait FetchPermissions { - fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError>; - fn check_read(&mut self, _p: &Path) -> Result<(), AnyError>; -} - -/// For use with `op_fetch` when the user does not want permissions. -pub struct NoFetchPermissions; - -impl FetchPermissions for NoFetchPermissions { - fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError> { - Ok(()) - } - - fn check_read(&mut self, _p: &Path) -> Result<(), AnyError> { - Ok(()) - } -} - -pub fn get_declaration() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_fetch.d.ts") -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FetchArgs { - method: ByteString, - url: String, - headers: Vec<(ByteString, ByteString)>, - client_rid: Option<u32>, - has_body: bool, - body_length: Option<u64>, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FetchReturn { - request_rid: ResourceId, - request_body_rid: Option<ResourceId>, - cancel_handle_rid: Option<ResourceId>, -} - -pub fn op_fetch<FP>( - state: &mut OpState, - args: FetchArgs, - data: Option<ZeroCopyBuf>, -) -> Result<FetchReturn, AnyError> -where - FP: FetchPermissions + 'static, -{ - let client = if let Some(rid) = args.client_rid { - let r = state - .resource_table - .get::<HttpClientResource>(rid) - .ok_or_else(bad_resource_id)?; - r.client.clone() - } else { - let client = state.borrow::<reqwest::Client>(); - client.clone() - }; - - let method = Method::from_bytes(&args.method)?; - let url = Url::parse(&args.url)?; - - // Check scheme before asking for net permission - let scheme = url.scheme(); - let (request_rid, request_body_rid, cancel_handle_rid) = match scheme { - "http" | "https" => { - let permissions = state.borrow_mut::<FP>(); - permissions.check_net_url(&url)?; - - let mut request = client.request(method, url); - - let request_body_rid = if args.has_body { - match data { - None => { - // If no body is passed, we return a writer for streaming the body. - let (tx, rx) = mpsc::channel::<std::io::Result<Vec<u8>>>(1); - - // If the size of the body is known, we include a content-length - // header explicitly. - if let Some(body_size) = args.body_length { - request = - request.header(CONTENT_LENGTH, HeaderValue::from(body_size)) - } - - request = request.body(Body::wrap_stream(ReceiverStream::new(rx))); - - let request_body_rid = - state.resource_table.add(FetchRequestBodyResource { - body: AsyncRefCell::new(tx), - cancel: CancelHandle::default(), - }); - - Some(request_body_rid) - } - Some(data) => { - // If a body is passed, we use it, and don't return a body for streaming. - request = request.body(Vec::from(&*data)); - None - } - } - } else { - None - }; - - for (key, value) in args.headers { - let name = HeaderName::from_bytes(&key).unwrap(); - let v = HeaderValue::from_bytes(&value).unwrap(); - if name != HOST { - request = request.header(name, v); - } - } - - let defaults = state.borrow::<HttpClientDefaults>(); - if let Some(request_builder_hook) = defaults.request_builder_hook { - request = request_builder_hook(request); - } - - let cancel_handle = CancelHandle::new_rc(); - let cancel_handle_ = cancel_handle.clone(); - - let fut = async move { - request - .send() - .or_cancel(cancel_handle_) - .await - .map(|res| res.map_err(|err| type_error(err.to_string()))) - }; - - let request_rid = state - .resource_table - .add(FetchRequestResource(Box::pin(fut))); - - let cancel_handle_rid = - state.resource_table.add(FetchCancelHandle(cancel_handle)); - - (request_rid, request_body_rid, Some(cancel_handle_rid)) - } - "data" => { - let data_url = DataUrl::process(url.as_str()) - .map_err(|e| type_error(format!("{:?}", e)))?; - - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{:?}", e)))?; - - let response = http::Response::builder() - .status(http::StatusCode::OK) - .header(http::header::CONTENT_TYPE, data_url.mime_type().to_string()) - .body(reqwest::Body::from(body))?; - - let fut = async move { Ok(Ok(Response::from(response))) }; - - let request_rid = state - .resource_table - .add(FetchRequestResource(Box::pin(fut))); - - (request_rid, None, None) - } - "blob" => { - let blob_store = state.try_borrow::<BlobStore>().ok_or_else(|| { - type_error("Blob URLs are not supported in this context.") - })?; - - let blob = blob_store - .get_object_url(url)? - .ok_or_else(|| type_error("Blob for the given URL not found."))?; - - if method != "GET" { - return Err(type_error("Blob URL fetch only supports GET method.")); - } - - let cancel_handle = CancelHandle::new_rc(); - let cancel_handle_ = cancel_handle.clone(); - - let fut = async move { - // TODO(lucacsonato): this should be a stream! - let chunk = match blob.read_all().or_cancel(cancel_handle_).await? { - Ok(chunk) => chunk, - Err(err) => return Ok(Err(err)), - }; - - let res = http::Response::builder() - .status(http::StatusCode::OK) - .header(http::header::CONTENT_LENGTH, chunk.len()) - .header(http::header::CONTENT_TYPE, blob.media_type.clone()) - .body(reqwest::Body::from(chunk)) - .map_err(|err| type_error(err.to_string())); - - match res { - Ok(response) => Ok(Ok(Response::from(response))), - Err(err) => Ok(Err(err)), - } - }; - - let request_rid = state - .resource_table - .add(FetchRequestResource(Box::pin(fut))); - - let cancel_handle_rid = - state.resource_table.add(FetchCancelHandle(cancel_handle)); - - (request_rid, None, Some(cancel_handle_rid)) - } - _ => return Err(type_error(format!("scheme '{}' not supported", scheme))), - }; - - Ok(FetchReturn { - request_rid, - request_body_rid, - cancel_handle_rid, - }) -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FetchResponse { - status: u16, - status_text: String, - headers: Vec<(ByteString, ByteString)>, - url: String, - response_rid: ResourceId, -} - -pub async fn op_fetch_send( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - _: (), -) -> Result<FetchResponse, AnyError> { - let request = state - .borrow_mut() - .resource_table - .take::<FetchRequestResource>(rid) - .ok_or_else(bad_resource_id)?; - - let request = Rc::try_unwrap(request) - .ok() - .expect("multiple op_fetch_send ongoing"); - - let res = match request.0.await { - Ok(Ok(res)) => res, - Ok(Err(err)) => return Err(type_error(err.to_string())), - Err(_) => return Err(type_error("request was cancelled")), - }; - - //debug!("Fetch response {}", url); - let status = res.status(); - let url = res.url().to_string(); - let mut res_headers = Vec::new(); - for (key, val) in res.headers().iter() { - let key_bytes: &[u8] = key.as_ref(); - res_headers.push(( - ByteString(key_bytes.to_owned()), - ByteString(val.as_bytes().to_owned()), - )); - } - - let stream: BytesStream = Box::pin(res.bytes_stream().map(|r| { - r.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) - })); - let stream_reader = StreamReader::new(stream); - let rid = state - .borrow_mut() - .resource_table - .add(FetchResponseBodyResource { - reader: AsyncRefCell::new(stream_reader), - cancel: CancelHandle::default(), - }); - - Ok(FetchResponse { - status: status.as_u16(), - status_text: status.canonical_reason().unwrap_or("").to_string(), - headers: res_headers, - url, - response_rid: rid, - }) -} - -pub async fn op_fetch_request_write( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - data: Option<ZeroCopyBuf>, -) -> Result<(), AnyError> { - let data = data.ok_or_else(null_opbuf)?; - let buf = Vec::from(&*data); - - let resource = state - .borrow() - .resource_table - .get::<FetchRequestBodyResource>(rid) - .ok_or_else(bad_resource_id)?; - let body = RcRef::map(&resource, |r| &r.body).borrow_mut().await; - let cancel = RcRef::map(resource, |r| &r.cancel); - body.send(Ok(buf)).or_cancel(cancel).await?.map_err(|_| { - type_error("request body receiver not connected (request closed)") - })?; - - Ok(()) -} - -pub async fn op_fetch_response_read( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - data: Option<ZeroCopyBuf>, -) -> Result<usize, AnyError> { - let data = data.ok_or_else(null_opbuf)?; - - let resource = state - .borrow() - .resource_table - .get::<FetchResponseBodyResource>(rid) - .ok_or_else(bad_resource_id)?; - let mut reader = RcRef::map(&resource, |r| &r.reader).borrow_mut().await; - let cancel = RcRef::map(resource, |r| &r.cancel); - let mut buf = data.clone(); - let read = reader.read(&mut buf).try_or_cancel(cancel).await?; - Ok(read) -} - -type CancelableResponseResult = Result<Result<Response, AnyError>, Canceled>; - -struct FetchRequestResource( - Pin<Box<dyn Future<Output = CancelableResponseResult>>>, -); - -impl Resource for FetchRequestResource { - fn name(&self) -> Cow<str> { - "fetchRequest".into() - } -} - -struct FetchCancelHandle(Rc<CancelHandle>); - -impl Resource for FetchCancelHandle { - fn name(&self) -> Cow<str> { - "fetchCancelHandle".into() - } - - fn close(self: Rc<Self>) { - self.0.cancel() - } -} - -struct FetchRequestBodyResource { - body: AsyncRefCell<mpsc::Sender<std::io::Result<Vec<u8>>>>, - cancel: CancelHandle, -} - -impl Resource for FetchRequestBodyResource { - fn name(&self) -> Cow<str> { - "fetchRequestBody".into() - } - - fn close(self: Rc<Self>) { - self.cancel.cancel() - } -} - -type BytesStream = - Pin<Box<dyn Stream<Item = Result<bytes::Bytes, std::io::Error>> + Unpin>>; - -struct FetchResponseBodyResource { - reader: AsyncRefCell<StreamReader<BytesStream, bytes::Bytes>>, - cancel: CancelHandle, -} - -impl Resource for FetchResponseBodyResource { - fn name(&self) -> Cow<str> { - "fetchResponseBody".into() - } - - fn close(self: Rc<Self>) { - self.cancel.cancel() - } -} - -struct HttpClientResource { - client: Client, -} - -impl Resource for HttpClientResource { - fn name(&self) -> Cow<str> { - "httpClient".into() - } -} - -impl HttpClientResource { - fn new(client: Client) -> Self { - Self { client } - } -} - -#[derive(Deserialize, Default, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(default)] -pub struct CreateHttpClientOptions { - ca_stores: Option<Vec<String>>, - ca_file: Option<String>, - ca_data: Option<ByteString>, - proxy: Option<Proxy>, -} - -pub fn op_create_http_client<FP>( - state: &mut OpState, - args: CreateHttpClientOptions, - _: (), -) -> Result<ResourceId, AnyError> -where - FP: FetchPermissions + 'static, -{ - if let Some(ca_file) = args.ca_file.clone() { - let permissions = state.borrow_mut::<FP>(); - permissions.check_read(&PathBuf::from(ca_file))?; - } - - if let Some(proxy) = args.proxy.clone() { - let permissions = state.borrow_mut::<FP>(); - let url = Url::parse(&proxy.url)?; - permissions.check_net_url(&url)?; - } - - let defaults = state.borrow::<HttpClientDefaults>(); - let cert_data = - get_cert_data(args.ca_file.as_deref(), args.ca_data.as_deref())?; - - let client = create_http_client( - defaults.user_agent.clone(), - defaults.root_cert_store.clone(), - cert_data, - args.proxy, - defaults.unsafely_ignore_certificate_errors.clone(), - ) - .unwrap(); - - let rid = state.resource_table.add(HttpClientResource::new(client)); - Ok(rid) -} - -fn get_cert_data( - ca_file: Option<&str>, - ca_data: Option<&[u8]>, -) -> Result<Option<Vec<u8>>, AnyError> { - if let Some(ca_data) = ca_data { - Ok(Some(ca_data.to_vec())) - } else if let Some(ca_file) = ca_file { - let mut buf = Vec::new(); - File::open(ca_file)?.read_to_end(&mut buf)?; - Ok(Some(buf)) - } else { - Ok(None) - } -} |