2022-01-07 22:09:52 -05:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
2020-09-18 09:20:55 -04:00
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
// @ts-check
|
|
|
|
/// <reference path="../../core/lib.deno_core.d.ts" />
|
|
|
|
/// <reference path="../web/internal.d.ts" />
|
2021-04-14 16:49:16 -04:00
|
|
|
/// <reference path="../url/internal.d.ts" />
|
2021-01-28 15:37:21 -05:00
|
|
|
/// <reference path="../web/lib.deno_web.d.ts" />
|
2021-06-14 07:51:02 -04:00
|
|
|
/// <reference path="../web/06_streams_types.d.ts" />
|
2021-01-28 15:37:21 -05:00
|
|
|
/// <reference path="./internal.d.ts" />
|
|
|
|
/// <reference path="./lib.deno_fetch.d.ts" />
|
|
|
|
/// <reference lib="esnext" />
|
2021-02-04 17:18:32 -05:00
|
|
|
"use strict";
|
2021-01-28 15:37:21 -05:00
|
|
|
|
2020-09-18 09:20:55 -04:00
|
|
|
((window) => {
|
|
|
|
const core = window.Deno.core;
|
2021-04-20 08:47:22 -04:00
|
|
|
const webidl = window.__bootstrap.webidl;
|
2021-11-22 19:23:11 -05:00
|
|
|
const { byteLowerCase } = window.__bootstrap.infra;
|
2022-02-01 12:06:11 -05:00
|
|
|
const { BlobPrototype } = window.__bootstrap.file;
|
|
|
|
const { errorReadableStream, ReadableStreamPrototype } =
|
|
|
|
window.__bootstrap.streams;
|
2021-04-20 08:47:22 -04:00
|
|
|
const { InnerBody, extractBody } = window.__bootstrap.fetchBody;
|
|
|
|
const {
|
|
|
|
toInnerRequest,
|
2021-06-06 09:37:17 -04:00
|
|
|
toInnerResponse,
|
2021-04-20 08:47:22 -04:00
|
|
|
fromInnerResponse,
|
|
|
|
redirectStatus,
|
|
|
|
nullBodyStatus,
|
|
|
|
networkError,
|
2021-06-06 09:37:17 -04:00
|
|
|
abortedNetworkError,
|
2021-04-20 08:47:22 -04:00
|
|
|
} = window.__bootstrap.fetch;
|
2021-06-06 09:37:17 -04:00
|
|
|
const abortSignal = window.__bootstrap.abortSignal;
|
2021-07-06 05:32:59 -04:00
|
|
|
const {
|
|
|
|
ArrayPrototypePush,
|
|
|
|
ArrayPrototypeSplice,
|
|
|
|
ArrayPrototypeFilter,
|
|
|
|
ArrayPrototypeIncludes,
|
2022-02-01 12:06:11 -05:00
|
|
|
ObjectPrototypeIsPrototypeOf,
|
2021-07-06 05:32:59 -04:00
|
|
|
Promise,
|
|
|
|
PromisePrototypeThen,
|
|
|
|
PromisePrototypeCatch,
|
2022-02-07 07:54:32 -05:00
|
|
|
SafeArrayIterator,
|
fix: a `Request` whose URL is a revoked blob URL should still fetch (#11947)
In the spec, a URL record has an associated "blob URL entry", which for
`blob:` URLs is populated during parsing to contain a reference to the
`Blob` object that backs that object URL. It is this blob URL entry that
the `fetch` API uses to resolve an object URL.
Therefore, since the `Request` constructor parses URL inputs, it will
have an associated blob URL entry which will be used when fetching, even
if the object URL has been revoked since the construction of the
`Request` object. (The `Request` constructor takes the URL as a string
and parses it, so the object URL must be live at the time it is called.)
This PR adds a new `blobFromObjectUrl` JS function (backed by a new
`op_blob_from_object_url` op) that, if the URL is a valid object URL,
returns a new `Blob` object whose parts are references to the same Rust
`BlobPart`s used by the original `Blob` object. It uses this function to
add a new `blobUrlEntry` field to inner requests, which will be `null`
or such a `Blob`, and then uses `Blob.prototype.stream()` as the
response's body. As a result of this, the `blob:` URL resolution from
`op_fetch` is now useless, and has been removed.
2021-09-08 05:29:21 -04:00
|
|
|
String,
|
2021-11-26 03:52:41 -05:00
|
|
|
StringPrototypeStartsWith,
|
2021-07-06 05:32:59 -04:00
|
|
|
StringPrototypeToLowerCase,
|
|
|
|
TypedArrayPrototypeSubarray,
|
|
|
|
TypeError,
|
|
|
|
Uint8Array,
|
2022-02-01 12:06:11 -05:00
|
|
|
Uint8ArrayPrototype,
|
2021-09-12 19:19:38 -04:00
|
|
|
WeakMap,
|
|
|
|
WeakMapPrototypeDelete,
|
|
|
|
WeakMapPrototypeGet,
|
|
|
|
WeakMapPrototypeHas,
|
|
|
|
WeakMapPrototypeSet,
|
2021-07-06 05:32:59 -04:00
|
|
|
} = window.__bootstrap.primordials;
|
2021-04-20 08:47:22 -04:00
|
|
|
|
|
|
|
const REQUEST_BODY_HEADER_NAMES = [
|
|
|
|
"content-encoding",
|
|
|
|
"content-language",
|
|
|
|
"content-location",
|
|
|
|
"content-type",
|
|
|
|
];
|
2020-09-18 09:20:55 -04:00
|
|
|
|
2021-09-12 19:19:38 -04:00
|
|
|
const requestBodyReaders = new WeakMap();
|
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
/**
|
2021-04-28 10:08:51 -04:00
|
|
|
* @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args
|
|
|
|
* @param {Uint8Array | null} body
|
2021-04-20 08:47:22 -04:00
|
|
|
* @returns {{ requestRid: number, requestBodyRid: number | null }}
|
2021-01-28 15:37:21 -05:00
|
|
|
*/
|
2020-09-18 09:20:55 -04:00
|
|
|
function opFetch(args, body) {
|
2021-04-20 08:47:22 -04:00
|
|
|
return core.opSync("op_fetch", args, body);
|
2021-01-10 14:54:29 -05:00
|
|
|
}
|
2020-09-18 09:20:55 -04:00
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
/**
|
2021-04-28 10:08:51 -04:00
|
|
|
* @param {number} rid
|
2021-04-20 08:47:22 -04:00
|
|
|
* @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>}
|
2021-01-28 15:37:21 -05:00
|
|
|
*/
|
2021-04-05 12:40:24 -04:00
|
|
|
function opFetchSend(rid) {
|
2021-04-12 15:55:05 -04:00
|
|
|
return core.opAsync("op_fetch_send", rid);
|
2021-01-10 14:54:29 -05:00
|
|
|
}
|
|
|
|
|
2021-07-20 15:06:24 -04:00
|
|
|
// A finalization registry to clean up underlying fetch resources that are GC'ed.
|
|
|
|
const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => {
|
2021-09-10 20:54:37 -04:00
|
|
|
core.tryClose(rid);
|
2021-07-20 15:06:24 -04:00
|
|
|
});
|
|
|
|
|
2021-04-12 20:45:57 -04:00
|
|
|
/**
|
2021-04-20 08:47:22 -04:00
|
|
|
* @param {number} responseBodyRid
|
2021-06-06 09:37:17 -04:00
|
|
|
* @param {AbortSignal} [terminator]
|
2021-04-20 08:47:22 -04:00
|
|
|
* @returns {ReadableStream<Uint8Array>}
|
2021-04-12 20:45:57 -04:00
|
|
|
*/
|
2021-06-06 09:37:17 -04:00
|
|
|
function createResponseBodyStream(responseBodyRid, terminator) {
|
|
|
|
function onAbort() {
|
|
|
|
if (readable) {
|
2021-12-16 06:58:24 -05:00
|
|
|
errorReadableStream(readable, terminator.reason);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
2021-09-10 20:54:37 -04:00
|
|
|
core.tryClose(responseBodyRid);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
// TODO(lucacasonato): clean up registration
|
|
|
|
terminator[abortSignal.add](onAbort);
|
|
|
|
const readable = new ReadableStream({
|
2021-04-20 08:47:22 -04:00
|
|
|
type: "bytes",
|
|
|
|
async pull(controller) {
|
|
|
|
try {
|
|
|
|
// This is the largest possible size for a single packet on a TLS
|
|
|
|
// stream.
|
|
|
|
const chunk = new Uint8Array(16 * 1024 + 256);
|
2021-11-09 13:26:17 -05:00
|
|
|
// TODO(@AaronO): switch to handle nulls if that's moved to core
|
|
|
|
const read = await core.read(
|
2021-04-20 08:47:22 -04:00
|
|
|
responseBodyRid,
|
|
|
|
chunk,
|
|
|
|
);
|
|
|
|
if (read > 0) {
|
|
|
|
// We read some data. Enqueue it onto the stream.
|
2021-07-06 05:32:59 -04:00
|
|
|
controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read));
|
2021-04-20 08:47:22 -04:00
|
|
|
} else {
|
2021-07-20 15:06:24 -04:00
|
|
|
RESOURCE_REGISTRY.unregister(readable);
|
2021-04-20 08:47:22 -04:00
|
|
|
// We have reached the end of the body, so we close the stream.
|
|
|
|
controller.close();
|
2021-09-10 20:54:37 -04:00
|
|
|
core.tryClose(responseBodyRid);
|
2021-04-20 08:47:22 -04:00
|
|
|
}
|
|
|
|
} catch (err) {
|
2021-07-20 15:06:24 -04:00
|
|
|
RESOURCE_REGISTRY.unregister(readable);
|
2021-06-06 09:37:17 -04:00
|
|
|
if (terminator.aborted) {
|
2021-12-16 06:58:24 -05:00
|
|
|
controller.error(terminator.reason);
|
2021-06-06 09:37:17 -04:00
|
|
|
} else {
|
|
|
|
// There was an error while reading a chunk of the body, so we
|
|
|
|
// error.
|
|
|
|
controller.error(err);
|
|
|
|
}
|
2021-09-10 20:54:37 -04:00
|
|
|
core.tryClose(responseBodyRid);
|
2021-04-20 08:47:22 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
cancel() {
|
2021-06-06 09:37:17 -04:00
|
|
|
if (!terminator.aborted) {
|
|
|
|
terminator[abortSignal.signalAbort]();
|
|
|
|
}
|
2021-04-20 08:47:22 -04:00
|
|
|
},
|
|
|
|
});
|
2021-07-20 15:06:24 -04:00
|
|
|
RESOURCE_REGISTRY.register(readable, responseBodyRid, readable);
|
2021-06-06 09:37:17 -04:00
|
|
|
return readable;
|
2021-04-12 20:45:57 -04:00
|
|
|
}
|
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
/**
|
2021-04-28 10:08:51 -04:00
|
|
|
* @param {InnerRequest} req
|
2021-04-20 08:47:22 -04:00
|
|
|
* @param {boolean} recursive
|
2021-06-06 09:37:17 -04:00
|
|
|
* @param {AbortSignal} terminator
|
2021-04-20 08:47:22 -04:00
|
|
|
* @returns {Promise<InnerResponse>}
|
2021-01-28 15:37:21 -05:00
|
|
|
*/
|
2021-06-06 09:37:17 -04:00
|
|
|
async function mainFetch(req, recursive, terminator) {
|
fix: a `Request` whose URL is a revoked blob URL should still fetch (#11947)
In the spec, a URL record has an associated "blob URL entry", which for
`blob:` URLs is populated during parsing to contain a reference to the
`Blob` object that backs that object URL. It is this blob URL entry that
the `fetch` API uses to resolve an object URL.
Therefore, since the `Request` constructor parses URL inputs, it will
have an associated blob URL entry which will be used when fetching, even
if the object URL has been revoked since the construction of the
`Request` object. (The `Request` constructor takes the URL as a string
and parses it, so the object URL must be live at the time it is called.)
This PR adds a new `blobFromObjectUrl` JS function (backed by a new
`op_blob_from_object_url` op) that, if the URL is a valid object URL,
returns a new `Blob` object whose parts are references to the same Rust
`BlobPart`s used by the original `Blob` object. It uses this function to
add a new `blobUrlEntry` field to inner requests, which will be `null`
or such a `Blob`, and then uses `Blob.prototype.stream()` as the
response's body. As a result of this, the `blob:` URL resolution from
`op_fetch` is now useless, and has been removed.
2021-09-08 05:29:21 -04:00
|
|
|
if (req.blobUrlEntry !== null) {
|
|
|
|
if (req.method !== "GET") {
|
|
|
|
throw new TypeError("Blob URL fetch only supports GET method.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const body = new InnerBody(req.blobUrlEntry.stream());
|
2021-12-16 06:58:24 -05:00
|
|
|
terminator[abortSignal.add](() => body.error(terminator.reason));
|
fix: a `Request` whose URL is a revoked blob URL should still fetch (#11947)
In the spec, a URL record has an associated "blob URL entry", which for
`blob:` URLs is populated during parsing to contain a reference to the
`Blob` object that backs that object URL. It is this blob URL entry that
the `fetch` API uses to resolve an object URL.
Therefore, since the `Request` constructor parses URL inputs, it will
have an associated blob URL entry which will be used when fetching, even
if the object URL has been revoked since the construction of the
`Request` object. (The `Request` constructor takes the URL as a string
and parses it, so the object URL must be live at the time it is called.)
This PR adds a new `blobFromObjectUrl` JS function (backed by a new
`op_blob_from_object_url` op) that, if the URL is a valid object URL,
returns a new `Blob` object whose parts are references to the same Rust
`BlobPart`s used by the original `Blob` object. It uses this function to
add a new `blobUrlEntry` field to inner requests, which will be `null`
or such a `Blob`, and then uses `Blob.prototype.stream()` as the
response's body. As a result of this, the `blob:` URL resolution from
`op_fetch` is now useless, and has been removed.
2021-09-08 05:29:21 -04:00
|
|
|
|
|
|
|
return {
|
|
|
|
headerList: [
|
|
|
|
["content-length", String(req.blobUrlEntry.size)],
|
|
|
|
["content-type", req.blobUrlEntry.type],
|
|
|
|
],
|
|
|
|
status: 200,
|
|
|
|
statusMessage: "OK",
|
|
|
|
body,
|
|
|
|
type: "basic",
|
|
|
|
url() {
|
|
|
|
if (this.urlList.length == 0) return null;
|
|
|
|
return this.urlList[this.urlList.length - 1];
|
|
|
|
},
|
2022-02-07 07:54:32 -05:00
|
|
|
urlList: recursive ? [] : [...new SafeArrayIterator(req.urlList)],
|
fix: a `Request` whose URL is a revoked blob URL should still fetch (#11947)
In the spec, a URL record has an associated "blob URL entry", which for
`blob:` URLs is populated during parsing to contain a reference to the
`Blob` object that backs that object URL. It is this blob URL entry that
the `fetch` API uses to resolve an object URL.
Therefore, since the `Request` constructor parses URL inputs, it will
have an associated blob URL entry which will be used when fetching, even
if the object URL has been revoked since the construction of the
`Request` object. (The `Request` constructor takes the URL as a string
and parses it, so the object URL must be live at the time it is called.)
This PR adds a new `blobFromObjectUrl` JS function (backed by a new
`op_blob_from_object_url` op) that, if the URL is a valid object URL,
returns a new `Blob` object whose parts are references to the same Rust
`BlobPart`s used by the original `Blob` object. It uses this function to
add a new `blobUrlEntry` field to inner requests, which will be `null`
or such a `Blob`, and then uses `Blob.prototype.stream()` as the
response's body. As a result of this, the `blob:` URL resolution from
`op_fetch` is now useless, and has been removed.
2021-09-08 05:29:21 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
|
|
|
|
let reqBody = null;
|
2021-06-18 05:14:14 -04:00
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
if (req.body !== null) {
|
2022-02-01 12:06:11 -05:00
|
|
|
if (
|
|
|
|
ObjectPrototypeIsPrototypeOf(
|
|
|
|
ReadableStreamPrototype,
|
|
|
|
req.body.streamOrStatic,
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
req.body.length === null ||
|
|
|
|
ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source)
|
|
|
|
) {
|
2021-04-20 08:47:22 -04:00
|
|
|
reqBody = req.body.stream;
|
2021-04-12 20:46:33 -04:00
|
|
|
} else {
|
2021-04-20 08:47:22 -04:00
|
|
|
const reader = req.body.stream.getReader();
|
2021-09-12 19:19:38 -04:00
|
|
|
WeakMapPrototypeSet(requestBodyReaders, req, reader);
|
2021-04-20 08:47:22 -04:00
|
|
|
const r1 = await reader.read();
|
2021-07-05 09:34:37 -04:00
|
|
|
if (r1.done) {
|
|
|
|
reqBody = new Uint8Array(0);
|
|
|
|
} else {
|
|
|
|
reqBody = r1.value;
|
|
|
|
const r2 = await reader.read();
|
|
|
|
if (!r2.done) throw new TypeError("Unreachable");
|
|
|
|
}
|
2021-09-12 19:19:38 -04:00
|
|
|
WeakMapPrototypeDelete(requestBodyReaders, req);
|
2021-04-12 20:46:33 -04:00
|
|
|
}
|
2020-09-18 09:20:55 -04:00
|
|
|
} else {
|
2021-04-20 08:47:22 -04:00
|
|
|
req.body.streamOrStatic.consumed = true;
|
|
|
|
reqBody = req.body.streamOrStatic.body;
|
2021-10-26 16:00:01 -04:00
|
|
|
// TODO(@AaronO): plumb support for StringOrBuffer all the way
|
|
|
|
reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody;
|
2021-04-20 08:47:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-01 12:06:11 -05:00
|
|
|
const { requestRid, requestBodyRid, cancelHandleRid } = opFetch(
|
|
|
|
{
|
|
|
|
method: req.method,
|
|
|
|
url: req.currentUrl(),
|
|
|
|
headers: req.headerList,
|
|
|
|
clientRid: req.clientRid,
|
|
|
|
hasBody: reqBody !== null,
|
|
|
|
bodyLength: req.body?.length,
|
|
|
|
},
|
|
|
|
ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody)
|
|
|
|
? reqBody
|
|
|
|
: null,
|
|
|
|
);
|
2021-04-20 08:47:22 -04:00
|
|
|
|
2021-06-06 09:37:17 -04:00
|
|
|
function onAbort() {
|
2021-09-10 20:54:37 -04:00
|
|
|
if (cancelHandleRid !== null) {
|
|
|
|
core.tryClose(cancelHandleRid);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
2021-09-10 20:54:37 -04:00
|
|
|
if (requestBodyRid !== null) {
|
|
|
|
core.tryClose(requestBodyRid);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
terminator[abortSignal.add](onAbort);
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
if (requestBodyRid !== null) {
|
2022-02-01 12:06:11 -05:00
|
|
|
if (
|
|
|
|
reqBody === null ||
|
|
|
|
!ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody)
|
|
|
|
) {
|
2021-04-20 08:47:22 -04:00
|
|
|
throw new TypeError("Unreachable");
|
|
|
|
}
|
|
|
|
const reader = reqBody.getReader();
|
2021-09-12 19:19:38 -04:00
|
|
|
WeakMapPrototypeSet(requestBodyReaders, req, reader);
|
2021-04-20 08:47:22 -04:00
|
|
|
(async () => {
|
|
|
|
while (true) {
|
2021-07-06 05:32:59 -04:00
|
|
|
const { value, done } = await PromisePrototypeCatch(
|
|
|
|
reader.read(),
|
|
|
|
(err) => {
|
|
|
|
if (terminator.aborted) return { done: true, value: undefined };
|
|
|
|
throw err;
|
|
|
|
},
|
|
|
|
);
|
2021-04-20 08:47:22 -04:00
|
|
|
if (done) break;
|
2022-02-01 12:06:11 -05:00
|
|
|
if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) {
|
2021-04-20 08:47:22 -04:00
|
|
|
await reader.cancel("value not a Uint8Array");
|
|
|
|
break;
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
2021-04-20 08:47:22 -04:00
|
|
|
try {
|
2021-07-06 05:32:59 -04:00
|
|
|
await PromisePrototypeCatch(
|
2021-11-09 13:26:17 -05:00
|
|
|
core.write(requestBodyRid, value),
|
2021-07-06 05:32:59 -04:00
|
|
|
(err) => {
|
|
|
|
if (terminator.aborted) return;
|
|
|
|
throw err;
|
|
|
|
},
|
|
|
|
);
|
2021-06-06 09:37:17 -04:00
|
|
|
if (terminator.aborted) break;
|
2021-04-20 08:47:22 -04:00
|
|
|
} catch (err) {
|
|
|
|
await reader.cancel(err);
|
|
|
|
break;
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
|
|
|
}
|
2021-09-12 19:19:38 -04:00
|
|
|
WeakMapPrototypeDelete(requestBodyReaders, req);
|
2021-09-10 20:54:37 -04:00
|
|
|
core.tryClose(requestBodyRid);
|
2021-04-20 08:47:22 -04:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2021-06-06 09:37:17 -04:00
|
|
|
let resp;
|
|
|
|
try {
|
2021-07-06 05:32:59 -04:00
|
|
|
resp = await PromisePrototypeCatch(opFetchSend(requestRid), (err) => {
|
2021-06-06 09:37:17 -04:00
|
|
|
if (terminator.aborted) return;
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
} finally {
|
2021-09-10 20:54:37 -04:00
|
|
|
if (cancelHandleRid !== null) {
|
|
|
|
core.tryClose(cancelHandleRid);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (terminator.aborted) return abortedNetworkError();
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
/** @type {InnerResponse} */
|
|
|
|
const response = {
|
|
|
|
headerList: resp.headers,
|
|
|
|
status: resp.status,
|
|
|
|
body: null,
|
|
|
|
statusMessage: resp.statusText,
|
|
|
|
type: "basic",
|
|
|
|
url() {
|
|
|
|
if (this.urlList.length == 0) return null;
|
|
|
|
return this.urlList[this.urlList.length - 1];
|
|
|
|
},
|
|
|
|
urlList: req.urlList,
|
|
|
|
};
|
|
|
|
if (redirectStatus(resp.status)) {
|
|
|
|
switch (req.redirectMode) {
|
|
|
|
case "error":
|
|
|
|
core.close(resp.responseRid);
|
|
|
|
return networkError(
|
|
|
|
"Encountered redirect while redirect mode is set to 'error'",
|
|
|
|
);
|
|
|
|
case "follow":
|
|
|
|
core.close(resp.responseRid);
|
2021-06-06 09:37:17 -04:00
|
|
|
return httpRedirectFetch(req, response, terminator);
|
2021-04-20 08:47:22 -04:00
|
|
|
case "manual":
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nullBodyStatus(response.status)) {
|
|
|
|
core.close(resp.responseRid);
|
|
|
|
} else {
|
2021-07-05 06:38:12 -04:00
|
|
|
if (req.method === "HEAD" || req.method === "CONNECT") {
|
2021-06-18 05:14:14 -04:00
|
|
|
response.body = null;
|
|
|
|
core.close(resp.responseRid);
|
|
|
|
} else {
|
|
|
|
response.body = new InnerBody(
|
|
|
|
createResponseBodyStream(resp.responseRid, terminator),
|
|
|
|
);
|
|
|
|
}
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
if (recursive) return response;
|
2020-09-18 09:20:55 -04:00
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
if (response.urlList.length === 0) {
|
2022-02-07 07:54:32 -05:00
|
|
|
response.urlList = [...new SafeArrayIterator(req.urlList)];
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
return response;
|
2021-01-07 13:06:08 -05:00
|
|
|
}
|
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
/**
|
2021-04-20 08:47:22 -04:00
|
|
|
* @param {InnerRequest} request
|
|
|
|
* @param {InnerResponse} response
|
2021-12-16 06:58:24 -05:00
|
|
|
* @param {AbortSignal} terminator
|
2021-04-20 08:47:22 -04:00
|
|
|
* @returns {Promise<InnerResponse>}
|
2021-01-28 15:37:21 -05:00
|
|
|
*/
|
2021-06-06 09:37:17 -04:00
|
|
|
function httpRedirectFetch(request, response, terminator) {
|
2021-07-06 05:32:59 -04:00
|
|
|
const locationHeaders = ArrayPrototypeFilter(
|
|
|
|
response.headerList,
|
2021-11-22 19:23:11 -05:00
|
|
|
(entry) => byteLowerCase(entry[0]) === "location",
|
2021-06-18 05:14:14 -04:00
|
|
|
);
|
2021-04-20 08:47:22 -04:00
|
|
|
if (locationHeaders.length === 0) {
|
|
|
|
return response;
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
2021-04-20 08:47:22 -04:00
|
|
|
const locationURL = new URL(
|
|
|
|
locationHeaders[0][1],
|
|
|
|
response.url() ?? undefined,
|
2021-01-10 14:54:29 -05:00
|
|
|
);
|
2021-04-20 08:47:22 -04:00
|
|
|
if (locationURL.hash === "") {
|
|
|
|
locationURL.hash = request.currentUrl().hash;
|
|
|
|
}
|
|
|
|
if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") {
|
|
|
|
return networkError("Can not redirect to a non HTTP(s) url");
|
|
|
|
}
|
|
|
|
if (request.redirectCount === 20) {
|
|
|
|
return networkError("Maximum number of redirects (20) reached");
|
|
|
|
}
|
|
|
|
request.redirectCount++;
|
|
|
|
if (
|
2021-06-18 05:14:14 -04:00
|
|
|
response.status !== 303 &&
|
|
|
|
request.body !== null &&
|
2021-04-20 08:47:22 -04:00
|
|
|
request.body.source === null
|
|
|
|
) {
|
|
|
|
return networkError(
|
|
|
|
"Can not redeliver a streaming request body after a redirect",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
((response.status === 301 || response.status === 302) &&
|
|
|
|
request.method === "POST") ||
|
|
|
|
(response.status === 303 &&
|
2021-06-18 05:14:14 -04:00
|
|
|
request.method !== "GET" &&
|
|
|
|
request.method !== "HEAD")
|
2021-04-20 08:47:22 -04:00
|
|
|
) {
|
|
|
|
request.method = "GET";
|
|
|
|
request.body = null;
|
|
|
|
for (let i = 0; i < request.headerList.length; i++) {
|
2021-07-06 05:32:59 -04:00
|
|
|
if (
|
|
|
|
ArrayPrototypeIncludes(
|
|
|
|
REQUEST_BODY_HEADER_NAMES,
|
2021-11-22 19:23:11 -05:00
|
|
|
byteLowerCase(request.headerList[i][0]),
|
2021-07-06 05:32:59 -04:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
ArrayPrototypeSplice(request.headerList, i, 1);
|
2021-04-20 08:47:22 -04:00
|
|
|
i--;
|
|
|
|
}
|
2021-01-28 15:37:21 -05:00
|
|
|
}
|
2021-01-10 14:54:29 -05:00
|
|
|
}
|
2021-04-20 08:47:22 -04:00
|
|
|
if (request.body !== null) {
|
|
|
|
const res = extractBody(request.body.source);
|
|
|
|
request.body = res.body;
|
|
|
|
}
|
2021-07-06 05:32:59 -04:00
|
|
|
ArrayPrototypePush(request.urlList, locationURL.href);
|
2021-06-06 09:37:17 -04:00
|
|
|
return mainFetch(request, true, terminator);
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
|
|
|
|
2021-01-28 15:37:21 -05:00
|
|
|
/**
|
2021-04-28 10:08:51 -04:00
|
|
|
* @param {RequestInfo} input
|
|
|
|
* @param {RequestInit} init
|
2021-01-28 15:37:21 -05:00
|
|
|
*/
|
2021-06-06 09:37:17 -04:00
|
|
|
function fetch(input, init = {}) {
|
2022-03-22 13:08:33 -04:00
|
|
|
// There is an async dispatch later that causes a stack trace disconnect.
|
|
|
|
// We reconnect it by assigning the result of that dispatch to `opPromise`,
|
|
|
|
// awaiting `opPromise` in an inner function also named `fetch()` and
|
|
|
|
// returning the result from that.
|
|
|
|
let opPromise = undefined;
|
2021-04-20 08:47:22 -04:00
|
|
|
// 1.
|
2022-03-22 13:08:33 -04:00
|
|
|
const result = new Promise((resolve, reject) => {
|
2021-06-06 09:37:17 -04:00
|
|
|
const prefix = "Failed to call 'fetch'";
|
|
|
|
webidl.requiredArguments(arguments.length, 1, { prefix });
|
|
|
|
// 2.
|
|
|
|
const requestObject = new Request(input, init);
|
|
|
|
// 3.
|
|
|
|
const request = toInnerRequest(requestObject);
|
|
|
|
// 4.
|
|
|
|
if (requestObject.signal.aborted) {
|
2021-12-16 06:58:24 -05:00
|
|
|
reject(abortFetch(request, null, requestObject.signal.reason));
|
2021-06-06 09:37:17 -04:00
|
|
|
return;
|
|
|
|
}
|
2020-09-18 09:20:55 -04:00
|
|
|
|
2021-06-06 09:37:17 -04:00
|
|
|
// 7.
|
|
|
|
let responseObject = null;
|
|
|
|
// 9.
|
|
|
|
let locallyAborted = false;
|
|
|
|
// 10.
|
|
|
|
function onabort() {
|
|
|
|
locallyAborted = true;
|
2021-12-16 06:58:24 -05:00
|
|
|
reject(
|
|
|
|
abortFetch(request, responseObject, requestObject.signal.reason),
|
|
|
|
);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
requestObject.signal[abortSignal.add](onabort);
|
|
|
|
|
2021-11-22 19:23:11 -05:00
|
|
|
if (!requestObject.headers.has("Accept")) {
|
|
|
|
ArrayPrototypePush(request.headerList, ["Accept", "*/*"]);
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 12.
|
2022-03-22 13:08:33 -04:00
|
|
|
opPromise = PromisePrototypeCatch(
|
2021-07-06 05:32:59 -04:00
|
|
|
PromisePrototypeThen(
|
|
|
|
mainFetch(request, false, requestObject.signal),
|
|
|
|
(response) => {
|
|
|
|
// 12.1.
|
|
|
|
if (locallyAborted) return;
|
|
|
|
// 12.2.
|
|
|
|
if (response.aborted) {
|
2021-12-16 06:58:24 -05:00
|
|
|
reject(
|
|
|
|
abortFetch(
|
|
|
|
request,
|
|
|
|
responseObject,
|
|
|
|
requestObject.signal.reason,
|
|
|
|
),
|
|
|
|
);
|
2021-07-06 05:32:59 -04:00
|
|
|
requestObject.signal[abortSignal.remove](onabort);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 12.3.
|
|
|
|
if (response.type === "error") {
|
|
|
|
const err = new TypeError(
|
|
|
|
"Fetch failed: " + (response.error ?? "unknown error"),
|
|
|
|
);
|
|
|
|
reject(err);
|
|
|
|
requestObject.signal[abortSignal.remove](onabort);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
responseObject = fromInnerResponse(response, "immutable");
|
|
|
|
resolve(responseObject);
|
|
|
|
requestObject.signal[abortSignal.remove](onabort);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(err) => {
|
2021-06-06 09:37:17 -04:00
|
|
|
reject(err);
|
|
|
|
requestObject.signal[abortSignal.remove](onabort);
|
2021-07-06 05:32:59 -04:00
|
|
|
},
|
|
|
|
);
|
2021-06-06 09:37:17 -04:00
|
|
|
});
|
2022-03-22 13:08:33 -04:00
|
|
|
if (opPromise) {
|
|
|
|
PromisePrototypeCatch(result, () => {});
|
|
|
|
return (async function fetch() {
|
|
|
|
await opPromise;
|
|
|
|
return result;
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
return result;
|
2021-06-06 09:37:17 -04:00
|
|
|
}
|
|
|
|
|
2021-12-16 06:58:24 -05:00
|
|
|
function abortFetch(request, responseObject, error) {
|
2021-09-12 19:19:38 -04:00
|
|
|
if (request.body !== null) {
|
|
|
|
if (WeakMapPrototypeHas(requestBodyReaders, request)) {
|
|
|
|
WeakMapPrototypeGet(requestBodyReaders, request).cancel(error);
|
|
|
|
} else {
|
|
|
|
request.body.cancel(error);
|
|
|
|
}
|
|
|
|
}
|
2021-06-06 09:37:17 -04:00
|
|
|
if (responseObject !== null) {
|
|
|
|
const response = toInnerResponse(responseObject);
|
|
|
|
if (response.body !== null) response.body.error(error);
|
|
|
|
}
|
|
|
|
return error;
|
2020-09-18 09:20:55 -04:00
|
|
|
}
|
|
|
|
|
2021-07-03 17:33:36 -04:00
|
|
|
/**
|
2022-03-29 08:44:33 -04:00
|
|
|
* Handle the Response argument to the WebAssembly streaming APIs, after
|
|
|
|
* resolving if it was passed as a promise. This function should be registered
|
|
|
|
* through `Deno.core.setWasmStreamingCallback`.
|
2021-07-03 17:33:36 -04:00
|
|
|
*
|
2022-03-29 08:44:33 -04:00
|
|
|
* @param {any} source The source parameter that the WebAssembly streaming API
|
|
|
|
* was called with. If it was called with a Promise, `source` is the resolved
|
|
|
|
* value of that promise.
|
|
|
|
* @param {number} rid An rid that represents the wasm streaming resource.
|
2021-07-03 17:33:36 -04:00
|
|
|
*/
|
|
|
|
function handleWasmStreaming(source, rid) {
|
|
|
|
// This implements part of
|
|
|
|
// https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response
|
2022-03-29 08:44:33 -04:00
|
|
|
try {
|
|
|
|
const res = webidl.converters["Response"](source, {
|
|
|
|
prefix: "Failed to call 'WebAssembly.compileStreaming'",
|
|
|
|
context: "Argument 1",
|
|
|
|
});
|
2021-07-03 17:33:36 -04:00
|
|
|
|
2022-03-29 08:44:33 -04:00
|
|
|
// 2.3.
|
|
|
|
// The spec is ambiguous here, see
|
|
|
|
// https://github.com/WebAssembly/spec/issues/1138. The WPT tests expect
|
|
|
|
// the raw value of the Content-Type attribute lowercased. We ignore this
|
|
|
|
// for file:// because file fetches don't have a Content-Type.
|
|
|
|
if (!StringPrototypeStartsWith(res.url, "file://")) {
|
|
|
|
const contentType = res.headers.get("Content-Type");
|
|
|
|
if (
|
|
|
|
typeof contentType !== "string" ||
|
|
|
|
StringPrototypeToLowerCase(contentType) !== "application/wasm"
|
|
|
|
) {
|
|
|
|
throw new TypeError("Invalid WebAssembly content type.");
|
2021-07-03 17:33:36 -04:00
|
|
|
}
|
2022-03-29 08:44:33 -04:00
|
|
|
}
|
2021-07-03 17:33:36 -04:00
|
|
|
|
2022-03-29 08:44:33 -04:00
|
|
|
// 2.5.
|
|
|
|
if (!res.ok) {
|
|
|
|
throw new TypeError(`HTTP status code ${res.status}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass the resolved URL to v8.
|
|
|
|
core.opSync("op_wasm_streaming_set_url", rid, res.url);
|
2021-10-10 10:03:23 -04:00
|
|
|
|
2022-03-29 08:44:33 -04:00
|
|
|
if (res.body !== null) {
|
2021-07-03 17:33:36 -04:00
|
|
|
// 2.6.
|
|
|
|
// Rather than consuming the body as an ArrayBuffer, this passes each
|
|
|
|
// chunk to the feed as soon as it's available.
|
2022-03-29 08:44:33 -04:00
|
|
|
(async () => {
|
2021-07-03 17:33:36 -04:00
|
|
|
const reader = res.body.getReader();
|
|
|
|
while (true) {
|
|
|
|
const { value: chunk, done } = await reader.read();
|
|
|
|
if (done) break;
|
refactor(core): Turn the `wasm_streaming_feed` binding into ops (#11985)
Async WebAssembly compilation was implemented by adding two
bindings: `set_wasm_streaming_callback`, which registered a callback to
be called whenever a streaming wasm compilation was started, and
`wasm_streaming_feed`, which let the JS callback modify the state of the
v8 wasm compiler.
`set_wasm_streaming_callback` cannot currently be implemented as
anything other than a binding, but `wasm_streaming_feed` does not really
need to use anything specific to bindings, and could indeed be
implemented as one or more ops. This PR does that, resulting in a
simplification of the relevant code.
There are three operations on the state of the v8 wasm compiler that
`wasm_streaming_feed` allowed: feeding new bytes into the compiler,
letting it know that there are no more bytes coming from the network,
and aborting the compilation. This PR provides `op_wasm_streaming_feed`
to feed new bytes into the compiler, and `op_wasm_streaming_abort` to
abort the compilation. It doesn't provide an op to let v8 know that the
response is finished, but closing the resource with `Deno.core.close()`
will achieve that.
2021-09-13 08:27:54 -04:00
|
|
|
core.opSync("op_wasm_streaming_feed", rid, chunk);
|
2021-07-03 17:33:36 -04:00
|
|
|
}
|
2022-03-29 08:44:33 -04:00
|
|
|
})().then(
|
|
|
|
// 2.7
|
|
|
|
() => core.close(rid),
|
|
|
|
// 2.8
|
|
|
|
(err) => core.abortWasmStreaming(rid, err),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
// 2.7
|
refactor(core): Turn the `wasm_streaming_feed` binding into ops (#11985)
Async WebAssembly compilation was implemented by adding two
bindings: `set_wasm_streaming_callback`, which registered a callback to
be called whenever a streaming wasm compilation was started, and
`wasm_streaming_feed`, which let the JS callback modify the state of the
v8 wasm compiler.
`set_wasm_streaming_callback` cannot currently be implemented as
anything other than a binding, but `wasm_streaming_feed` does not really
need to use anything specific to bindings, and could indeed be
implemented as one or more ops. This PR does that, resulting in a
simplification of the relevant code.
There are three operations on the state of the v8 wasm compiler that
`wasm_streaming_feed` allowed: feeding new bytes into the compiler,
letting it know that there are no more bytes coming from the network,
and aborting the compilation. This PR provides `op_wasm_streaming_feed`
to feed new bytes into the compiler, and `op_wasm_streaming_abort` to
abort the compilation. It doesn't provide an op to let v8 know that the
response is finished, but closing the resource with `Deno.core.close()`
will achieve that.
2021-09-13 08:27:54 -04:00
|
|
|
core.close(rid);
|
2021-07-03 17:33:36 -04:00
|
|
|
}
|
2022-03-29 08:44:33 -04:00
|
|
|
} catch (err) {
|
|
|
|
// 2.8
|
|
|
|
core.abortWasmStreaming(rid, err);
|
|
|
|
}
|
2021-07-03 17:33:36 -04:00
|
|
|
}
|
|
|
|
|
2021-04-20 08:47:22 -04:00
|
|
|
window.__bootstrap.fetch ??= {};
|
|
|
|
window.__bootstrap.fetch.fetch = fetch;
|
2021-07-03 17:33:36 -04:00
|
|
|
window.__bootstrap.fetch.handleWasmStreaming = handleWasmStreaming;
|
2020-09-18 09:20:55 -04:00
|
|
|
})(this);
|