mirror of
https://github.com/denoland/deno.git
synced 2024-11-27 16:10:57 -05:00
524 lines
14 KiB
JavaScript
524 lines
14 KiB
JavaScript
// 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);
|