(function () {
const { core, internals, primordials } = __bootstrap;
const {
op_fetch,
op_fetch_promise_is_settled,
op_fetch_send,
op_wasm_streaming_feed,
op_wasm_streaming_set_url,
} = core.ops;
const {
ArrayPrototypePush,
ArrayPrototypeSplice,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
DateNow,
Error,
ObjectPrototypeIsPrototypeOf,
Promise,
PromisePrototypeThen,
PromisePrototypeCatch,
SafeArrayIterator,
SafePromisePrototypeFinally,
String,
StringPrototypeEndsWith,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
StringPrototypeTrim,
TypeError,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
const { byteLowerCase } = core.loadExtScript("ext:deno_web/00_infra.js");
const {
errorReadableStream,
getReadableStreamResourceBacking,
readableStreamForRid,
ReadableStreamPrototype,
resourceForReadableStream,
} = core.loadExtScript("ext:deno_web/06_streams.js");
const { extractBody, InnerBody } = core.loadExtScript(
"ext:deno_fetch/22_body.js",
);
const { processUrlList, Request, toInnerRequest } = core.loadExtScript(
"ext:deno_fetch/23_request.js",
);
const {
abortedNetworkError,
fromInnerResponse,
networkError,
nullBodyStatus,
redirectStatus,
toInnerResponse,
} = core.loadExtScript("ext:deno_fetch/23_response.js");
const abortSignal = core.loadExtScript("ext:deno_web/03_abort_signal.js");
const {
builtinTracer,
ContextManager,
enterSpan,
restoreSnapshot,
} = internals.__telemetry;
const __telemetry = internals.__telemetry;
const {
updateSpanFromClientResponse,
updateSpanFromError,
updateSpanFromRequest,
} = internals.__telemetryUtil;
const REQUEST_BODY_HEADER_NAMES = [
"content-encoding",
"content-language",
"content-location",
"content-type",
];
const REDIRECT_SENSITIVE_HEADER_NAMES = [
"authorization",
"proxy-authorization",
"cookie",
];
function getInspectorNetwork() {
const ins = internals.__inspectorNetwork;
if (ins && ins.isEnabled()) return ins;
return null;
}
function safeEmit(fn, params) {
try {
fn(params);
} catch {
}
}
function joinHeaderValuesForCdp(headerList, lowerCaseNames) {
const out = { __proto__: null };
for (let i = 0; i < headerList.length; i++) {
const rawName = headerList[i][0];
const value = String(headerList[i][1]);
const lower = byteLowerCase(rawName);
let name;
if (lowerCaseNames) {
name = lower;
} else if (lower === "set-cookie") {
name = "Set-Cookie";
} else {
name = rawName;
}
let separator;
if (lower === "cookie") {
separator = "; ";
} else if (lower === "set-cookie") {
separator = "\n";
} else {
separator = ", ";
}
if (out[name] === undefined) {
out[name] = value;
} else {
out[name] = out[name] + separator + value;
}
}
return out;
}
function parseContentTypeForCdp(headerList) {
let raw = null;
for (let i = 0; i < headerList.length; i++) {
if (byteLowerCase(headerList[i][0]) === "content-type") {
raw = String(headerList[i][1]);
break;
}
}
if (raw === null) return { mimeType: "", charset: "" };
const semi = StringPrototypeIndexOf(raw, ";");
const mimeType = semi === -1
? StringPrototypeTrim(raw)
: StringPrototypeTrim(StringPrototypeSlice(raw, 0, semi));
let charset = "";
if (semi !== -1) {
const rest = StringPrototypeSlice(raw, semi + 1);
const parts = StringPrototypeSplit(rest, ";");
for (let i = 0; i < parts.length; i++) {
const p = StringPrototypeTrim(parts[i]);
if (
StringPrototypeStartsWith(StringPrototypeToLowerCase(p), "charset=")
) {
charset = StringPrototypeTrim(StringPrototypeSlice(p, 8));
if (
charset.length >= 2 && charset[0] === '"' &&
charset[charset.length - 1] === '"'
) {
charset = StringPrototypeSlice(charset, 1, charset.length - 1);
}
break;
}
}
}
return { mimeType, charset };
}
function drainResponseForInspector(inspectorStream, requestId, ins) {
const reader = inspectorStream.getReader();
let totalLength = 0;
(async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
const len = TypedArrayPrototypeGetByteLength(value);
if (len > 0) {
totalLength += len;
safeEmit(ins.dataReceived, {
requestId,
timestamp: DateNow() / 1000,
dataLength: len,
encodedDataLength: len,
data: value,
});
}
}
}
safeEmit(ins.loadingFinished, {
requestId,
timestamp: DateNow() / 1000,
encodedDataLength: totalLength,
});
} catch (err) {
safeEmit(ins.loadingFailed, {
requestId,
timestamp: DateNow() / 1000,
type: "Fetch",
errorText: err && err.message ? String(err.message) : String(err),
});
} finally {
try {
reader.releaseLock();
} catch {
}
}
})();
}
function opFetchSend(rid) {
return op_fetch_send(rid);
}
function createResponseBodyStream(responseBodyRid, terminator) {
const readable = readableStreamForRid(responseBodyRid);
function onAbort() {
errorReadableStream(readable, terminator.reason);
core.tryClose(responseBodyRid);
}
terminator[abortSignal.add](onAbort);
return readable;
}
async function mainFetch(req, recursive, terminator, inspectorCtx = null) {
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());
terminator[abortSignal.add](() => body.error(terminator.reason));
processUrlList(req.urlList, req.urlListProcessed);
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];
},
urlList: recursive
? []
: [...new SafeArrayIterator(req.urlListProcessed)],
};
}
let reqBody = null;
let reqRid = null;
if (req.body) {
const stream = req.body.streamOrStatic;
const body = stream.body;
if (TypedArrayPrototypeGetSymbolToStringTag(body) === "Uint8Array") {
reqBody = body;
} else if (typeof body === "string") {
reqBody = core.encode(body);
} else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, stream)) {
const resourceBacking = getReadableStreamResourceBacking(stream);
if (resourceBacking) {
reqRid = resourceBacking.rid;
} else {
reqRid = resourceForReadableStream(stream, req.body.length);
}
} else {
throw new TypeError("Invalid body");
}
}
const { requestRid, cancelHandleRid } = op_fetch(
req.method,
req.currentUrl(),
req.headerList,
req.clientRid,
reqBody !== null || reqRid !== null,
reqBody,
reqRid,
);
const inspectorNetwork = getInspectorNetwork();
let inspectorRequestId = inspectorCtx?.requestId ?? null;
const inspectorRedirectResponse = inspectorCtx?.redirectResponse ?? null;
if (
inspectorNetwork !== null && inspectorRequestId === null && !recursive
) {
inspectorRequestId = inspectorNetwork.nextRequestId();
}
if (inspectorRequestId !== null && inspectorNetwork === null) {
inspectorRequestId = null;
}
if (inspectorRequestId !== null) {
const hasPostData = reqBody !== null || reqRid !== null;
let postDataText;
if (
reqBody !== null &&
TypedArrayPrototypeGetSymbolToStringTag(reqBody) === "Uint8Array"
) {
try {
postDataText = core.decode(reqBody);
} catch {
postDataText = undefined;
}
}
const requestHeadersForCdp = joinHeaderValuesForCdp(
req.headerList,
true,
);
let requestCharset = parseContentTypeForCdp(req.headerList).charset ||
undefined;
if (requestCharset === undefined && postDataText !== undefined) {
requestCharset = "utf-8";
}
const requestWillBeSentParams = {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
wallTime: DateNow() / 1000,
type: "Fetch",
request: {
url: req.currentUrl(),
method: req.method,
headers: requestHeadersForCdp,
hasPostData,
postData: postDataText,
},
charset: requestCharset,
};
if (inspectorRedirectResponse !== null) {
requestWillBeSentParams.redirectResponse = inspectorRedirectResponse;
}
safeEmit(inspectorNetwork.requestWillBeSent, requestWillBeSentParams);
if (postDataText !== undefined) {
safeEmit(inspectorNetwork.dataSent, {
requestId: inspectorRequestId,
finished: true,
});
}
}
function onAbort() {
if (cancelHandleRid !== null) {
core.tryClose(cancelHandleRid);
}
}
terminator[abortSignal.add](onAbort);
let resp;
try {
resp = await opFetchSend(requestRid);
} catch (err) {
if (inspectorRequestId !== null) {
safeEmit(inspectorNetwork.loadingFailed, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
type: "Fetch",
errorText: err && err.message ? String(err.message) : String(err),
});
}
if (terminator.aborted) return abortedNetworkError();
throw err;
} finally {
if (cancelHandleRid !== null) {
core.tryClose(cancelHandleRid);
}
}
if (resp.error !== null) {
if (inspectorRequestId !== null) {
safeEmit(inspectorNetwork.loadingFailed, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
type: "Fetch",
errorText: resp.error[0],
});
}
const { 0: message, 1: cause } = resp.error;
throw new TypeError(message, { cause: new Error(cause) });
}
if (terminator.aborted) {
core.tryClose(resp.responseRid);
return abortedNetworkError();
}
processUrlList(req.urlList, req.urlListProcessed);
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.urlListProcessed,
};
const willFollowRedirect = redirectStatus(resp.status) &&
req.redirectMode === "follow";
let cdpResponse = null;
if (inspectorRequestId !== null) {
const { mimeType, charset } = parseContentTypeForCdp(resp.headers);
const responseHeadersForCdp = joinHeaderValuesForCdp(
resp.headers,
false,
);
cdpResponse = {
url: resp.url || req.currentUrl(),
status: resp.status,
statusText: resp.statusText,
headers: responseHeadersForCdp,
mimeType,
charset,
};
if (!willFollowRedirect) {
safeEmit(inspectorNetwork.responseReceived, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
type: "Fetch",
response: cdpResponse,
});
}
}
if (redirectStatus(resp.status)) {
switch (req.redirectMode) {
case "error":
core.close(resp.responseRid);
if (inspectorRequestId !== null) {
safeEmit(inspectorNetwork.loadingFailed, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
type: "Fetch",
errorText:
"Encountered redirect while redirect mode is set to 'error'",
});
}
return networkError(
"Encountered redirect while redirect mode is set to 'error'",
);
case "follow":
core.close(resp.responseRid);
return httpRedirectFetch(
req,
response,
terminator,
inspectorRequestId !== null
? { requestId: inspectorRequestId, redirectResponse: cdpResponse }
: null,
);
case "manual":
break;
}
}
if (nullBodyStatus(response.status)) {
core.close(resp.responseRid);
if (inspectorRequestId !== null) {
safeEmit(inspectorNetwork.loadingFinished, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
encodedDataLength: 0,
});
}
} else {
if (req.method === "HEAD" || req.method === "CONNECT") {
response.body = null;
core.close(resp.responseRid);
if (inspectorRequestId !== null) {
safeEmit(inspectorNetwork.loadingFinished, {
requestId: inspectorRequestId,
timestamp: DateNow() / 1000,
encodedDataLength: 0,
});
}
} else {
let bodyStream = createResponseBodyStream(resp.responseRid, terminator);
if (inspectorRequestId !== null) {
try {
const tee = bodyStream.tee();
bodyStream = tee[0];
drainResponseForInspector(
tee[1],
inspectorRequestId,
inspectorNetwork,
);
} catch {
}
}
response.body = new InnerBody(bodyStream);
}
}
if (recursive) return response;
if (response.urlList.length === 0) {
processUrlList(req.urlList, req.urlListProcessed);
response.urlList = [...new SafeArrayIterator(req.urlListProcessed)];
}
return response;
}
function httpRedirectFetch(request, response, terminator, inspectorCtx = null) {
const locationHeaders = ArrayPrototypeFilter(
response.headerList,
(entry) => byteLowerCase(entry[0]) === "location",
);
if (locationHeaders.length === 0) {
return response;
}
const currentURL = new URL(request.currentUrl());
const locationURL = new URL(
locationHeaders[0][1],
response.url() ?? undefined,
);
if (locationURL.hash === "") {
locationURL.hash = currentURL.hash;
}
if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") {
return networkError("Can not redirect to a non HTTP(s) url");
}
if (request.redirectCount === 20) {
return networkError("Maximum number of redirects (20) reached");
}
request.redirectCount++;
if (
response.status !== 303 &&
request.body !== null &&
request.body.source === null
) {
return networkError(
"Can not redeliver a streaming request body after a redirect",
);
}
if (
((response.status === 301 || response.status === 302) &&
request.method === "POST") ||
(response.status === 303 &&
request.method !== "GET" &&
request.method !== "HEAD")
) {
request.method = "GET";
request.body = null;
for (let i = 0; i < request.headerList.length; i++) {
if (
ArrayPrototypeIncludes(
REQUEST_BODY_HEADER_NAMES,
byteLowerCase(request.headerList[i][0]),
)
) {
ArrayPrototypeSplice(request.headerList, i, 1);
i--;
}
}
}
if (
(locationURL.protocol !== currentURL.protocol &&
locationURL.protocol !== "https:") ||
(locationURL.host !== currentURL.host &&
!isSubdomain(locationURL.host, currentURL.host))
) {
for (let i = 0; i < request.headerList.length; i++) {
if (
ArrayPrototypeIncludes(
REDIRECT_SENSITIVE_HEADER_NAMES,
byteLowerCase(request.headerList[i][0]),
)
) {
ArrayPrototypeSplice(request.headerList, i, 1);
i--;
}
}
}
if (request.body !== null) {
const res = extractBody(request.body.source);
request.body = res.body;
}
ArrayPrototypePush(request.urlList, () => locationURL.href);
return mainFetch(request, true, terminator, inspectorCtx);
}
function fetch(input, init = undefined) {
let span;
let snapshot;
try {
if (__telemetry.TRACING_ENABLED) {
span = builtinTracer().startSpan("fetch", { kind: 2 });
snapshot = enterSpan(span);
}
let opPromise = undefined;
const result = new Promise((resolve, reject) => {
const prefix = "Failed to execute 'fetch'";
webidl.requiredArguments(arguments.length, 1, prefix);
const requestObject = new Request(input, init);
if (span) {
const context = ContextManager.active();
for (
const propagator of new SafeArrayIterator(__telemetry.PROPAGATORS)
) {
propagator.inject(context, requestObject.headers, {
set(carrier, key, value) {
carrier.append(key, value);
},
});
}
updateSpanFromRequest(span, requestObject);
}
const request = toInnerRequest(requestObject);
if (requestObject.signal.aborted) {
if (span) {
updateSpanFromError(span, requestObject.signal.reason);
}
reject(abortFetch(request, null, requestObject.signal.reason));
return;
}
let responseObject = null;
let locallyAborted = false;
function onabort() {
locallyAborted = true;
reject(
abortFetch(request, responseObject, requestObject.signal.reason),
);
}
requestObject.signal[abortSignal.add](onabort);
if (!requestObject.headers.has("Accept")) {
ArrayPrototypePush(request.headerList, ["Accept", "*/*"]);
}
if (!requestObject.headers.has("Accept-Language")) {
ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]);
}
opPromise = PromisePrototypeCatch(
PromisePrototypeThen(
mainFetch(request, false, requestObject.signal),
(response) => {
if (locallyAborted) return;
if (response.aborted) {
reject(
abortFetch(
request,
responseObject,
requestObject.signal.reason,
),
);
requestObject.signal[abortSignal.remove](onabort);
return;
}
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");
if (span) {
updateSpanFromClientResponse(span, responseObject);
}
resolve(responseObject);
requestObject.signal[abortSignal.remove](onabort);
},
),
(err) => {
reject(err);
requestObject.signal[abortSignal.remove](onabort);
},
);
});
if (opPromise) {
PromisePrototypeCatch(result, (e) => {
if (span) {
updateSpanFromError(span, e);
}
});
return (async function fetch() {
try {
await opPromise;
return result;
} finally {
span?.end();
}
})();
}
if (span) {
if (op_fetch_promise_is_settled(result)) {
span?.end();
} else {
return SafePromisePrototypeFinally(result, () => {
span?.end();
});
}
}
return result;
} finally {
if (snapshot) restoreSnapshot(snapshot);
}
}
function abortFetch(request, responseObject, error) {
if (request.body !== null) {
if (!request.body.streamOrStatic.locked) {
request.body.cancel(error);
}
}
if (responseObject !== null) {
const response = toInnerResponse(responseObject);
if (response.body !== null) response.body.error(error);
}
return error;
}
function isSubdomain(subdomain, domain) {
const dot = subdomain.length - domain.length - 1;
return (
dot > 0 &&
subdomain[dot] === "." &&
StringPrototypeEndsWith(subdomain, domain)
);
}
function handleWasmStreaming(source, rid) {
try {
const res = webidl.converters["Response"](
source,
"Failed to execute 'WebAssembly.compileStreaming'",
"Argument 1",
);
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");
}
}
if (!res.ok) {
throw new TypeError(
`Failed to receive WebAssembly content: HTTP status code ${res.status}`,
);
}
op_wasm_streaming_set_url(rid, res.url);
if (res.body !== null) {
PromisePrototypeThen(
(async () => {
const reader = res.body.getReader();
while (true) {
const { value: chunk, done } = await reader.read();
if (done) break;
op_wasm_streaming_feed(rid, chunk);
}
})(),
() => core.close(rid),
(err) => core.abortWasmStreaming(rid, err),
);
} else {
core.close(rid);
}
} catch (err) {
core.abortWasmStreaming(rid, err);
}
}
return { fetch, handleWasmStreaming, mainFetch };
})();