summaryrefslogtreecommitdiff
path: root/extensions/fetch/23_request.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/fetch/23_request.js')
-rw-r--r--extensions/fetch/23_request.js524
1 files changed, 524 insertions, 0 deletions
diff --git a/extensions/fetch/23_request.js b/extensions/fetch/23_request.js
new file mode 100644
index 000000000..603a37a5f
--- /dev/null
+++ b/extensions/fetch/23_request.js
@@ -0,0 +1,524 @@
+// 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="../file/internal.d.ts" />
+/// <reference path="../file/lib.deno_file.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./11_streams_types.d.ts" />
+/// <reference path="./lib.deno_fetch.d.ts" />
+/// <reference lib="esnext" />
+"use strict";
+
+((window) => {
+ const webidl = window.__bootstrap.webidl;
+ const { HTTP_TOKEN_CODE_POINT_RE, byteUpperCase } = window.__bootstrap.infra;
+ const { URL } = window.__bootstrap.url;
+ const { guardFromHeaders } = window.__bootstrap.headers;
+ const { InnerBody, 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 _request = Symbol("request");
+ const _headers = Symbol("headers");
+ 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 | 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 {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 = [...request.headerList.map((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 (!HTTP_TOKEN_CODE_POINT_RE.test(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];
+ get [_mimeType]() {
+ let charset = null;
+ let essence = null;
+ let mimeType = null;
+ const values = getDecodeSplitHeader(
+ headerListFromHeaders(this[_headers]),
+ "Content-Type",
+ );
+ if (values === null) return null;
+ for (const value of values) {
+ const temporaryMimeType = mimesniff.parseMimeType(value);
+ if (
+ temporaryMimeType === null ||
+ mimesniff.essence(temporaryMimeType) == "*/*"
+ ) {
+ continue;
+ }
+ mimeType = temporaryMimeType;
+ if (mimesniff.essence(mimeType) !== essence) {
+ charset = null;
+ const newCharset = mimeType.parameters.get("charset");
+ if (newCharset !== undefined) {
+ charset = newCharset;
+ }
+ essence = mimesniff.essence(mimeType);
+ } else {
+ if (mimeType.parameters.has("charset") === null && charset !== null) {
+ mimeType.parameters.set("charset", charset);
+ }
+ }
+ }
+ if (mimeType === null) return null;
+ return mimeType;
+ }
+ get [_body]() {
+ return this[_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();
+
+ // 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];
+ }
+
+ // 22.
+ if (init.redirect !== undefined) {
+ request.redirectMode = init.redirect;
+ }
+
+ // 25.
+ if (init.method !== undefined) {
+ let method = init.method;
+ method = validateAndNormalizeMethod(method);
+ request.method = method;
+ }
+
+ // 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;
+
+ // 29.
+ this[_headers] = headersFromHeaderList(request.headerList, "request");
+
+ // 31.
+ if (Object.keys(init).length > 0) {
+ let headers = headerListFromHeaders(this[_headers]).slice(
+ 0,
+ headerListFromHeaders(this[_headers]).length,
+ );
+ if (init.headers !== undefined) {
+ headers = init.headers;
+ }
+ headerListFromHeaders(this[_headers]).splice(
+ 0,
+ headerListFromHeaders(this[_headers]).length,
+ );
+ fillHeaders(this[_headers], headers);
+ }
+
+ // 32.
+ let inputBody = null;
+ if (input instanceof Request) {
+ inputBody = input[_body];
+ }
+
+ // 33.
+ if (
+ (request.method === "GET" || request.method === "HEAD") &&
+ ((init.body !== undefined && init.body !== null) ||
+ inputBody !== null)
+ ) {
+ throw new TypeError("HEAD and GET requests may not have a body.");
+ }
+
+ // 34.
+ let initBody = null;
+
+ // 35.
+ 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);
+ }
+ }
+
+ // 36.
+ const inputOrInitBody = initBody ?? inputBody;
+
+ // 38.
+ const finalBody = inputOrInitBody;
+
+ // 39.
+ // TODO(lucacasonato): implement this step. Is it needed?
+
+ // 40.
+ 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 destination() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get referrer() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get referrerPolicy() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get mode() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get credentials() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get cache() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get redirect() {
+ webidl.assertBranded(this, Request);
+ return this[_request].redirectMode;
+ }
+
+ get integrity() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get keepalive() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get isReloadNavigation() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get isHistoryNavigation() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ get signal() {
+ webidl.assertBranded(this, Request);
+ throw new TypeError("This property is not implemented.");
+ }
+
+ clone() {
+ webidl.assertBranded(this, Request);
+ if (this[_body] && this[_body].unusable()) {
+ throw new TypeError("Body is unusable.");
+ }
+ const newReq = cloneInnerRequest(this[_request]);
+ return fromInnerRequest(newReq, guardFromHeaders(this[_headers]));
+ }
+
+ get [Symbol.toStringTag]() {
+ return "Request";
+ }
+
+ [Symbol.for("Deno.customInspect")](inspect) {
+ const inner = {
+ bodyUsed: this.bodyUsed,
+ headers: this.headers,
+ method: this.method,
+ redirect: this.redirect,
+ url: this.url,
+ };
+ return `Request ${inspect(inner)}`;
+ }
+ }
+
+ mixinBody(Request, _body, _mimeType);
+
+ 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["ReferrerPolicy"] = webidl.createEnumConverter(
+ "ReferrerPolicy",
+ [
+ "",
+ "no-referrer",
+ "no-referrer-when-downgrade",
+ "same-origin",
+ "origin",
+ "strict-origin",
+ "origin-when-cross-origin",
+ "strict-origin-when-cross-origin",
+ "unsafe-url",
+ ],
+ );
+ webidl.converters["RequestMode"] = webidl.createEnumConverter("RequestMode", [
+ "navigate",
+ "same-origin",
+ "no-cors",
+ "cors",
+ ]);
+ webidl.converters["RequestCredentials"] = webidl.createEnumConverter(
+ "RequestCredentials",
+ [
+ "omit",
+ "same-origin",
+ "include",
+ ],
+ );
+ webidl.converters["RequestCache"] = webidl.createEnumConverter(
+ "RequestCache",
+ [
+ "default",
+ "no-store",
+ "reload",
+ "no-cache",
+ "force-cache",
+ "only-if-cached",
+ ],
+ );
+ 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: "referrer", converter: webidl.converters["USVString"] },
+ { key: "referrerPolicy", converter: webidl.converters["ReferrerPolicy"] },
+ { key: "mode", converter: webidl.converters["RequestMode"] },
+ {
+ key: "credentials",
+ converter: webidl.converters["RequestCredentials"],
+ },
+ { key: "cache", converter: webidl.converters["RequestCache"] },
+ { key: "redirect", converter: webidl.converters["RequestRedirect"] },
+ { key: "integrity", converter: webidl.converters["DOMString"] },
+ { key: "keepalive", converter: webidl.converters["boolean"] },
+ {
+ 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, guard) {
+ const request = webidl.createBranded(Request);
+ request[_request] = inner;
+ 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);