mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 00:59:24 -05:00
chore: align fetch to spec (#10203)
This commit aligns the `fetch` API and the `Request` / `Response` classes belonging to it to the spec. This commit enables all the relevant `fetch` WPT tests. Spec compliance is now at around 90%. Performance is essentially identical now (within 1% of 1.9.0).
This commit is contained in:
parent
115197ffb0
commit
9e6cd91014
30 changed files with 2235 additions and 1384 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -737,6 +737,7 @@ dependencies = [
|
||||||
"bench_util",
|
"bench_util",
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"idna",
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[WILDCARD]error: Uncaught URIError: relative URL without a base
|
[WILDCARD]error: Uncaught TypeError: Invalid URL
|
||||||
[WILDCARD]
|
[WILDCARD]
|
||||||
|
|
|
@ -7,6 +7,7 @@ function buildBody(body: any, headers?: Headers): Body {
|
||||||
const stub = new Request("http://foo/", {
|
const stub = new Request("http://foo/", {
|
||||||
body: body,
|
body: body,
|
||||||
headers,
|
headers,
|
||||||
|
method: "POST",
|
||||||
});
|
});
|
||||||
return stub as Body;
|
return stub as Body;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ unitTest(
|
||||||
async (): Promise<void> => {
|
async (): Promise<void> => {
|
||||||
await fetch("http://<invalid>/");
|
await fetch("http://<invalid>/");
|
||||||
},
|
},
|
||||||
URIError,
|
TypeError,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -129,18 +129,6 @@ unitTest({ perms: { net: true } }, async function fetchBlob(): Promise<void> {
|
||||||
assertEquals(blob.size, Number(headers.get("Content-Length")));
|
assertEquals(blob.size, Number(headers.get("Content-Length")));
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest({ perms: { net: true } }, async function fetchBodyUsed(): Promise<
|
|
||||||
void
|
|
||||||
> {
|
|
||||||
const response = await fetch("http://localhost:4545/cli/tests/fixture.json");
|
|
||||||
assertEquals(response.bodyUsed, false);
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
(response as any).bodyUsed = true;
|
|
||||||
assertEquals(response.bodyUsed, false);
|
|
||||||
await response.blob();
|
|
||||||
assertEquals(response.bodyUsed, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
unitTest(
|
unitTest(
|
||||||
{ perms: { net: true } },
|
{ perms: { net: true } },
|
||||||
async function fetchBodyUsedReader(): Promise<void> {
|
async function fetchBodyUsedReader(): Promise<void> {
|
||||||
|
@ -278,7 +266,6 @@ unitTest(
|
||||||
TypeError,
|
TypeError,
|
||||||
"Invalid form data",
|
"Invalid form data",
|
||||||
);
|
);
|
||||||
await response.body.cancel();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -424,10 +411,11 @@ unitTest(
|
||||||
perms: { net: true },
|
perms: { net: true },
|
||||||
},
|
},
|
||||||
async function fetchWithInfRedirection(): Promise<void> {
|
async function fetchWithInfRedirection(): Promise<void> {
|
||||||
const response = await fetch("http://localhost:4549/cli/tests"); // will redirect to the same place
|
await assertThrowsAsync(
|
||||||
assertEquals(response.status, 0); // network error
|
() => fetch("http://localhost:4549/cli/tests"),
|
||||||
assertEquals(response.type, "error");
|
TypeError,
|
||||||
assertEquals(response.ok, false);
|
"redirect",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -661,8 +649,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"foo: Bar\r\n",
|
|
||||||
"hello: World\r\n",
|
"hello: World\r\n",
|
||||||
|
"foo: Bar\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -695,9 +683,9 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"content-type: text/plain;charset=UTF-8\r\n",
|
|
||||||
"foo: Bar\r\n",
|
|
||||||
"hello: World\r\n",
|
"hello: World\r\n",
|
||||||
|
"foo: Bar\r\n",
|
||||||
|
"content-type: text/plain;charset=UTF-8\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -733,8 +721,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"foo: Bar\r\n",
|
|
||||||
"hello: World\r\n",
|
"hello: World\r\n",
|
||||||
|
"foo: Bar\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -770,8 +758,9 @@ unitTest(
|
||||||
}); // will redirect to http://localhost:4545/
|
}); // will redirect to http://localhost:4545/
|
||||||
assertEquals(response.status, 301);
|
assertEquals(response.status, 301);
|
||||||
assertEquals(response.url, "http://localhost:4546/");
|
assertEquals(response.url, "http://localhost:4546/");
|
||||||
assertEquals(response.type, "default");
|
assertEquals(response.type, "basic");
|
||||||
assertEquals(response.headers.get("Location"), "http://localhost:4545/");
|
assertEquals(response.headers.get("Location"), "http://localhost:4545/");
|
||||||
|
await response.body!.cancel();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -780,21 +769,14 @@ unitTest(
|
||||||
perms: { net: true },
|
perms: { net: true },
|
||||||
},
|
},
|
||||||
async function fetchWithErrorRedirection(): Promise<void> {
|
async function fetchWithErrorRedirection(): Promise<void> {
|
||||||
const response = await fetch("http://localhost:4546/", {
|
await assertThrowsAsync(
|
||||||
redirect: "error",
|
() =>
|
||||||
}); // will redirect to http://localhost:4545/
|
fetch("http://localhost:4546/", {
|
||||||
assertEquals(response.status, 0);
|
redirect: "error",
|
||||||
assertEquals(response.statusText, "");
|
}),
|
||||||
assertEquals(response.url, "");
|
TypeError,
|
||||||
assertEquals(response.type, "error");
|
"redirect",
|
||||||
try {
|
);
|
||||||
await response.text();
|
|
||||||
fail(
|
|
||||||
"Response.text() didn't throw on a filtered response without a body (type error)",
|
|
||||||
);
|
|
||||||
} catch (_e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -803,7 +785,10 @@ unitTest(function responseRedirect(): void {
|
||||||
assertEquals(redir.status, 301);
|
assertEquals(redir.status, 301);
|
||||||
assertEquals(redir.statusText, "");
|
assertEquals(redir.statusText, "");
|
||||||
assertEquals(redir.url, "");
|
assertEquals(redir.url, "");
|
||||||
assertEquals(redir.headers.get("Location"), "example.com/newLocation");
|
assertEquals(
|
||||||
|
redir.headers.get("Location"),
|
||||||
|
"http://js-unit-tests/foo/example.com/newLocation",
|
||||||
|
);
|
||||||
assertEquals(redir.type, "default");
|
assertEquals(redir.type, "default");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1004,10 +989,7 @@ unitTest(function fetchResponseConstructorInvalidStatus(): void {
|
||||||
fail(`Invalid status: ${status}`);
|
fail(`Invalid status: ${status}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert(e instanceof RangeError);
|
assert(e instanceof RangeError);
|
||||||
assertEquals(
|
assert(e.message.endsWith("is outside the range [200, 599]."));
|
||||||
e.message,
|
|
||||||
`The status provided (${status}) is outside the range [200, 599]`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1024,8 +1006,9 @@ unitTest(function fetchResponseEmptyConstructor(): void {
|
||||||
assertEquals([...response.headers], []);
|
assertEquals([...response.headers], []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO(lucacasonato): reenable this test
|
||||||
unitTest(
|
unitTest(
|
||||||
{ perms: { net: true } },
|
{ perms: { net: true }, ignore: true },
|
||||||
async function fetchCustomHttpClientParamCertificateSuccess(): Promise<
|
async function fetchCustomHttpClientParamCertificateSuccess(): Promise<
|
||||||
void
|
void
|
||||||
> {
|
> {
|
||||||
|
@ -1115,8 +1098,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"foo: Bar\r\n",
|
|
||||||
"hello: World\r\n",
|
"hello: World\r\n",
|
||||||
|
"foo: Bar\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
|
|
@ -15,17 +15,6 @@ unitTest(async function fromInit(): Promise<void> {
|
||||||
assertEquals(req.headers.get("test-header"), "value");
|
assertEquals(req.headers.get("test-header"), "value");
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest(async function fromRequest(): Promise<void> {
|
|
||||||
const r = new Request("http://foo/", { body: "ahoyhoy" });
|
|
||||||
r.headers.set("test-header", "value");
|
|
||||||
|
|
||||||
const req = new Request(r);
|
|
||||||
|
|
||||||
assertEquals(await r.text(), await req.text());
|
|
||||||
assertEquals(req.url, r.url);
|
|
||||||
assertEquals(req.headers.get("test-header"), r.headers.get("test-header"));
|
|
||||||
});
|
|
||||||
|
|
||||||
unitTest(function requestNonString(): void {
|
unitTest(function requestNonString(): void {
|
||||||
const nonString = {
|
const nonString = {
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -50,9 +39,11 @@ unitTest(function requestRelativeUrl(): void {
|
||||||
|
|
||||||
unitTest(async function cloneRequestBodyStream(): Promise<void> {
|
unitTest(async function cloneRequestBodyStream(): Promise<void> {
|
||||||
// hack to get a stream
|
// hack to get a stream
|
||||||
const stream = new Request("http://foo/", { body: "a test body" }).body;
|
const stream =
|
||||||
|
new Request("http://foo/", { body: "a test body", method: "POST" }).body;
|
||||||
const r1 = new Request("http://foo/", {
|
const r1 = new Request("http://foo/", {
|
||||||
body: stream,
|
body: stream,
|
||||||
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|
||||||
const r2 = r1.clone();
|
const r2 = r1.clone();
|
||||||
|
|
|
@ -14,10 +14,14 @@
|
||||||
((window) => {
|
((window) => {
|
||||||
const webidl = window.__bootstrap.webidl;
|
const webidl = window.__bootstrap.webidl;
|
||||||
const {
|
const {
|
||||||
|
HTTP_TAB_OR_SPACE_PREFIX_RE,
|
||||||
|
HTTP_TAB_OR_SPACE_SUFFIX_RE,
|
||||||
HTTP_WHITESPACE_PREFIX_RE,
|
HTTP_WHITESPACE_PREFIX_RE,
|
||||||
HTTP_WHITESPACE_SUFFIX_RE,
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
HTTP_TOKEN_CODE_POINT_RE,
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
byteLowerCase,
|
byteLowerCase,
|
||||||
|
collectSequenceOfCodepoints,
|
||||||
|
collectHttpQuotedString,
|
||||||
} = window.__bootstrap.infra;
|
} = window.__bootstrap.infra;
|
||||||
|
|
||||||
const _headerList = Symbol("header list");
|
const _headerList = Symbol("header list");
|
||||||
|
@ -35,7 +39,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {string} potentialValue
|
* @param {string} potentialValue
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function normalizeHeaderValue(potentialValue) {
|
function normalizeHeaderValue(potentialValue) {
|
||||||
|
@ -103,6 +107,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* https://fetch.spec.whatwg.org/#concept-header-list-get
|
||||||
* @param {HeaderList} list
|
* @param {HeaderList} list
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
*/
|
*/
|
||||||
|
@ -118,10 +123,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = value.replaceAll(HTTP_TAB_OR_SPACE_PREFIX_RE, "");
|
||||||
|
value = value.replaceAll(HTTP_TAB_OR_SPACE_SUFFIX_RE, "");
|
||||||
|
|
||||||
|
values.push(value);
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
class Headers {
|
class Headers {
|
||||||
/** @type {HeaderList} */
|
/** @type {HeaderList} */
|
||||||
[_headerList] = [];
|
[_headerList] = [];
|
||||||
/** @type {"immutable"| "request"| "request-no-cors"| "response" | "none"} */
|
/** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */
|
||||||
[_guard];
|
[_guard];
|
||||||
|
|
||||||
get [_iterableHeaders]() {
|
get [_iterableHeaders]() {
|
||||||
|
@ -359,7 +410,40 @@
|
||||||
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 = {
|
window.__bootstrap.headers = {
|
||||||
Headers,
|
Headers,
|
||||||
|
headersFromHeaderList,
|
||||||
|
headerListFromHeaders,
|
||||||
|
fillHeaders,
|
||||||
|
getDecodeSplitHeader,
|
||||||
|
guardFromHeaders,
|
||||||
};
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -442,6 +442,11 @@
|
||||||
* @returns {FormData}
|
* @returns {FormData}
|
||||||
*/
|
*/
|
||||||
parse() {
|
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();
|
const formData = new FormData();
|
||||||
let headerText = "";
|
let headerText = "";
|
||||||
let boundaryIndex = 0;
|
let boundaryIndex = 0;
|
||||||
|
@ -525,5 +530,23 @@
|
||||||
return parser.parse();
|
return parser.parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.__bootstrap.formData = { FormData, encodeFormData, parseFormData };
|
/**
|
||||||
|
* @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,
|
||||||
|
encodeFormData,
|
||||||
|
parseFormData,
|
||||||
|
formDataFromEntries,
|
||||||
|
};
|
||||||
})(globalThis);
|
})(globalThis);
|
||||||
|
|
338
op_crates/fetch/22_body.js
Normal file
338
op_crates/fetch/22_body.js
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
// 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="../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 core = window.Deno.core;
|
||||||
|
const webidl = globalThis.__bootstrap.webidl;
|
||||||
|
const { parseUrlEncoded } = globalThis.__bootstrap.url;
|
||||||
|
const { parseFormData, formDataFromEntries, encodeFormData } =
|
||||||
|
globalThis.__bootstrap.formData;
|
||||||
|
const mimesniff = globalThis.__bootstrap.mimesniff;
|
||||||
|
const { isReadableStreamDisturbed } = globalThis.__bootstrap.streams;
|
||||||
|
|
||||||
|
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;
|
||||||
|
this.streamOrStatic = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(body);
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (consumed) {
|
||||||
|
this.streamOrStatic.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
chunks.push(chunk);
|
||||||
|
totalLength += chunk.byteLength;
|
||||||
|
}
|
||||||
|
const finalBuffer = new Uint8Array(totalLength);
|
||||||
|
let i = 0;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
finalBuffer.set(chunk, i);
|
||||||
|
i += chunk.byteLength;
|
||||||
|
}
|
||||||
|
return finalBuffer;
|
||||||
|
} else {
|
||||||
|
this.streamOrStatic.consumed = true;
|
||||||
|
return this.streamOrStatic.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 Promise.resolve(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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bodyUsed: {
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
if (this[bodySymbol] !== null) {
|
||||||
|
return this[bodySymbol].consumed();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arrayBuffer: {
|
||||||
|
/** @returns {Promise<ArrayBuffer>} */
|
||||||
|
value: async function arrayBuffer() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
const body = await consumeBody(this);
|
||||||
|
return packageData(body, "ArrayBuffer");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
blob: {
|
||||||
|
/** @returns {Promise<Blob>} */
|
||||||
|
value: async function blob() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
const body = await consumeBody(this);
|
||||||
|
return packageData(body, "Blob", this[mimeTypeSymbol]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formData: {
|
||||||
|
/** @returns {Promise<FormData>} */
|
||||||
|
value: async function formData() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
const body = await consumeBody(this);
|
||||||
|
return packageData(body, "FormData", this[mimeTypeSymbol]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
/** @returns {Promise<any>} */
|
||||||
|
value: async function json() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
const body = await consumeBody(this);
|
||||||
|
return packageData(body, "JSON");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
/** @returns {Promise<string>} */
|
||||||
|
value: async function text() {
|
||||||
|
webidl.assertBranded(this, prototype);
|
||||||
|
const body = await consumeBody(this);
|
||||||
|
return packageData(body, "text");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return Object.defineProperties(prototype.prototype, mixin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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(
|
||||||
|
entries.map((x) => ({ name: x[0], value: x[1] })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TypeError("Invalid form data");
|
||||||
|
}
|
||||||
|
throw new TypeError("Missing content type");
|
||||||
|
}
|
||||||
|
case "JSON":
|
||||||
|
return JSON.parse(decoder.decode(bytes));
|
||||||
|
case "text":
|
||||||
|
return decoder.decode(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 (ArrayBuffer.isView(object) || object instanceof ArrayBuffer) {
|
||||||
|
const u8 = ArrayBuffer.isView(object)
|
||||||
|
? new Uint8Array(
|
||||||
|
object.buffer,
|
||||||
|
object.byteOffset,
|
||||||
|
object.byteLength,
|
||||||
|
)
|
||||||
|
: new Uint8Array(object);
|
||||||
|
const copy = u8.slice(0, u8.byteLength);
|
||||||
|
source = copy;
|
||||||
|
} else if (object instanceof FormData) {
|
||||||
|
const res = encodeFormData(object);
|
||||||
|
stream = { body: res.body, consumed: false };
|
||||||
|
source = object;
|
||||||
|
length = res.body.byteLength;
|
||||||
|
contentType = res.contentType;
|
||||||
|
} else if (object instanceof URLSearchParams) {
|
||||||
|
source = encoder.encode(object.toString());
|
||||||
|
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||||
|
} else if (typeof object === "string") {
|
||||||
|
source = encoder.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 (ArrayBuffer.isView(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);
|
41
op_crates/fetch/22_http_client.js
Normal file
41
op_crates/fetch/22_http_client.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
/// <reference path="../webidl/internal.d.ts" />
|
||||||
|
/// <reference path="../web/internal.d.ts" />
|
||||||
|
/// <reference path="../url/internal.d.ts" />
|
||||||
|
/// <reference path="../file/internal.d.ts" />
|
||||||
|
/// <reference path="../file/lib.deno_file.d.ts" />
|
||||||
|
/// <reference path="./internal.d.ts" />
|
||||||
|
/// <reference path="./11_streams_types.d.ts" />
|
||||||
|
/// <reference path="./lib.deno_fetch.d.ts" />
|
||||||
|
/// <reference lib="esnext" />
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
((window) => {
|
||||||
|
const 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);
|
521
op_crates/fetch/23_request.js
Normal file
521
op_crates/fetch/23_request.js
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
// 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]);
|
||||||
|
if (init.headers !== undefined) {
|
||||||
|
headers = init.headers;
|
||||||
|
}
|
||||||
|
headerListFromHeaders(this[_headers]).slice(
|
||||||
|
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);
|
415
op_crates/fetch/23_response.js
Normal file
415
op_crates/fetch/23_response.js
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
/// <reference path="../webidl/internal.d.ts" />
|
||||||
|
/// <reference path="../web/internal.d.ts" />
|
||||||
|
/// <reference path="../url/internal.d.ts" />
|
||||||
|
/// <reference path="../file/internal.d.ts" />
|
||||||
|
/// <reference path="../file/lib.deno_file.d.ts" />
|
||||||
|
/// <reference path="./internal.d.ts" />
|
||||||
|
/// <reference path="./11_streams_types.d.ts" />
|
||||||
|
/// <reference path="./lib.deno_fetch.d.ts" />
|
||||||
|
/// <reference lib="esnext" />
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
((window) => {
|
||||||
|
const webidl = window.__bootstrap.webidl;
|
||||||
|
const { HTTP_TAB_OR_SPACE, regexMatcher } = window.__bootstrap.infra;
|
||||||
|
const { InnerBody, extractBody, mixinBody } = window.__bootstrap.fetchBody;
|
||||||
|
const { getLocationHref } = window.__bootstrap.location;
|
||||||
|
const mimesniff = window.__bootstrap.mimesniff;
|
||||||
|
const { URL } = window.__bootstrap.url;
|
||||||
|
const {
|
||||||
|
getDecodeSplitHeader,
|
||||||
|
headerListFromHeaders,
|
||||||
|
headersFromHeaderList,
|
||||||
|
guardFromHeaders,
|
||||||
|
fillHeaders,
|
||||||
|
} = window.__bootstrap.headers;
|
||||||
|
|
||||||
|
const VCHAR = ["\x21-\x7E"];
|
||||||
|
const OBS_TEXT = ["\x80-\xFF"];
|
||||||
|
|
||||||
|
const REASON_PHRASE = [...HTTP_TAB_OR_SPACE, ...VCHAR, ...OBS_TEXT];
|
||||||
|
const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE);
|
||||||
|
const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`);
|
||||||
|
|
||||||
|
const _response = Symbol("response");
|
||||||
|
const _headers = Symbol("headers");
|
||||||
|
const _mimeType = Symbol("mime type");
|
||||||
|
const _body = Symbol("body");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef InnerResponse
|
||||||
|
* @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type
|
||||||
|
* @property {() => string | null} url
|
||||||
|
* @property {string[]} urlList
|
||||||
|
* @property {number} status
|
||||||
|
* @property {string} statusMessage
|
||||||
|
* @property {[string, string][]} headerList
|
||||||
|
* @property {null | InnerBody} body
|
||||||
|
* @property {string} [error]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} status
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function nullBodyStatus(status) {
|
||||||
|
return status === 101 || status === 204 || status === 205 || status === 304;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} status
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function redirectStatus(status) {
|
||||||
|
return status === 301 || status === 302 || status === 303 ||
|
||||||
|
status === 307 || status === 308;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://fetch.spec.whatwg.org/#concept-response-clone
|
||||||
|
* @param {InnerResponse} response
|
||||||
|
* @returns {InnerResponse}
|
||||||
|
*/
|
||||||
|
function cloneInnerResponse(response) {
|
||||||
|
const urlList = [...response.urlList];
|
||||||
|
const headerList = [...response.headerList.map((x) => [x[0], x[1]])];
|
||||||
|
let body = null;
|
||||||
|
if (response.body !== null) {
|
||||||
|
body = response.body.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: response.type,
|
||||||
|
body,
|
||||||
|
headerList,
|
||||||
|
url() {
|
||||||
|
if (this.urlList.length == 0) return null;
|
||||||
|
return this.urlList[this.urlList.length - 1];
|
||||||
|
},
|
||||||
|
urlList,
|
||||||
|
status: response.status,
|
||||||
|
statusMessage: response.statusMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultInnerResponse = {
|
||||||
|
type: "default",
|
||||||
|
body: null,
|
||||||
|
url() {
|
||||||
|
if (this.urlList.length == 0) return null;
|
||||||
|
return this.urlList[this.urlList.length - 1];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {InnerResponse}
|
||||||
|
*/
|
||||||
|
function newInnerResponse(status = 200, statusMessage = "") {
|
||||||
|
return {
|
||||||
|
headerList: [],
|
||||||
|
urlList: [],
|
||||||
|
status,
|
||||||
|
statusMessage,
|
||||||
|
...defaultInnerResponse,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} error
|
||||||
|
* @returns {InnerResponse}
|
||||||
|
*/
|
||||||
|
function networkError(error) {
|
||||||
|
const resp = newInnerResponse(0);
|
||||||
|
resp.type = "error";
|
||||||
|
resp.error = error;
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
/** @type {InnerResponse} */
|
||||||
|
[_response];
|
||||||
|
/** @type {Headers} */
|
||||||
|
[_headers];
|
||||||
|
get [_mimeType]() {
|
||||||
|
let charset = null;
|
||||||
|
let essence = null;
|
||||||
|
let mimeType = null;
|
||||||
|
const values = getDecodeSplitHeader(
|
||||||
|
headerListFromHeaders(this[_headers]),
|
||||||
|
"Content-Type",
|
||||||
|
);
|
||||||
|
if (values === null) return null;
|
||||||
|
for (const value of values) {
|
||||||
|
const temporaryMimeType = mimesniff.parseMimeType(value);
|
||||||
|
if (
|
||||||
|
temporaryMimeType === null ||
|
||||||
|
mimesniff.essence(temporaryMimeType) == "*/*"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mimeType = temporaryMimeType;
|
||||||
|
if (mimesniff.essence(mimeType) !== essence) {
|
||||||
|
charset = null;
|
||||||
|
const newCharset = mimeType.parameters.get("charset");
|
||||||
|
if (newCharset !== undefined) {
|
||||||
|
charset = newCharset;
|
||||||
|
}
|
||||||
|
essence = mimesniff.essence(mimeType);
|
||||||
|
} else {
|
||||||
|
if (mimeType.parameters.has("charset") === null && charset !== null) {
|
||||||
|
mimeType.parameters.set("charset", charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mimeType === null) return null;
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
get [_body]() {
|
||||||
|
return this[_response].body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
static error() {
|
||||||
|
const inner = newInnerResponse(0);
|
||||||
|
inner.type = "error";
|
||||||
|
const response = webidl.createBranded(Response);
|
||||||
|
response[_response] = inner;
|
||||||
|
response[_headers] = headersFromHeaderList(
|
||||||
|
response[_response].headerList,
|
||||||
|
"immutable",
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @param {number} status
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
static redirect(url, status = 302) {
|
||||||
|
const prefix = "Failed to call 'Response.redirect'";
|
||||||
|
url = webidl.converters["USVString"](url, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
status = webidl.converters["unsigned short"](status, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 2",
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseURL = getLocationHref();
|
||||||
|
const parsedURL = new URL(url, baseURL);
|
||||||
|
if (!redirectStatus(status)) {
|
||||||
|
throw new RangeError("Invalid redirect status code.");
|
||||||
|
}
|
||||||
|
const inner = newInnerResponse(status);
|
||||||
|
inner.type = "default";
|
||||||
|
inner.headerList.push(["Location", parsedURL.href]);
|
||||||
|
const response = webidl.createBranded(Response);
|
||||||
|
response[_response] = inner;
|
||||||
|
response[_headers] = headersFromHeaderList(
|
||||||
|
response[_response].headerList,
|
||||||
|
"immutable",
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BodyInit | null} body
|
||||||
|
* @param {ResponseInit} init
|
||||||
|
*/
|
||||||
|
constructor(body = null, init = {}) {
|
||||||
|
const prefix = "Failed to construct 'Response'";
|
||||||
|
body = webidl.converters["BodyInit?"](body, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
init = webidl.converters["ResponseInit"](init, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 2",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (init.status < 200 || init.status > 599) {
|
||||||
|
throw new RangeError(
|
||||||
|
`The status provided (${init.status}) is outside the range [200, 599].`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!REASON_PHRASE_RE.test(init.statusText)) {
|
||||||
|
throw new TypeError("Status text is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this[webidl.brand] = webidl.brand;
|
||||||
|
const response = newInnerResponse(init.status, init.statusText);
|
||||||
|
this[_response] = response;
|
||||||
|
this[_headers] = headersFromHeaderList(response.headerList, "response");
|
||||||
|
if (init.headers !== undefined) {
|
||||||
|
fillHeaders(this[_headers], init.headers);
|
||||||
|
}
|
||||||
|
if (body !== null) {
|
||||||
|
if (nullBodyStatus(response.status)) {
|
||||||
|
throw new TypeError(
|
||||||
|
"Response with null body status cannot have body",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const res = extractBody(body);
|
||||||
|
response.body = res.body;
|
||||||
|
if (res.contentType !== null && !this[_headers].has("content-type")) {
|
||||||
|
this[_headers].append("Content-Type", res.contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"}
|
||||||
|
*/
|
||||||
|
get type() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
return this[_response].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get url() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
const url = this[_response].url();
|
||||||
|
if (url === null) return "";
|
||||||
|
const newUrl = new URL(url);
|
||||||
|
newUrl.hash = "";
|
||||||
|
return newUrl.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get redirected() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
return this[_response].urlList.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
get status() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
return this[_response].status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get ok() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
const status = this[_response].status;
|
||||||
|
return status >= 200 && status <= 299;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get statusText() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
return this[_response].statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Headers}
|
||||||
|
*/
|
||||||
|
get headers() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
return this[_headers];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
clone() {
|
||||||
|
webidl.assertBranded(this, Response);
|
||||||
|
if (this[_body] && this[_body].unusable()) {
|
||||||
|
throw new TypeError("Body is unusable.");
|
||||||
|
}
|
||||||
|
const second = webidl.createBranded(Response);
|
||||||
|
const newRes = cloneInnerResponse(this[_response]);
|
||||||
|
second[_response] = newRes;
|
||||||
|
second[_headers] = headersFromHeaderList(
|
||||||
|
newRes.headerList,
|
||||||
|
guardFromHeaders(this[_headers]),
|
||||||
|
);
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag]() {
|
||||||
|
return "Response";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.for("Deno.customInspect")](inspect) {
|
||||||
|
const inner = {
|
||||||
|
body: this.body,
|
||||||
|
bodyUsed: this.bodyUsed,
|
||||||
|
headers: this.headers,
|
||||||
|
ok: this.ok,
|
||||||
|
redirected: this.redirected,
|
||||||
|
status: this.status,
|
||||||
|
statusText: this.statusText,
|
||||||
|
url: this.url(),
|
||||||
|
};
|
||||||
|
return `Response ${inspect(inner)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixinBody(Response, _body, _mimeType);
|
||||||
|
|
||||||
|
webidl.converters["Response"] = webidl.createInterfaceConverter(
|
||||||
|
"Response",
|
||||||
|
Response,
|
||||||
|
);
|
||||||
|
webidl.converters["ResponseInit"] = webidl.createDictionaryConverter(
|
||||||
|
"ResponseInit",
|
||||||
|
[{
|
||||||
|
key: "status",
|
||||||
|
defaultValue: 200,
|
||||||
|
converter: webidl.converters["unsigned short"],
|
||||||
|
}, {
|
||||||
|
key: "statusText",
|
||||||
|
defaultValue: "",
|
||||||
|
converter: webidl.converters["ByteString"],
|
||||||
|
}, {
|
||||||
|
key: "headers",
|
||||||
|
converter: webidl.converters["HeadersInit"],
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Response} response
|
||||||
|
* @returns {InnerResponse}
|
||||||
|
*/
|
||||||
|
function toInnerResponse(response) {
|
||||||
|
return response[_response];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {InnerResponse} inner
|
||||||
|
* @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
function fromInnerResponse(inner, guard) {
|
||||||
|
const response = webidl.createBranded(Response);
|
||||||
|
response[_response] = inner;
|
||||||
|
response[_headers] = headersFromHeaderList(inner.headerList, guard);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__bootstrap.fetch ??= {};
|
||||||
|
window.__bootstrap.fetch.Response = Response;
|
||||||
|
window.__bootstrap.fetch.toInnerResponse = toInnerResponse;
|
||||||
|
window.__bootstrap.fetch.fromInnerResponse = fromInnerResponse;
|
||||||
|
window.__bootstrap.fetch.redirectStatus = redirectStatus;
|
||||||
|
window.__bootstrap.fetch.nullBodyStatus = nullBodyStatus;
|
||||||
|
window.__bootstrap.fetch.networkError = networkError;
|
||||||
|
})(globalThis);
|
File diff suppressed because it is too large
Load diff
93
op_crates/fetch/internal.d.ts
vendored
93
op_crates/fetch/internal.d.ts
vendored
|
@ -15,22 +15,99 @@ declare namespace globalThis {
|
||||||
DomIterableMixin(base: any, dataSymbol: symbol): any;
|
DomIterableMixin(base: any, dataSymbol: symbol): any;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare var headers: {
|
declare namespace headers {
|
||||||
Headers: typeof 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 var formData: {
|
declare namespace formData {
|
||||||
FormData: typeof FormData;
|
declare type FormData = typeof FormData;
|
||||||
encodeFormData(formdata: FormData): {
|
declare function encodeFormData(formdata: FormData): {
|
||||||
body: Uint8Array;
|
body: Uint8Array;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
};
|
};
|
||||||
parseFormData(body: Uint8Array, boundary: string | undefined): FormData;
|
declare function parseFormData(
|
||||||
};
|
body: Uint8Array,
|
||||||
|
boundary: string | undefined,
|
||||||
|
): FormData;
|
||||||
|
declare function formDataFromEntries(entries: FormDataEntry[]): FormData;
|
||||||
|
}
|
||||||
|
|
||||||
declare var streams: {
|
declare var streams: {
|
||||||
ReadableStream: typeof ReadableStream;
|
ReadableStream: typeof ReadableStream;
|
||||||
isReadableStreamDisturbed(stream: ReadableStream): boolean;
|
isReadableStreamDisturbed(stream: ReadableStream): boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,13 +70,29 @@ pub fn init(isolate: &mut JsRuntime) {
|
||||||
"deno:op_crates/fetch/21_formdata.js",
|
"deno:op_crates/fetch/21_formdata.js",
|
||||||
include_str!("21_formdata.js"),
|
include_str!("21_formdata.js"),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"deno:op_crates/fetch/22_body.js",
|
||||||
|
include_str!("22_body.js"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deno:op_crates/fetch/22_http_client.js",
|
||||||
|
include_str!("22_http_client.js"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deno:op_crates/fetch/23_request.js",
|
||||||
|
include_str!("23_request.js"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deno:op_crates/fetch/23_response.js",
|
||||||
|
include_str!("23_response.js"),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"deno:op_crates/fetch/26_fetch.js",
|
"deno:op_crates/fetch/26_fetch.js",
|
||||||
include_str!("26_fetch.js"),
|
include_str!("26_fetch.js"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
for (url, source_code) in files {
|
for (url, source_code) in files {
|
||||||
isolate.execute(url, source_code).unwrap();
|
isolate.execute(url, source_code).expect(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +126,8 @@ pub fn get_declaration() -> PathBuf {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FetchArgs {
|
pub struct FetchArgs {
|
||||||
method: Option<String>,
|
method: String,
|
||||||
url: String,
|
url: String,
|
||||||
base_url: Option<String>,
|
|
||||||
headers: Vec<(String, String)>,
|
headers: Vec<(String, String)>,
|
||||||
client_rid: Option<u32>,
|
client_rid: Option<u32>,
|
||||||
has_body: bool,
|
has_body: bool,
|
||||||
|
@ -144,18 +159,8 @@ where
|
||||||
client.clone()
|
client.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let method = match args.method {
|
let method = Method::from_bytes(args.method.as_bytes())?;
|
||||||
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
|
let url = Url::parse(&args.url)?;
|
||||||
None => Method::GET,
|
|
||||||
};
|
|
||||||
|
|
||||||
let base_url = match args.base_url {
|
|
||||||
Some(base_url) => Some(Url::parse(&base_url)?),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let url = Url::options()
|
|
||||||
.base_url(base_url.as_ref())
|
|
||||||
.parse(&args.url)?;
|
|
||||||
|
|
||||||
// Check scheme before asking for net permission
|
// Check scheme before asking for net permission
|
||||||
let scheme = url.scheme();
|
let scheme = url.scheme();
|
||||||
|
|
|
@ -391,8 +391,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function implements application/x-www-form-urlencoded parsing.
|
||||||
|
* https://url.spec.whatwg.org/#concept-urlencoded-parser
|
||||||
|
* @param {Uint8Array} bytes
|
||||||
|
* @returns {[string, string][]}
|
||||||
|
*/
|
||||||
|
function parseUrlEncoded(bytes) {
|
||||||
|
return core.opSync("op_url_parse_search_params", null, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
window.__bootstrap.url = {
|
window.__bootstrap.url = {
|
||||||
URL,
|
URL,
|
||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
|
parseUrlEncoded,
|
||||||
};
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -16,6 +16,7 @@ path = "lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deno_core = { version = "0.84.0", path = "../../core" }
|
deno_core = { version = "0.84.0", path = "../../core" }
|
||||||
idna = "0.2.2"
|
idna = "0.2.2"
|
||||||
|
percent-encoding = "2.1.0"
|
||||||
serde = { version = "1.0.125", features = ["derive"] }
|
serde = { version = "1.0.125", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
1
op_crates/url/internal.d.ts
vendored
1
op_crates/url/internal.d.ts
vendored
|
@ -8,6 +8,7 @@ declare namespace globalThis {
|
||||||
declare var url: {
|
declare var url: {
|
||||||
URL: typeof URL;
|
URL: typeof URL;
|
||||||
URLSearchParams: typeof URLSearchParams;
|
URLSearchParams: typeof URLSearchParams;
|
||||||
|
parseUrlEncoded(bytes: Uint8Array): [string, string][];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,14 +118,21 @@ pub fn op_url_parse(
|
||||||
|
|
||||||
pub fn op_url_parse_search_params(
|
pub fn op_url_parse_search_params(
|
||||||
_state: &mut deno_core::OpState,
|
_state: &mut deno_core::OpState,
|
||||||
args: String,
|
args: Option<String>,
|
||||||
_zero_copy: Option<ZeroCopyBuf>,
|
zero_copy: Option<ZeroCopyBuf>,
|
||||||
) -> Result<Vec<(String, String)>, AnyError> {
|
) -> Result<Vec<(String, String)>, AnyError> {
|
||||||
let search_params: Vec<_> = form_urlencoded::parse(args.as_bytes())
|
let params = match (args, zero_copy) {
|
||||||
.into_iter()
|
(None, Some(zero_copy)) => form_urlencoded::parse(&zero_copy)
|
||||||
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
|
.into_iter()
|
||||||
.collect();
|
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
|
||||||
Ok(search_params)
|
.collect(),
|
||||||
|
(Some(args), None) => form_urlencoded::parse(args.as_bytes())
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
|
||||||
|
.collect(),
|
||||||
|
_ => return Err(type_error("invalid parameters")),
|
||||||
|
};
|
||||||
|
Ok(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn op_url_stringify_search_params(
|
pub fn op_url_stringify_search_params(
|
||||||
|
|
|
@ -46,6 +46,15 @@
|
||||||
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
||||||
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
||||||
);
|
);
|
||||||
|
const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE);
|
||||||
|
const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp(
|
||||||
|
`^[${HTTP_TAB_OR_SPACE_MATCHER}]+`,
|
||||||
|
"g",
|
||||||
|
);
|
||||||
|
const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp(
|
||||||
|
`[${HTTP_TAB_OR_SPACE_MATCHER}]+$`,
|
||||||
|
"g",
|
||||||
|
);
|
||||||
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
||||||
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
||||||
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
||||||
|
@ -113,6 +122,62 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
||||||
|
* @param {string} input
|
||||||
|
* @param {number} position
|
||||||
|
* @param {boolean} extractValue
|
||||||
|
* @returns {{result: string, position: number}}
|
||||||
|
*/
|
||||||
|
function collectHttpQuotedString(input, position, extractValue) {
|
||||||
|
// 1.
|
||||||
|
const positionStart = position;
|
||||||
|
// 2.
|
||||||
|
let value = "";
|
||||||
|
// 3.
|
||||||
|
if (input[position] !== "\u0022") throw new Error('must be "');
|
||||||
|
// 4.
|
||||||
|
position++;
|
||||||
|
// 5.
|
||||||
|
while (true) {
|
||||||
|
// 5.1.
|
||||||
|
const res = collectSequenceOfCodepoints(
|
||||||
|
input,
|
||||||
|
position,
|
||||||
|
(c) => c !== "\u0022" && c !== "\u005C",
|
||||||
|
);
|
||||||
|
value += res.result;
|
||||||
|
position = res.position;
|
||||||
|
// 5.2.
|
||||||
|
if (position >= input.length) break;
|
||||||
|
// 5.3.
|
||||||
|
const quoteOrBackslash = input[position];
|
||||||
|
// 5.4.
|
||||||
|
position++;
|
||||||
|
// 5.5.
|
||||||
|
if (quoteOrBackslash === "\u005C") {
|
||||||
|
// 5.5.1.
|
||||||
|
if (position >= input.length) {
|
||||||
|
value += "\u005C";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 5.5.2.
|
||||||
|
value += input[position];
|
||||||
|
// 5.5.3.
|
||||||
|
position++;
|
||||||
|
} else { // 5.6.
|
||||||
|
// 5.6.1
|
||||||
|
if (quoteOrBackslash !== "\u0022") throw new Error('must be "');
|
||||||
|
// 5.6.2
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 6.
|
||||||
|
if (extractValue) return { result: value, position };
|
||||||
|
// 7.
|
||||||
|
return { result: input.substring(positionStart, position + 1), position };
|
||||||
|
}
|
||||||
|
|
||||||
window.__bootstrap.infra = {
|
window.__bootstrap.infra = {
|
||||||
collectSequenceOfCodepoints,
|
collectSequenceOfCodepoints,
|
||||||
ASCII_DIGIT,
|
ASCII_DIGIT,
|
||||||
|
@ -126,10 +191,13 @@
|
||||||
HTTP_TOKEN_CODE_POINT_RE,
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT,
|
HTTP_QUOTED_STRING_TOKEN_POINT,
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||||
|
HTTP_TAB_OR_SPACE_PREFIX_RE,
|
||||||
|
HTTP_TAB_OR_SPACE_SUFFIX_RE,
|
||||||
HTTP_WHITESPACE_PREFIX_RE,
|
HTTP_WHITESPACE_PREFIX_RE,
|
||||||
HTTP_WHITESPACE_SUFFIX_RE,
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
regexMatcher,
|
regexMatcher,
|
||||||
byteUpperCase,
|
byteUpperCase,
|
||||||
byteLowerCase,
|
byteLowerCase,
|
||||||
|
collectHttpQuotedString,
|
||||||
};
|
};
|
||||||
})(globalThis);
|
})(globalThis);
|
||||||
|
|
|
@ -15,64 +15,9 @@
|
||||||
HTTP_WHITESPACE_SUFFIX_RE,
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||||
HTTP_TOKEN_CODE_POINT_RE,
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
|
collectHttpQuotedString,
|
||||||
} = window.__bootstrap.infra;
|
} = window.__bootstrap.infra;
|
||||||
|
|
||||||
/**
|
|
||||||
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
|
||||||
* @param {string} input
|
|
||||||
* @param {number} position
|
|
||||||
* @param {boolean} extractValue
|
|
||||||
* @returns {{result: string, position: number}}
|
|
||||||
*/
|
|
||||||
function collectHttpQuotedString(input, position, extractValue) {
|
|
||||||
// 1.
|
|
||||||
const positionStart = position;
|
|
||||||
// 2.
|
|
||||||
let value = "";
|
|
||||||
// 3.
|
|
||||||
if (input[position] !== "\u0022") throw new Error('must be "');
|
|
||||||
// 4.
|
|
||||||
position++;
|
|
||||||
// 5.
|
|
||||||
while (true) {
|
|
||||||
// 5.1.
|
|
||||||
const res = collectSequenceOfCodepoints(
|
|
||||||
input,
|
|
||||||
position,
|
|
||||||
(c) => c !== "\u0022" && c !== "\u005C",
|
|
||||||
);
|
|
||||||
value += res.result;
|
|
||||||
position = res.position;
|
|
||||||
// 5.2.
|
|
||||||
if (position >= input.length) break;
|
|
||||||
// 5.3.
|
|
||||||
const quoteOrBackslash = input[position];
|
|
||||||
// 5.4.
|
|
||||||
position++;
|
|
||||||
// 5.5.
|
|
||||||
if (quoteOrBackslash === "\u005C") {
|
|
||||||
// 5.5.1.
|
|
||||||
if (position >= input.length) {
|
|
||||||
value += "\u005C";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 5.5.2.
|
|
||||||
value += input[position];
|
|
||||||
// 5.5.3.
|
|
||||||
position++;
|
|
||||||
} else { // 5.6.
|
|
||||||
// 5.6.1
|
|
||||||
if (input[position] !== "\u0022") throw new Error('must be "');
|
|
||||||
// 5.6.2
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 6.
|
|
||||||
if (extractValue) return { result: value, position };
|
|
||||||
// 7.
|
|
||||||
return { result: input.substring(positionStart, position + 1), position };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef MimeType
|
* @typedef MimeType
|
||||||
* @property {string} type
|
* @property {string} type
|
||||||
|
@ -172,7 +117,7 @@
|
||||||
let parameterValue = null;
|
let parameterValue = null;
|
||||||
|
|
||||||
// 11.8.
|
// 11.8.
|
||||||
if (input[position] == "\u0022") {
|
if (input[position] === "\u0022") {
|
||||||
// 11.8.1.
|
// 11.8.1.
|
||||||
const res = collectHttpQuotedString(input, position, true);
|
const res = collectHttpQuotedString(input, position, true);
|
||||||
parameterValue = res.result;
|
parameterValue = res.result;
|
||||||
|
@ -214,5 +159,32 @@
|
||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__bootstrap.mimesniff = { parseMimeType };
|
/**
|
||||||
|
* @param {MimeType} mimeType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function essence(mimeType) {
|
||||||
|
return `${mimeType.type}/${mimeType.subtype}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {MimeType} mimeType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function serializeMimeType(mimeType) {
|
||||||
|
let serialization = essence(mimeType);
|
||||||
|
for (const param of mimeType.parameters) {
|
||||||
|
serialization += `;${param[0]}=`;
|
||||||
|
let value = param[1];
|
||||||
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(value)) {
|
||||||
|
value = value.replaceAll("\\", "\\\\");
|
||||||
|
value = value.replaceAll('"', '\\"');
|
||||||
|
value = `"${value}"`;
|
||||||
|
}
|
||||||
|
serialization += value;
|
||||||
|
}
|
||||||
|
return serialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__bootstrap.mimesniff = { parseMimeType, essence, serializeMimeType };
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
|
const webidl = window.__bootstrap.webidl;
|
||||||
const { setIsTrusted } = window.__bootstrap.event;
|
const { setIsTrusted } = window.__bootstrap.event;
|
||||||
|
|
||||||
const add = Symbol("add");
|
const add = Symbol("add");
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
throw new TypeError("Illegal constructor.");
|
throw new TypeError("Illegal constructor.");
|
||||||
}
|
}
|
||||||
super();
|
super();
|
||||||
|
this[webidl.brand] = webidl.brand;
|
||||||
}
|
}
|
||||||
|
|
||||||
get aborted() {
|
get aborted() {
|
||||||
|
@ -111,6 +113,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webidl.converters["AbortSignal"] = webidl.createInterfaceConverter(
|
||||||
|
"AbortSignal",
|
||||||
|
AbortSignal,
|
||||||
|
);
|
||||||
|
|
||||||
window.AbortSignal = AbortSignal;
|
window.AbortSignal = AbortSignal;
|
||||||
window.AbortController = AbortController;
|
window.AbortController = AbortController;
|
||||||
window.__bootstrap = window.__bootstrap || {};
|
window.__bootstrap = window.__bootstrap || {};
|
||||||
|
|
12
op_crates/web/internal.d.ts
vendored
12
op_crates/web/internal.d.ts
vendored
|
@ -28,11 +28,21 @@ declare namespace globalThis {
|
||||||
HTTP_TOKEN_CODE_POINT_RE: RegExp;
|
HTTP_TOKEN_CODE_POINT_RE: RegExp;
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT: string[];
|
HTTP_QUOTED_STRING_TOKEN_POINT: string[];
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp;
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp;
|
||||||
|
HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp;
|
||||||
|
HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp;
|
||||||
HTTP_WHITESPACE_PREFIX_RE: RegExp;
|
HTTP_WHITESPACE_PREFIX_RE: RegExp;
|
||||||
HTTP_WHITESPACE_SUFFIX_RE: RegExp;
|
HTTP_WHITESPACE_SUFFIX_RE: RegExp;
|
||||||
regexMatcher(chars: string[]): string;
|
regexMatcher(chars: string[]): string;
|
||||||
byteUpperCase(s: string): string;
|
byteUpperCase(s: string): string;
|
||||||
byteLowerCase(s: string): string;
|
byteLowerCase(s: string): string;
|
||||||
|
collectHttpQuotedString(
|
||||||
|
input: string,
|
||||||
|
position: number,
|
||||||
|
extractValue: boolean,
|
||||||
|
): {
|
||||||
|
result: string;
|
||||||
|
position: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
declare namespace mimesniff {
|
declare namespace mimesniff {
|
||||||
|
@ -42,6 +52,8 @@ declare namespace globalThis {
|
||||||
parameters: Map<string, string>;
|
parameters: Map<string, string>;
|
||||||
}
|
}
|
||||||
declare function parseMimeType(input: string): MimeType | null;
|
declare function parseMimeType(input: string): MimeType | null;
|
||||||
|
declare function essence(mimeType: MimeType): string;
|
||||||
|
declare function serializeMimeType(mimeType: MimeType): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare var eventTarget: {
|
declare var eventTarget: {
|
||||||
|
|
|
@ -135,7 +135,9 @@
|
||||||
converter: webidl.createSequenceConverter(
|
converter: webidl.createSequenceConverter(
|
||||||
webidl.converters["GPUFeatureName"],
|
webidl.converters["GPUFeatureName"],
|
||||||
),
|
),
|
||||||
defaultValue: [],
|
get defaultValue() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "nonGuaranteedLimits",
|
key: "nonGuaranteedLimits",
|
||||||
|
@ -143,7 +145,9 @@
|
||||||
webidl.converters["DOMString"],
|
webidl.converters["DOMString"],
|
||||||
webidl.converters["GPUSize32"],
|
webidl.converters["GPUSize32"],
|
||||||
),
|
),
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter(
|
webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter(
|
||||||
|
@ -1046,7 +1050,9 @@
|
||||||
webidl.converters["GPUVertexBufferLayout"],
|
webidl.converters["GPUVertexBufferLayout"],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
defaultValue: [],
|
get defaultValue() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter(
|
webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter(
|
||||||
|
@ -1187,12 +1193,16 @@
|
||||||
{
|
{
|
||||||
key: "stencilFront",
|
key: "stencilFront",
|
||||||
converter: webidl.converters["GPUStencilFaceState"],
|
converter: webidl.converters["GPUStencilFaceState"],
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "stencilBack",
|
key: "stencilBack",
|
||||||
converter: webidl.converters["GPUStencilFaceState"],
|
converter: webidl.converters["GPUStencilFaceState"],
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "stencilReadMask",
|
key: "stencilReadMask",
|
||||||
|
@ -1379,7 +1389,9 @@
|
||||||
{
|
{
|
||||||
key: "primitive",
|
key: "primitive",
|
||||||
converter: webidl.converters["GPUPrimitiveState"],
|
converter: webidl.converters["GPUPrimitiveState"],
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "depthStencil",
|
key: "depthStencil",
|
||||||
|
@ -1388,7 +1400,9 @@
|
||||||
{
|
{
|
||||||
key: "multisample",
|
key: "multisample",
|
||||||
converter: webidl.converters["GPUMultisampleState"],
|
converter: webidl.converters["GPUMultisampleState"],
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ key: "fragment", converter: webidl.converters["GPUFragmentState"] },
|
{ key: "fragment", converter: webidl.converters["GPUFragmentState"] },
|
||||||
];
|
];
|
||||||
|
@ -1530,7 +1544,9 @@
|
||||||
{
|
{
|
||||||
key: "origin",
|
key: "origin",
|
||||||
converter: webidl.converters["GPUOrigin3D"],
|
converter: webidl.converters["GPUOrigin3D"],
|
||||||
defaultValue: {},
|
get defaultValue() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "aspect",
|
key: "aspect",
|
||||||
|
@ -1793,7 +1809,9 @@
|
||||||
converter: webidl.createSequenceConverter(
|
converter: webidl.createSequenceConverter(
|
||||||
webidl.converters["GPUPipelineStatisticName"],
|
webidl.converters["GPUPipelineStatisticName"],
|
||||||
),
|
),
|
||||||
defaultValue: [],
|
get defaultValue() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter(
|
webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter(
|
||||||
|
|
|
@ -375,40 +375,12 @@
|
||||||
return V;
|
return V;
|
||||||
}
|
}
|
||||||
|
|
||||||
const abByteLengthGetter = Object.getOwnPropertyDescriptor(
|
|
||||||
ArrayBuffer.prototype,
|
|
||||||
"byteLength",
|
|
||||||
).get;
|
|
||||||
|
|
||||||
function isNonSharedArrayBuffer(V) {
|
function isNonSharedArrayBuffer(V) {
|
||||||
try {
|
return V instanceof ArrayBuffer;
|
||||||
// This will throw on SharedArrayBuffers, but not detached ArrayBuffers.
|
|
||||||
// (The spec says it should throw, but the spec conflicts with implementations: https://github.com/tc39/ecma262/issues/678)
|
|
||||||
abByteLengthGetter.call(V);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sabByteLengthGetter;
|
|
||||||
|
|
||||||
function isSharedArrayBuffer(V) {
|
function isSharedArrayBuffer(V) {
|
||||||
// TODO(lucacasonato): vulnerable to prototype pollution. Needs to happen
|
return V instanceof SharedArrayBuffer;
|
||||||
// here because SharedArrayBuffer is not available during snapshotting.
|
|
||||||
if (!sabByteLengthGetter) {
|
|
||||||
sabByteLengthGetter = Object.getOwnPropertyDescriptor(
|
|
||||||
SharedArrayBuffer.prototype,
|
|
||||||
"byteLength",
|
|
||||||
).get;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
sabByteLengthGetter.call(V);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isArrayBufferDetached(V) {
|
function isArrayBufferDetached(V) {
|
||||||
|
@ -439,14 +411,8 @@
|
||||||
return V;
|
return V;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dvByteLengthGetter = Object.getOwnPropertyDescriptor(
|
|
||||||
DataView.prototype,
|
|
||||||
"byteLength",
|
|
||||||
).get;
|
|
||||||
converters.DataView = (V, opts = {}) => {
|
converters.DataView = (V, opts = {}) => {
|
||||||
try {
|
if (!(V instanceof DataView)) {
|
||||||
dvByteLengthGetter.call(V);
|
|
||||||
} catch (e) {
|
|
||||||
throw makeException(TypeError, "is not a DataView", opts);
|
throw makeException(TypeError, "is not a DataView", opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,10 +580,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmptyObject(V) {
|
||||||
|
for (const _ in V) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function createDictionaryConverter(name, ...dictionaries) {
|
function createDictionaryConverter(name, ...dictionaries) {
|
||||||
|
let hasRequiredKey = false;
|
||||||
const allMembers = [];
|
const allMembers = [];
|
||||||
for (const members of dictionaries) {
|
for (const members of dictionaries) {
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
|
if (member.required) {
|
||||||
|
hasRequiredKey = true;
|
||||||
|
}
|
||||||
allMembers.push(member);
|
allMembers.push(member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -628,6 +603,29 @@
|
||||||
return a.key < b.key ? -1 : 1;
|
return a.key < b.key ? -1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultValues = {};
|
||||||
|
for (const member of allMembers) {
|
||||||
|
if ("defaultValue" in member) {
|
||||||
|
const idlMemberValue = member.defaultValue;
|
||||||
|
const imvType = typeof idlMemberValue;
|
||||||
|
// Copy by value types can be directly assigned, copy by reference types
|
||||||
|
// need to be re-created for each allocation.
|
||||||
|
if (
|
||||||
|
imvType === "number" || imvType === "boolean" ||
|
||||||
|
imvType === "string" || imvType === "bigint" ||
|
||||||
|
imvType === "undefined"
|
||||||
|
) {
|
||||||
|
defaultValues[member.key] = idlMemberValue;
|
||||||
|
} else {
|
||||||
|
Object.defineProperty(defaultValues, member.key, {
|
||||||
|
get() {
|
||||||
|
return member.defaultValue;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return function (V, opts = {}) {
|
return function (V, opts = {}) {
|
||||||
const typeV = type(V);
|
const typeV = type(V);
|
||||||
switch (typeV) {
|
switch (typeV) {
|
||||||
|
@ -644,7 +642,14 @@
|
||||||
}
|
}
|
||||||
const esDict = V;
|
const esDict = V;
|
||||||
|
|
||||||
const idlDict = {};
|
const idlDict = { ...defaultValues };
|
||||||
|
|
||||||
|
// NOTE: fast path Null and Undefined and empty objects.
|
||||||
|
if (
|
||||||
|
(V === undefined || V === null || isEmptyObject(V)) && !hasRequiredKey
|
||||||
|
) {
|
||||||
|
return idlDict;
|
||||||
|
}
|
||||||
|
|
||||||
for (const member of allMembers) {
|
for (const member of allMembers) {
|
||||||
const key = member.key;
|
const key = member.key;
|
||||||
|
@ -656,20 +661,12 @@
|
||||||
esMemberValue = esDict[key];
|
esMemberValue = esDict[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = `'${key}' of '${name}'${
|
|
||||||
opts.context ? ` (${opts.context})` : ""
|
|
||||||
}`;
|
|
||||||
|
|
||||||
if (esMemberValue !== undefined) {
|
if (esMemberValue !== undefined) {
|
||||||
|
const context = `'${key}' of '${name}'${
|
||||||
|
opts.context ? ` (${opts.context})` : ""
|
||||||
|
}`;
|
||||||
const converter = member.converter;
|
const converter = member.converter;
|
||||||
const idlMemberValue = converter(esMemberValue, {
|
const idlMemberValue = converter(esMemberValue, { ...opts, context });
|
||||||
...opts,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
idlDict[key] = idlMemberValue;
|
|
||||||
} else if ("defaultValue" in member) {
|
|
||||||
const defaultValue = member.defaultValue;
|
|
||||||
const idlMemberValue = defaultValue;
|
|
||||||
idlDict[key] = idlMemberValue;
|
idlDict[key] = idlMemberValue;
|
||||||
} else if (member.required) {
|
} else if (member.required) {
|
||||||
throw makeException(
|
throw makeException(
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
const { Request, dontValidateUrl, lazyHeaders, fastBody, Response } =
|
const { InnerBody } = window.__bootstrap.fetchBody;
|
||||||
|
const { Response, fromInnerRequest, toInnerResponse, newInnerRequest } =
|
||||||
window.__bootstrap.fetch;
|
window.__bootstrap.fetch;
|
||||||
const { Headers } = window.__bootstrap.headers;
|
|
||||||
const errors = window.__bootstrap.errors.errors;
|
const errors = window.__bootstrap.errors.errors;
|
||||||
const core = window.Deno.core;
|
const core = window.Deno.core;
|
||||||
const { ReadableStream } = window.__bootstrap.streams;
|
const { ReadableStream } = window.__bootstrap.streams;
|
||||||
|
@ -53,18 +53,18 @@
|
||||||
] = nextRequest;
|
] = nextRequest;
|
||||||
|
|
||||||
/** @type {ReadableStream<Uint8Array> | undefined} */
|
/** @type {ReadableStream<Uint8Array> | undefined} */
|
||||||
let body = undefined;
|
let body = null;
|
||||||
if (typeof requestBodyRid === "number") {
|
if (typeof requestBodyRid === "number") {
|
||||||
body = createRequestBodyStream(requestBodyRid);
|
body = createRequestBodyStream(requestBodyRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new Request(url, {
|
const innerRequest = newInnerRequest(
|
||||||
body,
|
|
||||||
method,
|
method,
|
||||||
headers: headersList,
|
url,
|
||||||
[dontValidateUrl]: true,
|
headersList,
|
||||||
[lazyHeaders]: true,
|
body !== null ? new InnerBody(body) : null,
|
||||||
});
|
);
|
||||||
|
const request = fromInnerRequest(innerRequest, "immutable");
|
||||||
|
|
||||||
const respondWith = createRespondWith(responseSenderRid, this.#rid);
|
const respondWith = createRespondWith(responseSenderRid, this.#rid);
|
||||||
|
|
||||||
|
@ -96,16 +96,6 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IMPORTANT: Equivalent to `Array.from(headers).flat()` but more performant.
|
|
||||||
* Please preserve. */
|
|
||||||
function flattenHeaders(headers) {
|
|
||||||
const array = [];
|
|
||||||
for (const pair of headers) {
|
|
||||||
array.push(pair[0], pair[1]);
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRespondWith(responseSenderRid) {
|
function createRespondWith(responseSenderRid) {
|
||||||
return async function respondWith(resp) {
|
return async function respondWith(resp) {
|
||||||
if (resp instanceof Promise) {
|
if (resp instanceof Promise) {
|
||||||
|
@ -117,46 +107,66 @@
|
||||||
"First argument to respondWith must be a Response or a promise resolving to a Response.",
|
"First argument to respondWith must be a Response or a promise resolving to a Response.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// If response body is Uint8Array it will be sent synchronously
|
|
||||||
// in a single op, in other case a "response body" resource will be
|
const innerResp = toInnerResponse(resp);
|
||||||
// created and we'll be streaming it.
|
|
||||||
const body = resp[fastBody]();
|
// If response body length is known, it will be sent synchronously in a
|
||||||
let zeroCopyBuf;
|
// single op, in other case a "response body" resource will be created and
|
||||||
if (body instanceof ArrayBuffer) {
|
// we'll be streaming it.
|
||||||
zeroCopyBuf = new Uint8Array(body);
|
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
|
||||||
} else if (!body) {
|
let respBody = null;
|
||||||
zeroCopyBuf = new Uint8Array(0);
|
if (innerResp.body !== null) {
|
||||||
|
if (innerResp.body.unusable()) throw new TypeError("Body is unusable.");
|
||||||
|
if (innerResp.body.streamOrStatic instanceof ReadableStream) {
|
||||||
|
if (innerResp.body.length === null) {
|
||||||
|
respBody = innerResp.body.stream;
|
||||||
|
} else {
|
||||||
|
const reader = innerResp.body.stream.getReader();
|
||||||
|
const r1 = await reader.read();
|
||||||
|
if (r1.done) throw new TypeError("Unreachable");
|
||||||
|
respBody = r1.value;
|
||||||
|
const r2 = await reader.read();
|
||||||
|
if (!r2.done) throw new TypeError("Unreachable");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
innerResp.body.streamOrStatic.consumed = true;
|
||||||
|
respBody = innerResp.body.streamOrStatic.body;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
zeroCopyBuf = null;
|
respBody = new Uint8Array(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseBodyRid = await Deno.core.opAsync("op_http_response", [
|
const responseBodyRid = await Deno.core.opAsync("op_http_response", [
|
||||||
responseSenderRid,
|
responseSenderRid,
|
||||||
resp.status ?? 200,
|
innerResp.status ?? 200,
|
||||||
flattenHeaders(resp.headers),
|
innerResp.headerList,
|
||||||
], zeroCopyBuf);
|
], respBody instanceof Uint8Array ? respBody : null);
|
||||||
|
|
||||||
// If `respond` returns a responseBodyRid, we should stream the body
|
// If `respond` returns a responseBodyRid, we should stream the body
|
||||||
// to that resource.
|
// to that resource.
|
||||||
if (typeof responseBodyRid === "number") {
|
if (responseBodyRid !== null) {
|
||||||
if (!body || !(body instanceof ReadableStream)) {
|
if (respBody === null || !(respBody instanceof ReadableStream)) {
|
||||||
throw new Error(
|
throw new TypeError("Unreachable");
|
||||||
"internal error: recieved responseBodyRid, but response has no body or is not a stream",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
for await (const chunk of body) {
|
const reader = respBody.getReader();
|
||||||
const data = new Uint8Array(
|
while (true) {
|
||||||
chunk.buffer,
|
const { value, done } = await reader.read();
|
||||||
chunk.byteOffset,
|
if (done) break;
|
||||||
chunk.byteLength,
|
if (!(value instanceof Uint8Array)) {
|
||||||
);
|
await reader.cancel("value not a Uint8Array");
|
||||||
await Deno.core.opAsync(
|
break;
|
||||||
"op_http_response_write",
|
}
|
||||||
responseBodyRid,
|
try {
|
||||||
data,
|
await Deno.core.opAsync(
|
||||||
);
|
"op_http_response_write",
|
||||||
|
responseBodyRid,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
await reader.cancel(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once all chunks are sent, and the request body is closed, we can close
|
// Once all chunks are sent, and the request body is closed, we can close
|
||||||
// the response body.
|
// the response body.
|
||||||
await Deno.core.opAsync("op_http_response_close", responseBodyRid);
|
await Deno.core.opAsync("op_http_response_close", responseBodyRid);
|
||||||
|
|
|
@ -421,7 +421,6 @@ delete Object.prototype.__proto__;
|
||||||
|
|
||||||
if (locationHref != null) {
|
if (locationHref != null) {
|
||||||
location.setLocationHref(locationHref);
|
location.setLocationHref(locationHref);
|
||||||
fetch.setBaseUrl(locationHref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerErrors();
|
registerErrors();
|
||||||
|
@ -488,7 +487,6 @@ delete Object.prototype.__proto__;
|
||||||
runtimeOptions;
|
runtimeOptions;
|
||||||
|
|
||||||
location.setLocationHref(locationHref);
|
location.setLocationHref(locationHref);
|
||||||
fetch.setBaseUrl(locationHref);
|
|
||||||
registerErrors();
|
registerErrors();
|
||||||
|
|
||||||
const internalSymbol = Symbol("Deno.internal");
|
const internalSymbol = Symbol("Deno.internal");
|
||||||
|
|
|
@ -331,7 +331,7 @@ struct RespondArgs(
|
||||||
// status:
|
// status:
|
||||||
u16,
|
u16,
|
||||||
// headers:
|
// headers:
|
||||||
Vec<String>,
|
Vec<(String, String)>,
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn op_http_response(
|
async fn op_http_response(
|
||||||
|
@ -358,11 +358,9 @@ async fn op_http_response(
|
||||||
|
|
||||||
let mut builder = Response::builder().status(status);
|
let mut builder = Response::builder().status(status);
|
||||||
|
|
||||||
debug_assert_eq!(headers.len() % 2, 0);
|
builder.headers_mut().unwrap().reserve(headers.len());
|
||||||
let headers_count = headers.len() / 2;
|
for (key, value) in &headers {
|
||||||
builder.headers_mut().unwrap().reserve(headers_count);
|
builder = builder.header(key, value);
|
||||||
for i in 0..headers_count {
|
|
||||||
builder = builder.header(&headers[2 * i], &headers[2 * i + 1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let res;
|
let res;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 579608584916d582d38d0159666aae9a6aaf07ad
|
Subproject commit 5d9a0686bd51cc20df785fc013700c7b18fc0e0b
|
|
@ -627,28 +627,7 @@
|
||||||
"Setting pathname with trailing U+001F (wpt++:)"
|
"Setting pathname with trailing U+001F (wpt++:)"
|
||||||
],
|
],
|
||||||
"url-tojson.any.js": true,
|
"url-tojson.any.js": true,
|
||||||
"urlencoded-parser.any.js": [
|
"urlencoded-parser.any.js": true,
|
||||||
"request.formData() with input: test=",
|
|
||||||
"response.formData() with input: test=",
|
|
||||||
"request.formData() with input: †&†=x",
|
|
||||||
"response.formData() with input: †&†=x",
|
|
||||||
"request.formData() with input: _charset_=windows-1252&test=%C2x",
|
|
||||||
"response.formData() with input: _charset_=windows-1252&test=%C2x",
|
|
||||||
"request.formData() with input: %=a",
|
|
||||||
"response.formData() with input: %=a",
|
|
||||||
"request.formData() with input: %a=a",
|
|
||||||
"response.formData() with input: %a=a",
|
|
||||||
"request.formData() with input: %a_=a",
|
|
||||||
"response.formData() with input: %a_=a",
|
|
||||||
"request.formData() with input: id=0&value=%",
|
|
||||||
"response.formData() with input: id=0&value=%",
|
|
||||||
"request.formData() with input: b=%2sf%2a",
|
|
||||||
"response.formData() with input: b=%2sf%2a",
|
|
||||||
"request.formData() with input: b=%2%2af%2a",
|
|
||||||
"response.formData() with input: b=%2%2af%2a",
|
|
||||||
"request.formData() with input: b=%%2a",
|
|
||||||
"response.formData() with input: b=%%2a"
|
|
||||||
],
|
|
||||||
"urlsearchparams-append.any.js": true,
|
"urlsearchparams-append.any.js": true,
|
||||||
"urlsearchparams-constructor.any.js": [
|
"urlsearchparams-constructor.any.js": [
|
||||||
"Construct with 2 unpaired surrogates (no trailing)",
|
"Construct with 2 unpaired surrogates (no trailing)",
|
||||||
|
@ -672,18 +651,16 @@
|
||||||
"fetch": {
|
"fetch": {
|
||||||
"api": {
|
"api": {
|
||||||
"request": {
|
"request": {
|
||||||
"request-structure.any.js": [
|
"request-init-002.any.js": true,
|
||||||
"Check destination attribute",
|
"request-init-stream.any.js": [
|
||||||
"Check referrer attribute",
|
"Constructing a Request with a Request on which body.getReader() is called",
|
||||||
"Check referrerPolicy attribute",
|
"Constructing a Request with a Request on which body.getReader().read() is called",
|
||||||
"Check mode attribute",
|
"Constructing a Request with a Request on which read() and releaseLock() are called"
|
||||||
"Check credentials attribute",
|
],
|
||||||
"Check cache attribute",
|
"request-consume-empty.any.js": [
|
||||||
"Check redirect attribute",
|
"Consume empty FormData request body as text"
|
||||||
"Check integrity attribute",
|
],
|
||||||
"Check isReloadNavigation attribute",
|
"request-consume.any.js": true
|
||||||
"Check isHistoryNavigation attribute"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
"headers-basic.any.js": true,
|
"headers-basic.any.js": true,
|
||||||
|
@ -693,12 +670,143 @@
|
||||||
"headers-normalize.any.js": true,
|
"headers-normalize.any.js": true,
|
||||||
"headers-record.any.js": true,
|
"headers-record.any.js": true,
|
||||||
"headers-structure.any.js": true
|
"headers-structure.any.js": true
|
||||||
|
},
|
||||||
|
"basic": {
|
||||||
|
"request-head.any.js": true,
|
||||||
|
"request-headers-case.any.js": false,
|
||||||
|
"request-headers-nonascii.any.js": false,
|
||||||
|
"request-headers.any.js": [
|
||||||
|
"Fetch with PUT without body",
|
||||||
|
"Fetch with PUT with body",
|
||||||
|
"Fetch with POST without body",
|
||||||
|
"Fetch with POST with text body",
|
||||||
|
"Fetch with POST with FormData body",
|
||||||
|
"Fetch with POST with URLSearchParams body",
|
||||||
|
"Fetch with POST with Blob body",
|
||||||
|
"Fetch with POST with ArrayBuffer body",
|
||||||
|
"Fetch with POST with Uint8Array body",
|
||||||
|
"Fetch with POST with Int8Array body",
|
||||||
|
"Fetch with POST with Float32Array body",
|
||||||
|
"Fetch with POST with Float64Array body",
|
||||||
|
"Fetch with POST with DataView body",
|
||||||
|
"Fetch with POST with Blob body with mime type",
|
||||||
|
"Fetch with Chicken",
|
||||||
|
"Fetch with Chicken with body",
|
||||||
|
"Fetch with POST and mode \"same-origin\" needs an Origin header",
|
||||||
|
"Fetch with POST and mode \"no-cors\" needs an Origin header",
|
||||||
|
"Fetch with PUT and mode \"same-origin\" needs an Origin header",
|
||||||
|
"Fetch with TacO and mode \"same-origin\" needs an Origin header",
|
||||||
|
"Fetch with TacO and mode \"cors\" needs an Origin header"
|
||||||
|
],
|
||||||
|
"text-utf8.any.js": true,
|
||||||
|
"accept-header.any.js": [
|
||||||
|
"Request through fetch should have a 'accept-language' header"
|
||||||
|
],
|
||||||
|
"conditional-get.any.js": false,
|
||||||
|
"error-after-response.any.js": false,
|
||||||
|
"header-value-combining.any.js": false,
|
||||||
|
"header-value-null-byte.any.js": true,
|
||||||
|
"historical.any.js": true,
|
||||||
|
"http-response-code.any.js": true,
|
||||||
|
"integrity.sub.any.js": [
|
||||||
|
"Invalid integrity",
|
||||||
|
"Multiple integrities: invalid stronger than valid",
|
||||||
|
"Multiple integrities: both are invalid",
|
||||||
|
"CORS invalid integrity",
|
||||||
|
"Empty string integrity for opaque response"
|
||||||
|
],
|
||||||
|
"request-upload.any.js": [
|
||||||
|
"Fetch with POST with ReadableStream",
|
||||||
|
"Fetch with POST with ReadableStream containing String",
|
||||||
|
"Fetch with POST with ReadableStream containing null",
|
||||||
|
"Fetch with POST with ReadableStream containing number",
|
||||||
|
"Fetch with POST with ReadableStream containing ArrayBuffer",
|
||||||
|
"Fetch with POST with ReadableStream containing Blob",
|
||||||
|
"Fetch with POST with text body on 421 response should be retried once on new connection."
|
||||||
|
],
|
||||||
|
"response-url.sub.any.js": true,
|
||||||
|
"scheme-about.any.js": true,
|
||||||
|
"scheme-blob.sub.any.js": true,
|
||||||
|
"scheme-data.any.js": false,
|
||||||
|
"scheme-others.sub.any.js": true,
|
||||||
|
"stream-response.any.js": true,
|
||||||
|
"stream-safe-creation.any.js": false
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"json.any.js": true,
|
||||||
|
"response-init-001.any.js": true,
|
||||||
|
"response-init-002.any.js": true,
|
||||||
|
"response-static-error.any.js": true,
|
||||||
|
"response-static-redirect.any.js": true,
|
||||||
|
"response-stream-disturbed-1.any.js": true,
|
||||||
|
"response-stream-disturbed-2.any.js": true,
|
||||||
|
"response-stream-disturbed-3.any.js": true,
|
||||||
|
"response-stream-disturbed-4.any.js": true,
|
||||||
|
"response-stream-disturbed-5.any.js": true,
|
||||||
|
"response-stream-disturbed-6.any.js": true,
|
||||||
|
"response-stream-disturbed-by-pipe.any.js": true,
|
||||||
|
"response-stream-with-broken-then.any.js": [
|
||||||
|
"Attempt to inject {done: false, value: bye} via Object.prototype.then.",
|
||||||
|
"Attempt to inject value: undefined via Object.prototype.then.",
|
||||||
|
"Attempt to inject undefined via Object.prototype.then.",
|
||||||
|
"Attempt to inject 8.2 via Object.prototype.then.",
|
||||||
|
"intercepting arraybuffer to text conversion via Object.prototype.then should not be possible"
|
||||||
|
],
|
||||||
|
"response-error-from-stream.any.js": true,
|
||||||
|
"response-error.any.js": true,
|
||||||
|
"response-from-stream.any.js": true,
|
||||||
|
"response-cancel-stream.any.js": true,
|
||||||
|
"response-clone.any.js": [
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Int8Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Int16Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Int32Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (ArrayBufferchunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Uint8Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Uint8ClampedArraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Uint16Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Uint32Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Float32Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (Float64Arraychunk)",
|
||||||
|
"Check response clone use structureClone for teed ReadableStreams (DataViewchunk)"
|
||||||
|
],
|
||||||
|
"response-consume-empty.any.js": [
|
||||||
|
"Consume empty FormData response body as text"
|
||||||
|
],
|
||||||
|
"response-consume-stream.any.js": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"mime-type.any.js": true
|
||||||
|
},
|
||||||
|
"redirect": {
|
||||||
|
"redirect-count.any.js": true,
|
||||||
|
"redirect-empty-location.any.js": [
|
||||||
|
"redirect response with empty Location, manual mode"
|
||||||
|
],
|
||||||
|
"redirect-location.any.js": [
|
||||||
|
"Redirect 301 in \"manual\" mode without location",
|
||||||
|
"Redirect 301 in \"manual\" mode with invalid location",
|
||||||
|
"Redirect 301 in \"manual\" mode with data location",
|
||||||
|
"Redirect 302 in \"manual\" mode without location",
|
||||||
|
"Redirect 302 in \"manual\" mode with invalid location",
|
||||||
|
"Redirect 302 in \"manual\" mode with data location",
|
||||||
|
"Redirect 303 in \"manual\" mode without location",
|
||||||
|
"Redirect 303 in \"manual\" mode with invalid location",
|
||||||
|
"Redirect 303 in \"manual\" mode with data location",
|
||||||
|
"Redirect 307 in \"manual\" mode without location",
|
||||||
|
"Redirect 307 in \"manual\" mode with invalid location",
|
||||||
|
"Redirect 307 in \"manual\" mode with data location",
|
||||||
|
"Redirect 308 in \"manual\" mode without location",
|
||||||
|
"Redirect 308 in \"manual\" mode with invalid location",
|
||||||
|
"Redirect 308 in \"manual\" mode with data location"
|
||||||
|
],
|
||||||
|
"redirect-method.any.js": true,
|
||||||
|
"redirect-schemes.any.js": true,
|
||||||
|
"redirect-to-dataurl.any.js": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"data-urls": {
|
"data-urls": {
|
||||||
"base64.any.js": true,
|
"base64.any.js": true,
|
||||||
"processing.any.js": [
|
"processing.any.js": [
|
||||||
"\"data://test:test/,X\"",
|
|
||||||
"\"data:text/plain;a=\\\",\\\",X\""
|
"\"data:text/plain;a=\\\",\\\",X\""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,7 @@ export async function runSingleTest(
|
||||||
reporter(result);
|
reporter(result);
|
||||||
} else {
|
} else {
|
||||||
stderr += line + "\n";
|
stderr += line + "\n";
|
||||||
|
console.error(stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue