aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/web/fetch
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-14 14:46:37 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-14 14:46:37 -0800
commitafa87af01c79a9baa539f2992d32154d2a4739bd (patch)
tree92c7416db734270a2fee1d72ee9cc119379ff8e1 /vanilla/node_modules/undici/lib/web/fetch
parent3b927e84d200402281f68181cd4253bc77e5528d (diff)
downloadneko-afa87af01c79a9baa539f2992d32154d2a4739bd.tar.gz
neko-afa87af01c79a9baa539f2992d32154d2a4739bd.tar.bz2
neko-afa87af01c79a9baa539f2992d32154d2a4739bd.zip
task: delete vanilla js prototype\n\n- Removed vanilla/ directory and web/dist/vanilla directory\n- Updated Makefile, Dockerfile, and CI workflow to remove vanilla references\n- Cleaned up web/web.go to remove vanilla embed and routes\n- Verified build and tests pass\n\nCloses NK-2tcnmq
Diffstat (limited to 'vanilla/node_modules/undici/lib/web/fetch')
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/LICENSE21
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/body.js509
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/constants.js131
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/data-url.js596
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/formdata-parser.js575
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/formdata.js259
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/global.js40
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/headers.js719
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/index.js2372
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/request.js1115
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/response.js641
-rw-r--r--vanilla/node_modules/undici/lib/web/fetch/util.js1520
12 files changed, 0 insertions, 8498 deletions
diff --git a/vanilla/node_modules/undici/lib/web/fetch/LICENSE b/vanilla/node_modules/undici/lib/web/fetch/LICENSE
deleted file mode 100644
index 2943500..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2020 Ethan Arrowood
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/vanilla/node_modules/undici/lib/web/fetch/body.js b/vanilla/node_modules/undici/lib/web/fetch/body.js
deleted file mode 100644
index 5d17249..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/body.js
+++ /dev/null
@@ -1,509 +0,0 @@
-'use strict'
-
-const util = require('../../core/util')
-const {
- ReadableStreamFrom,
- readableStreamClose,
- fullyReadBody,
- extractMimeType
-} = require('./util')
-const { FormData, setFormDataState } = require('./formdata')
-const { webidl } = require('../webidl')
-const assert = require('node:assert')
-const { isErrored, isDisturbed } = require('node:stream')
-const { isUint8Array } = require('node:util/types')
-const { serializeAMimeType } = require('./data-url')
-const { multipartFormDataParser } = require('./formdata-parser')
-const { createDeferredPromise } = require('../../util/promise')
-const { parseJSONFromBytes } = require('../infra')
-const { utf8DecodeBytes } = require('../../encoding')
-const { runtimeFeatures } = require('../../util/runtime-features.js')
-
-const random = runtimeFeatures.has('crypto')
- ? require('node:crypto').randomInt
- : (max) => Math.floor(Math.random() * max)
-
-const textEncoder = new TextEncoder()
-function noop () {}
-
-const streamRegistry = new FinalizationRegistry((weakRef) => {
- const stream = weakRef.deref()
- if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
- stream.cancel('Response object has been garbage collected').catch(noop)
- }
-})
-
-/**
- * Extract a body with type from a byte sequence or BodyInit object
- *
- * @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
- * @param {boolean} [keepalive=false] - If true, indicates that the body
- * @returns {[{stream: ReadableStream, source: any, length: number | null}, string | null]} - Returns a tuple containing the body and its type
- *
- * @see https://fetch.spec.whatwg.org/#concept-bodyinit-extract
- */
-function extractBody (object, keepalive = false) {
- // 1. Let stream be null.
- let stream = null
- let controller = null
-
- // 2. If object is a ReadableStream object, then set stream to object.
- if (webidl.is.ReadableStream(object)) {
- stream = object
- } else if (webidl.is.Blob(object)) {
- // 3. Otherwise, if object is a Blob object, set stream to the
- // result of running object’s get stream.
- stream = object.stream()
- } else {
- // 4. Otherwise, set stream to a new ReadableStream object, and set
- // up stream with byte reading support.
- stream = new ReadableStream({
- pull () {},
- start (c) {
- controller = c
- },
- cancel () {},
- type: 'bytes'
- })
- }
-
- // 5. Assert: stream is a ReadableStream object.
- assert(webidl.is.ReadableStream(stream))
-
- // 6. Let action be null.
- let action = null
-
- // 7. Let source be null.
- let source = null
-
- // 8. Let length be null.
- let length = null
-
- // 9. Let type be null.
- let type = null
-
- // 10. Switch on object:
- if (typeof object === 'string') {
- // Set source to the UTF-8 encoding of object.
- // Note: setting source to a Uint8Array here breaks some mocking assumptions.
- source = object
-
- // Set type to `text/plain;charset=UTF-8`.
- type = 'text/plain;charset=UTF-8'
- } else if (webidl.is.URLSearchParams(object)) {
- // URLSearchParams
-
- // spec says to run application/x-www-form-urlencoded on body.list
- // this is implemented in Node.js as apart of an URLSearchParams instance toString method
- // See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490
- // and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100
-
- // Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list.
- source = object.toString()
-
- // Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
- type = 'application/x-www-form-urlencoded;charset=UTF-8'
- } else if (webidl.is.BufferSource(object)) {
- // Set source to a copy of the bytes held by object.
- source = webidl.util.getCopyOfBytesHeldByBufferSource(object)
- } else if (webidl.is.FormData(object)) {
- const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
- const prefix = `--${boundary}\r\nContent-Disposition: form-data`
-
- /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
- const formdataEscape = (str) =>
- str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
- const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, '\r\n')
-
- // Set action to this step: run the multipart/form-data
- // encoding algorithm, with object’s entry list and UTF-8.
- // - This ensures that the body is immutable and can't be changed afterwords
- // - That the content-length is calculated in advance.
- // - And that all parts are pre-encoded and ready to be sent.
-
- const blobParts = []
- const rn = new Uint8Array([13, 10]) // '\r\n'
- length = 0
- let hasUnknownSizeValue = false
-
- for (const [name, value] of object) {
- if (typeof value === 'string') {
- const chunk = textEncoder.encode(prefix +
- `; name="${formdataEscape(normalizeLinefeeds(name))}"` +
- `\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
- blobParts.push(chunk)
- length += chunk.byteLength
- } else {
- const chunk = textEncoder.encode(`${prefix}; name="${formdataEscape(normalizeLinefeeds(name))}"` +
- (value.name ? `; filename="${formdataEscape(value.name)}"` : '') + '\r\n' +
- `Content-Type: ${
- value.type || 'application/octet-stream'
- }\r\n\r\n`)
- blobParts.push(chunk, value, rn)
- if (typeof value.size === 'number') {
- length += chunk.byteLength + value.size + rn.byteLength
- } else {
- hasUnknownSizeValue = true
- }
- }
- }
-
- // CRLF is appended to the body to function with legacy servers and match other implementations.
- // https://github.com/curl/curl/blob/3434c6b46e682452973972e8313613dfa58cd690/lib/mime.c#L1029-L1030
- // https://github.com/form-data/form-data/issues/63
- const chunk = textEncoder.encode(`--${boundary}--\r\n`)
- blobParts.push(chunk)
- length += chunk.byteLength
- if (hasUnknownSizeValue) {
- length = null
- }
-
- // Set source to object.
- source = object
-
- action = async function * () {
- for (const part of blobParts) {
- if (part.stream) {
- yield * part.stream()
- } else {
- yield part
- }
- }
- }
-
- // Set type to `multipart/form-data; boundary=`,
- // followed by the multipart/form-data boundary string generated
- // by the multipart/form-data encoding algorithm.
- type = `multipart/form-data; boundary=${boundary}`
- } else if (webidl.is.Blob(object)) {
- // Blob
-
- // Set source to object.
- source = object
-
- // Set length to object’s size.
- length = object.size
-
- // If object’s type attribute is not the empty byte sequence, set
- // type to its value.
- if (object.type) {
- type = object.type
- }
- } else if (typeof object[Symbol.asyncIterator] === 'function') {
- // If keepalive is true, then throw a TypeError.
- if (keepalive) {
- throw new TypeError('keepalive')
- }
-
- // If object is disturbed or locked, then throw a TypeError.
- if (util.isDisturbed(object) || object.locked) {
- throw new TypeError(
- 'Response body object should not be disturbed or locked'
- )
- }
-
- stream =
- webidl.is.ReadableStream(object) ? object : ReadableStreamFrom(object)
- }
-
- // 11. If source is a byte sequence, then set action to a
- // step that returns source and length to source’s length.
- if (typeof source === 'string' || isUint8Array(source)) {
- action = () => {
- length = typeof source === 'string' ? Buffer.byteLength(source) : source.length
- return source
- }
- }
-
- // 12. If action is non-null, then run these steps in parallel:
- if (action != null) {
- ;(async () => {
- // 1. Run action.
- const result = action()
-
- // 2. Whenever one or more bytes are available and stream is not errored,
- // enqueue the result of creating a Uint8Array from the available bytes into stream.
- const iterator = result?.[Symbol.asyncIterator]?.()
- if (iterator) {
- for await (const bytes of iterator) {
- if (isErrored(stream)) break
- if (bytes.length) {
- controller.enqueue(new Uint8Array(bytes))
- }
- }
- } else if (result?.length && !isErrored(stream)) {
- controller.enqueue(typeof result === 'string' ? textEncoder.encode(result) : new Uint8Array(result))
- }
-
- // 3. When running action is done, close stream.
- queueMicrotask(() => readableStreamClose(controller))
- })()
- }
-
- // 13. Let body be a body whose stream is stream, source is source,
- // and length is length.
- const body = { stream, source, length }
-
- // 14. Return (body, type).
- return [body, type]
-}
-
-/**
- * @typedef {object} ExtractBodyResult
- * @property {ReadableStream<Uint8Array<ArrayBuffer>>} stream - The ReadableStream containing the body data
- * @property {any} source - The original source of the body data
- * @property {number | null} length - The length of the body data, or null
- */
-
-/**
- * Safely extract a body with type from a byte sequence or BodyInit object.
- *
- * @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
- * @param {boolean} [keepalive=false] - If true, indicates that the body
- * @returns {[ExtractBodyResult, string | null]} - Returns a tuple containing the body and its type
- *
- * @see https://fetch.spec.whatwg.org/#bodyinit-safely-extract
- */
-function safelyExtractBody (object, keepalive = false) {
- // To safely extract a body and a `Content-Type` value from
- // a byte sequence or BodyInit object object, run these steps:
-
- // 1. If object is a ReadableStream object, then:
- if (webidl.is.ReadableStream(object)) {
- // Assert: object is neither disturbed nor locked.
- assert(!util.isDisturbed(object), 'The body has already been consumed.')
- assert(!object.locked, 'The stream is locked.')
- }
-
- // 2. Return the results of extracting object.
- return extractBody(object, keepalive)
-}
-
-function cloneBody (body) {
- // To clone a body body, run these steps:
-
- // https://fetch.spec.whatwg.org/#concept-body-clone
-
- // 1. Let « out1, out2 » be the result of teeing body’s stream.
- const { 0: out1, 1: out2 } = body.stream.tee()
-
- // 2. Set body’s stream to out1.
- body.stream = out1
-
- // 3. Return a body whose stream is out2 and other members are copied from body.
- return {
- stream: out2,
- length: body.length,
- source: body.source
- }
-}
-
-function bodyMixinMethods (instance, getInternalState) {
- const methods = {
- blob () {
- // The blob() method steps are to return the result of
- // running consume body with this and the following step
- // given a byte sequence bytes: return a Blob whose
- // contents are bytes and whose type attribute is this’s
- // MIME type.
- return consumeBody(this, (bytes) => {
- let mimeType = bodyMimeType(getInternalState(this))
-
- if (mimeType === null) {
- mimeType = ''
- } else if (mimeType) {
- mimeType = serializeAMimeType(mimeType)
- }
-
- // Return a Blob whose contents are bytes and type attribute
- // is mimeType.
- return new Blob([bytes], { type: mimeType })
- }, instance, getInternalState)
- },
-
- arrayBuffer () {
- // The arrayBuffer() method steps are to return the result
- // of running consume body with this and the following step
- // given a byte sequence bytes: return a new ArrayBuffer
- // whose contents are bytes.
- return consumeBody(this, (bytes) => {
- return new Uint8Array(bytes).buffer
- }, instance, getInternalState)
- },
-
- text () {
- // The text() method steps are to return the result of running
- // consume body with this and UTF-8 decode.
- return consumeBody(this, utf8DecodeBytes, instance, getInternalState)
- },
-
- json () {
- // The json() method steps are to return the result of running
- // consume body with this and parse JSON from bytes.
- return consumeBody(this, parseJSONFromBytes, instance, getInternalState)
- },
-
- formData () {
- // The formData() method steps are to return the result of running
- // consume body with this and the following step given a byte sequence bytes:
- return consumeBody(this, (value) => {
- // 1. Let mimeType be the result of get the MIME type with this.
- const mimeType = bodyMimeType(getInternalState(this))
-
- // 2. If mimeType is non-null, then switch on mimeType’s essence and run
- // the corresponding steps:
- if (mimeType !== null) {
- switch (mimeType.essence) {
- case 'multipart/form-data': {
- // 1. ... [long step]
- // 2. If that fails for some reason, then throw a TypeError.
- const parsed = multipartFormDataParser(value, mimeType)
-
- // 3. Return a new FormData object, appending each entry,
- // resulting from the parsing operation, to its entry list.
- const fd = new FormData()
- setFormDataState(fd, parsed)
-
- return fd
- }
- case 'application/x-www-form-urlencoded': {
- // 1. Let entries be the result of parsing bytes.
- const entries = new URLSearchParams(value.toString())
-
- // 2. If entries is failure, then throw a TypeError.
-
- // 3. Return a new FormData object whose entry list is entries.
- const fd = new FormData()
-
- for (const [name, value] of entries) {
- fd.append(name, value)
- }
-
- return fd
- }
- }
- }
-
- // 3. Throw a TypeError.
- throw new TypeError(
- 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
- )
- }, instance, getInternalState)
- },
-
- bytes () {
- // The bytes() method steps are to return the result of running consume body
- // with this and the following step given a byte sequence bytes: return the
- // result of creating a Uint8Array from bytes in this’s relevant realm.
- return consumeBody(this, (bytes) => {
- return new Uint8Array(bytes)
- }, instance, getInternalState)
- }
- }
-
- return methods
-}
-
-function mixinBody (prototype, getInternalState) {
- Object.assign(prototype.prototype, bodyMixinMethods(prototype, getInternalState))
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-body-consume-body
- * @param {any} object internal state
- * @param {(value: unknown) => unknown} convertBytesToJSValue
- * @param {any} instance
- * @param {(target: any) => any} getInternalState
- */
-function consumeBody (object, convertBytesToJSValue, instance, getInternalState) {
- try {
- webidl.brandCheck(object, instance)
- } catch (e) {
- return Promise.reject(e)
- }
-
- object = getInternalState(object)
-
- // 1. If object is unusable, then return a promise rejected
- // with a TypeError.
- if (bodyUnusable(object)) {
- return Promise.reject(new TypeError('Body is unusable: Body has already been read'))
- }
-
- // 2. Let promise be a new promise.
- const promise = createDeferredPromise()
-
- // 3. Let errorSteps given error be to reject promise with error.
- const errorSteps = promise.reject
-
- // 4. Let successSteps given a byte sequence data be to resolve
- // promise with the result of running convertBytesToJSValue
- // with data. If that threw an exception, then run errorSteps
- // with that exception.
- const successSteps = (data) => {
- try {
- promise.resolve(convertBytesToJSValue(data))
- } catch (e) {
- errorSteps(e)
- }
- }
-
- // 5. If object’s body is null, then run successSteps with an
- // empty byte sequence.
- if (object.body == null) {
- successSteps(Buffer.allocUnsafe(0))
- return promise.promise
- }
-
- // 6. Otherwise, fully read object’s body given successSteps,
- // errorSteps, and object’s relevant global object.
- fullyReadBody(object.body, successSteps, errorSteps)
-
- // 7. Return promise.
- return promise.promise
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#body-unusable
- * @param {any} object internal state
- */
-function bodyUnusable (object) {
- const body = object.body
-
- // An object including the Body interface mixin is
- // said to be unusable if its body is non-null and
- // its body’s stream is disturbed or locked.
- return body != null && (body.stream.locked || util.isDisturbed(body.stream))
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-body-mime-type
- * @param {any} requestOrResponse internal state
- */
-function bodyMimeType (requestOrResponse) {
- // 1. Let headers be null.
- // 2. If requestOrResponse is a Request object, then set headers to requestOrResponse’s request’s header list.
- // 3. Otherwise, set headers to requestOrResponse’s response’s header list.
- /** @type {import('./headers').HeadersList} */
- const headers = requestOrResponse.headersList
-
- // 4. Let mimeType be the result of extracting a MIME type from headers.
- const mimeType = extractMimeType(headers)
-
- // 5. If mimeType is failure, then return null.
- if (mimeType === 'failure') {
- return null
- }
-
- // 6. Return mimeType.
- return mimeType
-}
-
-module.exports = {
- extractBody,
- safelyExtractBody,
- cloneBody,
- mixinBody,
- streamRegistry,
- bodyUnusable
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/constants.js b/vanilla/node_modules/undici/lib/web/fetch/constants.js
deleted file mode 100644
index ef63b0c..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/constants.js
+++ /dev/null
@@ -1,131 +0,0 @@
-'use strict'
-
-const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST'])
-const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
-
-const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304])
-
-const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308])
-const redirectStatusSet = new Set(redirectStatus)
-
-/**
- * @see https://fetch.spec.whatwg.org/#block-bad-port
- */
-const badPorts = /** @type {const} */ ([
- '1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
- '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
- '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
- '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
- '2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679',
- '6697', '10080'
-])
-const badPortsSet = new Set(badPorts)
-
-/**
- * @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header
- */
-const referrerPolicyTokens = /** @type {const} */ ([
- 'no-referrer',
- 'no-referrer-when-downgrade',
- 'same-origin',
- 'origin',
- 'strict-origin',
- 'origin-when-cross-origin',
- 'strict-origin-when-cross-origin',
- 'unsafe-url'
-])
-
-/**
- * @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
- */
-const referrerPolicy = /** @type {const} */ ([
- '',
- ...referrerPolicyTokens
-])
-const referrerPolicyTokensSet = new Set(referrerPolicyTokens)
-
-const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error'])
-
-const safeMethods = /** @type {const} */ (['GET', 'HEAD', 'OPTIONS', 'TRACE'])
-const safeMethodsSet = new Set(safeMethods)
-
-const requestMode = /** @type {const} */ (['navigate', 'same-origin', 'no-cors', 'cors'])
-
-const requestCredentials = /** @type {const} */ (['omit', 'same-origin', 'include'])
-
-const requestCache = /** @type {const} */ ([
- 'default',
- 'no-store',
- 'reload',
- 'no-cache',
- 'force-cache',
- 'only-if-cached'
-])
-
-/**
- * @see https://fetch.spec.whatwg.org/#request-body-header-name
- */
-const requestBodyHeader = /** @type {const} */ ([
- 'content-encoding',
- 'content-language',
- 'content-location',
- 'content-type',
- // See https://github.com/nodejs/undici/issues/2021
- // 'Content-Length' is a forbidden header name, which is typically
- // removed in the Headers implementation. However, undici doesn't
- // filter out headers, so we add it here.
- 'content-length'
-])
-
-/**
- * @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
- */
-const requestDuplex = /** @type {const} */ ([
- 'half'
-])
-
-/**
- * @see http://fetch.spec.whatwg.org/#forbidden-method
- */
-const forbiddenMethods = /** @type {const} */ (['CONNECT', 'TRACE', 'TRACK'])
-const forbiddenMethodsSet = new Set(forbiddenMethods)
-
-const subresource = /** @type {const} */ ([
- 'audio',
- 'audioworklet',
- 'font',
- 'image',
- 'manifest',
- 'paintworklet',
- 'script',
- 'style',
- 'track',
- 'video',
- 'xslt',
- ''
-])
-const subresourceSet = new Set(subresource)
-
-module.exports = {
- subresource,
- forbiddenMethods,
- requestBodyHeader,
- referrerPolicy,
- requestRedirect,
- requestMode,
- requestCredentials,
- requestCache,
- redirectStatus,
- corsSafeListedMethods,
- nullBodyStatus,
- safeMethods,
- badPorts,
- requestDuplex,
- subresourceSet,
- badPortsSet,
- redirectStatusSet,
- corsSafeListedMethodsSet,
- safeMethodsSet,
- forbiddenMethodsSet,
- referrerPolicyTokens: referrerPolicyTokensSet
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/data-url.js b/vanilla/node_modules/undici/lib/web/fetch/data-url.js
deleted file mode 100644
index 27ad7ae..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/data-url.js
+++ /dev/null
@@ -1,596 +0,0 @@
-'use strict'
-
-const assert = require('node:assert')
-const { forgivingBase64, collectASequenceOfCodePoints, collectASequenceOfCodePointsFast, isomorphicDecode, removeASCIIWhitespace, removeChars } = require('../infra')
-
-const encoder = new TextEncoder()
-
-/**
- * @see https://mimesniff.spec.whatwg.org/#http-token-code-point
- */
-const HTTP_TOKEN_CODEPOINTS = /^[-!#$%&'*+.^_|~A-Za-z0-9]+$/u
-const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/u // eslint-disable-line
-
-/**
- * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
- */
-const HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/u // eslint-disable-line
-
-// https://fetch.spec.whatwg.org/#data-url-processor
-/** @param {URL} dataURL */
-function dataURLProcessor (dataURL) {
- // 1. Assert: dataURL’s scheme is "data".
- assert(dataURL.protocol === 'data:')
-
- // 2. Let input be the result of running the URL
- // serializer on dataURL with exclude fragment
- // set to true.
- let input = URLSerializer(dataURL, true)
-
- // 3. Remove the leading "data:" string from input.
- input = input.slice(5)
-
- // 4. Let position point at the start of input.
- const position = { position: 0 }
-
- // 5. Let mimeType be the result of collecting a
- // sequence of code points that are not equal
- // to U+002C (,), given position.
- let mimeType = collectASequenceOfCodePointsFast(
- ',',
- input,
- position
- )
-
- // 6. Strip leading and trailing ASCII whitespace
- // from mimeType.
- // Undici implementation note: we need to store the
- // length because if the mimetype has spaces removed,
- // the wrong amount will be sliced from the input in
- // step #9
- const mimeTypeLength = mimeType.length
- mimeType = removeASCIIWhitespace(mimeType, true, true)
-
- // 7. If position is past the end of input, then
- // return failure
- if (position.position >= input.length) {
- return 'failure'
- }
-
- // 8. Advance position by 1.
- position.position++
-
- // 9. Let encodedBody be the remainder of input.
- const encodedBody = input.slice(mimeTypeLength + 1)
-
- // 10. Let body be the percent-decoding of encodedBody.
- let body = stringPercentDecode(encodedBody)
-
- // 11. If mimeType ends with U+003B (;), followed by
- // zero or more U+0020 SPACE, followed by an ASCII
- // case-insensitive match for "base64", then:
- if (/;(?:\u0020*)base64$/ui.test(mimeType)) {
- // 1. Let stringBody be the isomorphic decode of body.
- const stringBody = isomorphicDecode(body)
-
- // 2. Set body to the forgiving-base64 decode of
- // stringBody.
- body = forgivingBase64(stringBody)
-
- // 3. If body is failure, then return failure.
- if (body === 'failure') {
- return 'failure'
- }
-
- // 4. Remove the last 6 code points from mimeType.
- mimeType = mimeType.slice(0, -6)
-
- // 5. Remove trailing U+0020 SPACE code points from mimeType,
- // if any.
- mimeType = mimeType.replace(/(\u0020+)$/u, '')
-
- // 6. Remove the last U+003B (;) code point from mimeType.
- mimeType = mimeType.slice(0, -1)
- }
-
- // 12. If mimeType starts with U+003B (;), then prepend
- // "text/plain" to mimeType.
- if (mimeType.startsWith(';')) {
- mimeType = 'text/plain' + mimeType
- }
-
- // 13. Let mimeTypeRecord be the result of parsing
- // mimeType.
- let mimeTypeRecord = parseMIMEType(mimeType)
-
- // 14. If mimeTypeRecord is failure, then set
- // mimeTypeRecord to text/plain;charset=US-ASCII.
- if (mimeTypeRecord === 'failure') {
- mimeTypeRecord = parseMIMEType('text/plain;charset=US-ASCII')
- }
-
- // 15. Return a new data: URL struct whose MIME
- // type is mimeTypeRecord and body is body.
- // https://fetch.spec.whatwg.org/#data-url-struct
- return { mimeType: mimeTypeRecord, body }
-}
-
-// https://url.spec.whatwg.org/#concept-url-serializer
-/**
- * @param {URL} url
- * @param {boolean} excludeFragment
- */
-function URLSerializer (url, excludeFragment = false) {
- if (!excludeFragment) {
- return url.href
- }
-
- const href = url.href
- const hashLength = url.hash.length
-
- const serialized = hashLength === 0 ? href : href.substring(0, href.length - hashLength)
-
- if (!hashLength && href.endsWith('#')) {
- return serialized.slice(0, -1)
- }
-
- return serialized
-}
-
-// https://url.spec.whatwg.org/#string-percent-decode
-/** @param {string} input */
-function stringPercentDecode (input) {
- // 1. Let bytes be the UTF-8 encoding of input.
- const bytes = encoder.encode(input)
-
- // 2. Return the percent-decoding of bytes.
- return percentDecode(bytes)
-}
-
-/**
- * @param {number} byte
- */
-function isHexCharByte (byte) {
- // 0-9 A-F a-f
- return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) || (byte >= 0x61 && byte <= 0x66)
-}
-
-/**
- * @param {number} byte
- */
-function hexByteToNumber (byte) {
- return (
- // 0-9
- byte >= 0x30 && byte <= 0x39
- ? (byte - 48)
- // Convert to uppercase
- // ((byte & 0xDF) - 65) + 10
- : ((byte & 0xDF) - 55)
- )
-}
-
-// https://url.spec.whatwg.org/#percent-decode
-/** @param {Uint8Array} input */
-function percentDecode (input) {
- const length = input.length
- // 1. Let output be an empty byte sequence.
- /** @type {Uint8Array} */
- const output = new Uint8Array(length)
- let j = 0
- let i = 0
- // 2. For each byte byte in input:
- while (i < length) {
- const byte = input[i]
-
- // 1. If byte is not 0x25 (%), then append byte to output.
- if (byte !== 0x25) {
- output[j++] = byte
-
- // 2. Otherwise, if byte is 0x25 (%) and the next two bytes
- // after byte in input are not in the ranges
- // 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
- // and 0x61 (a) to 0x66 (f), all inclusive, append byte
- // to output.
- } else if (
- byte === 0x25 &&
- !(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
- ) {
- output[j++] = 0x25
-
- // 3. Otherwise:
- } else {
- // 1. Let bytePoint be the two bytes after byte in input,
- // decoded, and then interpreted as hexadecimal number.
- // 2. Append a byte whose value is bytePoint to output.
- output[j++] = (hexByteToNumber(input[i + 1]) << 4) | hexByteToNumber(input[i + 2])
-
- // 3. Skip the next two bytes in input.
- i += 2
- }
- ++i
- }
-
- // 3. Return output.
- return length === j ? output : output.subarray(0, j)
-}
-
-// https://mimesniff.spec.whatwg.org/#parse-a-mime-type
-/** @param {string} input */
-function parseMIMEType (input) {
- // 1. Remove any leading and trailing HTTP whitespace
- // from input.
- input = removeHTTPWhitespace(input, true, true)
-
- // 2. Let position be a position variable for input,
- // initially pointing at the start of input.
- const position = { position: 0 }
-
- // 3. Let type be the result of collecting a sequence
- // of code points that are not U+002F (/) from
- // input, given position.
- const type = collectASequenceOfCodePointsFast(
- '/',
- input,
- position
- )
-
- // 4. If type is the empty string or does not solely
- // contain HTTP token code points, then return failure.
- // https://mimesniff.spec.whatwg.org/#http-token-code-point
- if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) {
- return 'failure'
- }
-
- // 5. If position is past the end of input, then return
- // failure
- if (position.position >= input.length) {
- return 'failure'
- }
-
- // 6. Advance position by 1. (This skips past U+002F (/).)
- position.position++
-
- // 7. Let subtype be the result of collecting a sequence of
- // code points that are not U+003B (;) from input, given
- // position.
- let subtype = collectASequenceOfCodePointsFast(
- ';',
- input,
- position
- )
-
- // 8. Remove any trailing HTTP whitespace from subtype.
- subtype = removeHTTPWhitespace(subtype, false, true)
-
- // 9. If subtype is the empty string or does not solely
- // contain HTTP token code points, then return failure.
- if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) {
- return 'failure'
- }
-
- const typeLowercase = type.toLowerCase()
- const subtypeLowercase = subtype.toLowerCase()
-
- // 10. Let mimeType be a new MIME type record whose type
- // is type, in ASCII lowercase, and subtype is subtype,
- // in ASCII lowercase.
- // https://mimesniff.spec.whatwg.org/#mime-type
- const mimeType = {
- type: typeLowercase,
- subtype: subtypeLowercase,
- /** @type {Map<string, string>} */
- parameters: new Map(),
- // https://mimesniff.spec.whatwg.org/#mime-type-essence
- essence: `${typeLowercase}/${subtypeLowercase}`
- }
-
- // 11. While position is not past the end of input:
- while (position.position < input.length) {
- // 1. Advance position by 1. (This skips past U+003B (;).)
- position.position++
-
- // 2. Collect a sequence of code points that are HTTP
- // whitespace from input given position.
- collectASequenceOfCodePoints(
- // https://fetch.spec.whatwg.org/#http-whitespace
- char => HTTP_WHITESPACE_REGEX.test(char),
- input,
- position
- )
-
- // 3. Let parameterName be the result of collecting a
- // sequence of code points that are not U+003B (;)
- // or U+003D (=) from input, given position.
- let parameterName = collectASequenceOfCodePoints(
- (char) => char !== ';' && char !== '=',
- input,
- position
- )
-
- // 4. Set parameterName to parameterName, in ASCII
- // lowercase.
- parameterName = parameterName.toLowerCase()
-
- // 5. If position is not past the end of input, then:
- if (position.position < input.length) {
- // 1. If the code point at position within input is
- // U+003B (;), then continue.
- if (input[position.position] === ';') {
- continue
- }
-
- // 2. Advance position by 1. (This skips past U+003D (=).)
- position.position++
- }
-
- // 6. If position is past the end of input, then break.
- if (position.position >= input.length) {
- break
- }
-
- // 7. Let parameterValue be null.
- let parameterValue = null
-
- // 8. If the code point at position within input is
- // U+0022 ("), then:
- if (input[position.position] === '"') {
- // 1. Set parameterValue to the result of collecting
- // an HTTP quoted string from input, given position
- // and the extract-value flag.
- parameterValue = collectAnHTTPQuotedString(input, position, true)
-
- // 2. Collect a sequence of code points that are not
- // U+003B (;) from input, given position.
- collectASequenceOfCodePointsFast(
- ';',
- input,
- position
- )
-
- // 9. Otherwise:
- } else {
- // 1. Set parameterValue to the result of collecting
- // a sequence of code points that are not U+003B (;)
- // from input, given position.
- parameterValue = collectASequenceOfCodePointsFast(
- ';',
- input,
- position
- )
-
- // 2. Remove any trailing HTTP whitespace from parameterValue.
- parameterValue = removeHTTPWhitespace(parameterValue, false, true)
-
- // 3. If parameterValue is the empty string, then continue.
- if (parameterValue.length === 0) {
- continue
- }
- }
-
- // 10. If all of the following are true
- // - parameterName is not the empty string
- // - parameterName solely contains HTTP token code points
- // - parameterValue solely contains HTTP quoted-string token code points
- // - mimeType’s parameters[parameterName] does not exist
- // then set mimeType’s parameters[parameterName] to parameterValue.
- if (
- parameterName.length !== 0 &&
- HTTP_TOKEN_CODEPOINTS.test(parameterName) &&
- (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) &&
- !mimeType.parameters.has(parameterName)
- ) {
- mimeType.parameters.set(parameterName, parameterValue)
- }
- }
-
- // 12. Return mimeType.
- return mimeType
-}
-
-// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
-// tests: https://fetch.spec.whatwg.org/#example-http-quoted-string
-/**
- * @param {string} input
- * @param {{ position: number }} position
- * @param {boolean} [extractValue=false]
- */
-function collectAnHTTPQuotedString (input, position, extractValue = false) {
- // 1. Let positionStart be position.
- const positionStart = position.position
-
- // 2. Let value be the empty string.
- let value = ''
-
- // 3. Assert: the code point at position within input
- // is U+0022 (").
- assert(input[position.position] === '"')
-
- // 4. Advance position by 1.
- position.position++
-
- // 5. While true:
- while (true) {
- // 1. Append the result of collecting a sequence of code points
- // that are not U+0022 (") or U+005C (\) from input, given
- // position, to value.
- value += collectASequenceOfCodePoints(
- (char) => char !== '"' && char !== '\\',
- input,
- position
- )
-
- // 2. If position is past the end of input, then break.
- if (position.position >= input.length) {
- break
- }
-
- // 3. Let quoteOrBackslash be the code point at position within
- // input.
- const quoteOrBackslash = input[position.position]
-
- // 4. Advance position by 1.
- position.position++
-
- // 5. If quoteOrBackslash is U+005C (\), then:
- if (quoteOrBackslash === '\\') {
- // 1. If position is past the end of input, then append
- // U+005C (\) to value and break.
- if (position.position >= input.length) {
- value += '\\'
- break
- }
-
- // 2. Append the code point at position within input to value.
- value += input[position.position]
-
- // 3. Advance position by 1.
- position.position++
-
- // 6. Otherwise:
- } else {
- // 1. Assert: quoteOrBackslash is U+0022 (").
- assert(quoteOrBackslash === '"')
-
- // 2. Break.
- break
- }
- }
-
- // 6. If the extract-value flag is set, then return value.
- if (extractValue) {
- return value
- }
-
- // 7. Return the code points from positionStart to position,
- // inclusive, within input.
- return input.slice(positionStart, position.position)
-}
-
-/**
- * @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type
- */
-function serializeAMimeType (mimeType) {
- assert(mimeType !== 'failure')
- const { parameters, essence } = mimeType
-
- // 1. Let serialization be the concatenation of mimeType’s
- // type, U+002F (/), and mimeType’s subtype.
- let serialization = essence
-
- // 2. For each name → value of mimeType’s parameters:
- for (let [name, value] of parameters.entries()) {
- // 1. Append U+003B (;) to serialization.
- serialization += ';'
-
- // 2. Append name to serialization.
- serialization += name
-
- // 3. Append U+003D (=) to serialization.
- serialization += '='
-
- // 4. If value does not solely contain HTTP token code
- // points or value is the empty string, then:
- if (!HTTP_TOKEN_CODEPOINTS.test(value)) {
- // 1. Precede each occurrence of U+0022 (") or
- // U+005C (\) in value with U+005C (\).
- value = value.replace(/[\\"]/ug, '\\$&')
-
- // 2. Prepend U+0022 (") to value.
- value = '"' + value
-
- // 3. Append U+0022 (") to value.
- value += '"'
- }
-
- // 5. Append value to serialization.
- serialization += value
- }
-
- // 3. Return serialization.
- return serialization
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#http-whitespace
- * @param {number} char
- */
-function isHTTPWhiteSpace (char) {
- // "\r\n\t "
- return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x020
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#http-whitespace
- * @param {string} str
- * @param {boolean} [leading=true]
- * @param {boolean} [trailing=true]
- */
-function removeHTTPWhitespace (str, leading = true, trailing = true) {
- return removeChars(str, leading, trailing, isHTTPWhiteSpace)
-}
-
-/**
- * @see https://mimesniff.spec.whatwg.org/#minimize-a-supported-mime-type
- * @param {Exclude<ReturnType<typeof parseMIMEType>, 'failure'>} mimeType
- */
-function minimizeSupportedMimeType (mimeType) {
- switch (mimeType.essence) {
- case 'application/ecmascript':
- case 'application/javascript':
- case 'application/x-ecmascript':
- case 'application/x-javascript':
- case 'text/ecmascript':
- case 'text/javascript':
- case 'text/javascript1.0':
- case 'text/javascript1.1':
- case 'text/javascript1.2':
- case 'text/javascript1.3':
- case 'text/javascript1.4':
- case 'text/javascript1.5':
- case 'text/jscript':
- case 'text/livescript':
- case 'text/x-ecmascript':
- case 'text/x-javascript':
- // 1. If mimeType is a JavaScript MIME type, then return "text/javascript".
- return 'text/javascript'
- case 'application/json':
- case 'text/json':
- // 2. If mimeType is a JSON MIME type, then return "application/json".
- return 'application/json'
- case 'image/svg+xml':
- // 3. If mimeType’s essence is "image/svg+xml", then return "image/svg+xml".
- return 'image/svg+xml'
- case 'text/xml':
- case 'application/xml':
- // 4. If mimeType is an XML MIME type, then return "application/xml".
- return 'application/xml'
- }
-
- // 2. If mimeType is a JSON MIME type, then return "application/json".
- if (mimeType.subtype.endsWith('+json')) {
- return 'application/json'
- }
-
- // 4. If mimeType is an XML MIME type, then return "application/xml".
- if (mimeType.subtype.endsWith('+xml')) {
- return 'application/xml'
- }
-
- // 5. If mimeType is supported by the user agent, then return mimeType’s essence.
- // Technically, node doesn't support any mimetypes.
-
- // 6. Return the empty string.
- return ''
-}
-
-module.exports = {
- dataURLProcessor,
- URLSerializer,
- stringPercentDecode,
- parseMIMEType,
- collectAnHTTPQuotedString,
- serializeAMimeType,
- removeHTTPWhitespace,
- minimizeSupportedMimeType,
- HTTP_TOKEN_CODEPOINTS
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/formdata-parser.js b/vanilla/node_modules/undici/lib/web/fetch/formdata-parser.js
deleted file mode 100644
index 4ba204c..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/formdata-parser.js
+++ /dev/null
@@ -1,575 +0,0 @@
-'use strict'
-
-const { bufferToLowerCasedHeaderName } = require('../../core/util')
-const { HTTP_TOKEN_CODEPOINTS } = require('./data-url')
-const { makeEntry } = require('./formdata')
-const { webidl } = require('../webidl')
-const assert = require('node:assert')
-const { isomorphicDecode } = require('../infra')
-const { utf8DecodeBytes } = require('../../encoding')
-
-const dd = Buffer.from('--')
-const decoder = new TextDecoder()
-
-/**
- * @param {string} chars
- */
-function isAsciiString (chars) {
- for (let i = 0; i < chars.length; ++i) {
- if ((chars.charCodeAt(i) & ~0x7F) !== 0) {
- return false
- }
- }
- return true
-}
-
-/**
- * @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-boundary
- * @param {string} boundary
- */
-function validateBoundary (boundary) {
- const length = boundary.length
-
- // - its length is greater or equal to 27 and lesser or equal to 70, and
- if (length < 27 || length > 70) {
- return false
- }
-
- // - it is composed by bytes in the ranges 0x30 to 0x39, 0x41 to 0x5A, or
- // 0x61 to 0x7A, inclusive (ASCII alphanumeric), or which are 0x27 ('),
- // 0x2D (-) or 0x5F (_).
- for (let i = 0; i < length; ++i) {
- const cp = boundary.charCodeAt(i)
-
- if (!(
- (cp >= 0x30 && cp <= 0x39) ||
- (cp >= 0x41 && cp <= 0x5a) ||
- (cp >= 0x61 && cp <= 0x7a) ||
- cp === 0x27 ||
- cp === 0x2d ||
- cp === 0x5f
- )) {
- return false
- }
- }
-
- return true
-}
-
-/**
- * @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-parser
- * @param {Buffer} input
- * @param {ReturnType<import('./data-url')['parseMIMEType']>} mimeType
- */
-function multipartFormDataParser (input, mimeType) {
- // 1. Assert: mimeType’s essence is "multipart/form-data".
- assert(mimeType !== 'failure' && mimeType.essence === 'multipart/form-data')
-
- const boundaryString = mimeType.parameters.get('boundary')
-
- // 2. If mimeType’s parameters["boundary"] does not exist, return failure.
- // Otherwise, let boundary be the result of UTF-8 decoding mimeType’s
- // parameters["boundary"].
- if (boundaryString === undefined) {
- throw parsingError('missing boundary in content-type header')
- }
-
- const boundary = Buffer.from(`--${boundaryString}`, 'utf8')
-
- // 3. Let entry list be an empty entry list.
- const entryList = []
-
- // 4. Let position be a pointer to a byte in input, initially pointing at
- // the first byte.
- const position = { position: 0 }
-
- // Note: Per RFC 2046 Section 5.1.1, we must ignore anything before the
- // first boundary delimiter line (preamble). Search for the first boundary.
- const firstBoundaryIndex = input.indexOf(boundary)
-
- if (firstBoundaryIndex === -1) {
- throw parsingError('no boundary found in multipart body')
- }
-
- // Start parsing from the first boundary, ignoring any preamble
- position.position = firstBoundaryIndex
-
- // 5. While true:
- while (true) {
- // 5.1. If position points to a sequence of bytes starting with 0x2D 0x2D
- // (`--`) followed by boundary, advance position by 2 + the length of
- // boundary. Otherwise, return failure.
- // Note: boundary is padded with 2 dashes already, no need to add 2.
- if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) {
- position.position += boundary.length
- } else {
- throw parsingError('expected a value starting with -- and the boundary')
- }
-
- // 5.2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A
- // (`--` followed by CR LF) followed by the end of input, return entry list.
- // Note: Per RFC 2046 Section 5.1.1, we must ignore anything after the
- // final boundary delimiter (epilogue). Check for -- or --CRLF and return
- // regardless of what follows.
- if (bufferStartsWith(input, dd, position)) {
- // Found closing boundary delimiter (--), ignore any epilogue
- return entryList
- }
-
- // 5.3. If position does not point to a sequence of bytes starting with 0x0D
- // 0x0A (CR LF), return failure.
- if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
- throw parsingError('expected CRLF')
- }
-
- // 5.4. Advance position by 2. (This skips past the newline.)
- position.position += 2
-
- // 5.5. Let name, filename and contentType be the result of parsing
- // multipart/form-data headers on input and position, if the result
- // is not failure. Otherwise, return failure.
- const result = parseMultipartFormDataHeaders(input, position)
-
- let { name, filename, contentType, encoding } = result
-
- // 5.6. Advance position by 2. (This skips past the empty line that marks
- // the end of the headers.)
- position.position += 2
-
- // 5.7. Let body be the empty byte sequence.
- let body
-
- // 5.8. Body loop: While position is not past the end of input:
- // TODO: the steps here are completely wrong
- {
- const boundaryIndex = input.indexOf(boundary.subarray(2), position.position)
-
- if (boundaryIndex === -1) {
- throw parsingError('expected boundary after body')
- }
-
- body = input.subarray(position.position, boundaryIndex - 4)
-
- position.position += body.length
-
- // Note: position must be advanced by the body's length before being
- // decoded, otherwise the parsing will fail.
- if (encoding === 'base64') {
- body = Buffer.from(body.toString(), 'base64')
- }
- }
-
- // 5.9. If position does not point to a sequence of bytes starting with
- // 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2.
- if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
- throw parsingError('expected CRLF')
- } else {
- position.position += 2
- }
-
- // 5.10. If filename is not null:
- let value
-
- if (filename !== null) {
- // 5.10.1. If contentType is null, set contentType to "text/plain".
- contentType ??= 'text/plain'
-
- // 5.10.2. If contentType is not an ASCII string, set contentType to the empty string.
-
- // Note: `buffer.isAscii` can be used at zero-cost, but converting a string to a buffer is a high overhead.
- // Content-Type is a relatively small string, so it is faster to use `String#charCodeAt`.
- if (!isAsciiString(contentType)) {
- contentType = ''
- }
-
- // 5.10.3. Let value be a new File object with name filename, type contentType, and body body.
- value = new File([body], filename, { type: contentType })
- } else {
- // 5.11. Otherwise:
-
- // 5.11.1. Let value be the UTF-8 decoding without BOM of body.
- value = utf8DecodeBytes(Buffer.from(body))
- }
-
- // 5.12. Assert: name is a scalar value string and value is either a scalar value string or a File object.
- assert(webidl.is.USVString(name))
- assert((typeof value === 'string' && webidl.is.USVString(value)) || webidl.is.File(value))
-
- // 5.13. Create an entry with name and value, and append it to entry list.
- entryList.push(makeEntry(name, value, filename))
- }
-}
-
-/**
- * Parses content-disposition attributes (e.g., name="value" or filename*=utf-8''encoded)
- * @param {Buffer} input
- * @param {{ position: number }} position
- * @returns {{ name: string, value: string }}
- */
-function parseContentDispositionAttribute (input, position) {
- // Skip leading semicolon and whitespace
- if (input[position.position] === 0x3b /* ; */) {
- position.position++
- }
-
- // Skip whitespace
- collectASequenceOfBytes(
- (char) => char === 0x20 || char === 0x09,
- input,
- position
- )
-
- // Collect attribute name (token characters)
- const attributeName = collectASequenceOfBytes(
- (char) => isToken(char) && char !== 0x3d && char !== 0x2a, // not = or *
- input,
- position
- )
-
- if (attributeName.length === 0) {
- return null
- }
-
- const attrNameStr = attributeName.toString('ascii').toLowerCase()
-
- // Check for extended notation (attribute*)
- const isExtended = input[position.position] === 0x2a /* * */
- if (isExtended) {
- position.position++ // skip *
- }
-
- // Expect = sign
- if (input[position.position] !== 0x3d /* = */) {
- return null
- }
- position.position++ // skip =
-
- // Skip whitespace
- collectASequenceOfBytes(
- (char) => char === 0x20 || char === 0x09,
- input,
- position
- )
-
- let value
-
- if (isExtended) {
- // Extended attribute format: charset'language'encoded-value
- const headerValue = collectASequenceOfBytes(
- (char) => char !== 0x20 && char !== 0x0d && char !== 0x0a && char !== 0x3b, // not space, CRLF, or ;
- input,
- position
- )
-
- // Check for utf-8'' prefix (case insensitive)
- if (
- (headerValue[0] !== 0x75 && headerValue[0] !== 0x55) || // u or U
- (headerValue[1] !== 0x74 && headerValue[1] !== 0x54) || // t or T
- (headerValue[2] !== 0x66 && headerValue[2] !== 0x46) || // f or F
- headerValue[3] !== 0x2d || // -
- headerValue[4] !== 0x38 // 8
- ) {
- throw parsingError('unknown encoding, expected utf-8\'\'')
- }
-
- // Skip utf-8'' and decode the rest
- value = decodeURIComponent(decoder.decode(headerValue.subarray(7)))
- } else if (input[position.position] === 0x22 /* " */) {
- // Quoted string
- position.position++ // skip opening quote
-
- const quotedValue = collectASequenceOfBytes(
- (char) => char !== 0x0a && char !== 0x0d && char !== 0x22, // not LF, CR, or "
- input,
- position
- )
-
- if (input[position.position] !== 0x22) {
- throw parsingError('Closing quote not found')
- }
- position.position++ // skip closing quote
-
- value = decoder.decode(quotedValue)
- .replace(/%0A/ig, '\n')
- .replace(/%0D/ig, '\r')
- .replace(/%22/g, '"')
- } else {
- // Token value (no quotes)
- const tokenValue = collectASequenceOfBytes(
- (char) => isToken(char) && char !== 0x3b, // not ;
- input,
- position
- )
-
- value = decoder.decode(tokenValue)
- }
-
- return { name: attrNameStr, value }
-}
-
-/**
- * @see https://andreubotella.github.io/multipart-form-data/#parse-multipart-form-data-headers
- * @param {Buffer} input
- * @param {{ position: number }} position
- */
-function parseMultipartFormDataHeaders (input, position) {
- // 1. Let name, filename and contentType be null.
- let name = null
- let filename = null
- let contentType = null
- let encoding = null
-
- // 2. While true:
- while (true) {
- // 2.1. If position points to a sequence of bytes starting with 0x0D 0x0A (CR LF):
- if (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
- // 2.1.1. If name is null, return failure.
- if (name === null) {
- throw parsingError('header name is null')
- }
-
- // 2.1.2. Return name, filename and contentType.
- return { name, filename, contentType, encoding }
- }
-
- // 2.2. Let header name be the result of collecting a sequence of bytes that are
- // not 0x0A (LF), 0x0D (CR) or 0x3A (:), given position.
- let headerName = collectASequenceOfBytes(
- (char) => char !== 0x0a && char !== 0x0d && char !== 0x3a,
- input,
- position
- )
-
- // 2.3. Remove any HTTP tab or space bytes from the start or end of header name.
- headerName = removeChars(headerName, true, true, (char) => char === 0x9 || char === 0x20)
-
- // 2.4. If header name does not match the field-name token production, return failure.
- if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) {
- throw parsingError('header name does not match the field-name token production')
- }
-
- // 2.5. If the byte at position is not 0x3A (:), return failure.
- if (input[position.position] !== 0x3a) {
- throw parsingError('expected :')
- }
-
- // 2.6. Advance position by 1.
- position.position++
-
- // 2.7. Collect a sequence of bytes that are HTTP tab or space bytes given position.
- // (Do nothing with those bytes.)
- collectASequenceOfBytes(
- (char) => char === 0x20 || char === 0x09,
- input,
- position
- )
-
- // 2.8. Byte-lowercase header name and switch on the result:
- switch (bufferToLowerCasedHeaderName(headerName)) {
- case 'content-disposition': {
- name = filename = null
-
- // Collect the disposition type (should be "form-data")
- const dispositionType = collectASequenceOfBytes(
- (char) => isToken(char),
- input,
- position
- )
-
- if (dispositionType.toString('ascii').toLowerCase() !== 'form-data') {
- throw parsingError('expected form-data for content-disposition header')
- }
-
- // Parse attributes recursively until CRLF
- while (
- position.position < input.length &&
- input[position.position] !== 0x0d &&
- input[position.position + 1] !== 0x0a
- ) {
- const attribute = parseContentDispositionAttribute(input, position)
-
- if (!attribute) {
- break
- }
-
- if (attribute.name === 'name') {
- name = attribute.value
- } else if (attribute.name === 'filename') {
- filename = attribute.value
- }
- }
-
- if (name === null) {
- throw parsingError('name attribute is required in content-disposition header')
- }
-
- break
- }
- case 'content-type': {
- // 1. Let header value be the result of collecting a sequence of bytes that are
- // not 0x0A (LF) or 0x0D (CR), given position.
- let headerValue = collectASequenceOfBytes(
- (char) => char !== 0x0a && char !== 0x0d,
- input,
- position
- )
-
- // 2. Remove any HTTP tab or space bytes from the end of header value.
- headerValue = removeChars(headerValue, false, true, (char) => char === 0x9 || char === 0x20)
-
- // 3. Set contentType to the isomorphic decoding of header value.
- contentType = isomorphicDecode(headerValue)
-
- break
- }
- case 'content-transfer-encoding': {
- let headerValue = collectASequenceOfBytes(
- (char) => char !== 0x0a && char !== 0x0d,
- input,
- position
- )
-
- headerValue = removeChars(headerValue, false, true, (char) => char === 0x9 || char === 0x20)
-
- encoding = isomorphicDecode(headerValue)
-
- break
- }
- default: {
- // Collect a sequence of bytes that are not 0x0A (LF) or 0x0D (CR), given position.
- // (Do nothing with those bytes.)
- collectASequenceOfBytes(
- (char) => char !== 0x0a && char !== 0x0d,
- input,
- position
- )
- }
- }
-
- // 2.9. If position does not point to a sequence of bytes starting with 0x0D 0x0A
- // (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
- if (input[position.position] !== 0x0d && input[position.position + 1] !== 0x0a) {
- throw parsingError('expected CRLF')
- } else {
- position.position += 2
- }
- }
-}
-
-/**
- * @param {(char: number) => boolean} condition
- * @param {Buffer} input
- * @param {{ position: number }} position
- */
-function collectASequenceOfBytes (condition, input, position) {
- let start = position.position
-
- while (start < input.length && condition(input[start])) {
- ++start
- }
-
- return input.subarray(position.position, (position.position = start))
-}
-
-/**
- * @param {Buffer} buf
- * @param {boolean} leading
- * @param {boolean} trailing
- * @param {(charCode: number) => boolean} predicate
- * @returns {Buffer}
- */
-function removeChars (buf, leading, trailing, predicate) {
- let lead = 0
- let trail = buf.length - 1
-
- if (leading) {
- while (lead < buf.length && predicate(buf[lead])) lead++
- }
-
- if (trailing) {
- while (trail > 0 && predicate(buf[trail])) trail--
- }
-
- return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1)
-}
-
-/**
- * Checks if {@param buffer} starts with {@param start}
- * @param {Buffer} buffer
- * @param {Buffer} start
- * @param {{ position: number }} position
- */
-function bufferStartsWith (buffer, start, position) {
- if (buffer.length < start.length) {
- return false
- }
-
- for (let i = 0; i < start.length; i++) {
- if (start[i] !== buffer[position.position + i]) {
- return false
- }
- }
-
- return true
-}
-
-function parsingError (cause) {
- return new TypeError('Failed to parse body as FormData.', { cause: new TypeError(cause) })
-}
-
-/**
- * CTL = <any US-ASCII control character
- * (octets 0 - 31) and DEL (127)>
- * @param {number} char
- */
-function isCTL (char) {
- return char <= 0x1f || char === 0x7f
-}
-
-/**
- * tspecials := "(" / ")" / "<" / ">" / "@" /
- * "," / ";" / ":" / "\" / <">
- * "/" / "[" / "]" / "?" / "="
- * ; Must be in quoted-string,
- * ; to use within parameter values
- * @param {number} char
- */
-function isTSpecial (char) {
- return (
- char === 0x28 || // (
- char === 0x29 || // )
- char === 0x3c || // <
- char === 0x3e || // >
- char === 0x40 || // @
- char === 0x2c || // ,
- char === 0x3b || // ;
- char === 0x3a || // :
- char === 0x5c || // \
- char === 0x22 || // "
- char === 0x2f || // /
- char === 0x5b || // [
- char === 0x5d || // ]
- char === 0x3f || // ?
- char === 0x3d // +
- )
-}
-
-/**
- * token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
- * or tspecials>
- * @param {number} char
- */
-function isToken (char) {
- return (
- char <= 0x7f && // ascii
- char !== 0x20 && // space
- char !== 0x09 &&
- !isCTL(char) &&
- !isTSpecial(char)
- )
-}
-
-module.exports = {
- multipartFormDataParser,
- validateBoundary
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/formdata.js b/vanilla/node_modules/undici/lib/web/fetch/formdata.js
deleted file mode 100644
index c21fb06..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/formdata.js
+++ /dev/null
@@ -1,259 +0,0 @@
-'use strict'
-
-const { iteratorMixin } = require('./util')
-const { kEnumerableProperty } = require('../../core/util')
-const { webidl } = require('../webidl')
-const nodeUtil = require('node:util')
-
-// https://xhr.spec.whatwg.org/#formdata
-class FormData {
- #state = []
-
- constructor (form = undefined) {
- webidl.util.markAsUncloneable(this)
-
- if (form !== undefined) {
- throw webidl.errors.conversionFailed({
- prefix: 'FormData constructor',
- argument: 'Argument 1',
- types: ['undefined']
- })
- }
- }
-
- append (name, value, filename = undefined) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.append'
- webidl.argumentLengthCheck(arguments, 2, prefix)
-
- name = webidl.converters.USVString(name)
-
- if (arguments.length === 3 || webidl.is.Blob(value)) {
- value = webidl.converters.Blob(value, prefix, 'value')
-
- if (filename !== undefined) {
- filename = webidl.converters.USVString(filename)
- }
- } else {
- value = webidl.converters.USVString(value)
- }
-
- // 1. Let value be value if given; otherwise blobValue.
-
- // 2. Let entry be the result of creating an entry with
- // name, value, and filename if given.
- const entry = makeEntry(name, value, filename)
-
- // 3. Append entry to this’s entry list.
- this.#state.push(entry)
- }
-
- delete (name) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.delete'
- webidl.argumentLengthCheck(arguments, 1, prefix)
-
- name = webidl.converters.USVString(name)
-
- // The delete(name) method steps are to remove all entries whose name
- // is name from this’s entry list.
- this.#state = this.#state.filter(entry => entry.name !== name)
- }
-
- get (name) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.get'
- webidl.argumentLengthCheck(arguments, 1, prefix)
-
- name = webidl.converters.USVString(name)
-
- // 1. If there is no entry whose name is name in this’s entry list,
- // then return null.
- const idx = this.#state.findIndex((entry) => entry.name === name)
- if (idx === -1) {
- return null
- }
-
- // 2. Return the value of the first entry whose name is name from
- // this’s entry list.
- return this.#state[idx].value
- }
-
- getAll (name) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.getAll'
- webidl.argumentLengthCheck(arguments, 1, prefix)
-
- name = webidl.converters.USVString(name)
-
- // 1. If there is no entry whose name is name in this’s entry list,
- // then return the empty list.
- // 2. Return the values of all entries whose name is name, in order,
- // from this’s entry list.
- return this.#state
- .filter((entry) => entry.name === name)
- .map((entry) => entry.value)
- }
-
- has (name) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.has'
- webidl.argumentLengthCheck(arguments, 1, prefix)
-
- name = webidl.converters.USVString(name)
-
- // The has(name) method steps are to return true if there is an entry
- // whose name is name in this’s entry list; otherwise false.
- return this.#state.findIndex((entry) => entry.name === name) !== -1
- }
-
- set (name, value, filename = undefined) {
- webidl.brandCheck(this, FormData)
-
- const prefix = 'FormData.set'
- webidl.argumentLengthCheck(arguments, 2, prefix)
-
- name = webidl.converters.USVString(name)
-
- if (arguments.length === 3 || webidl.is.Blob(value)) {
- value = webidl.converters.Blob(value, prefix, 'value')
-
- if (filename !== undefined) {
- filename = webidl.converters.USVString(filename)
- }
- } else {
- value = webidl.converters.USVString(value)
- }
-
- // The set(name, value) and set(name, blobValue, filename) method steps
- // are:
-
- // 1. Let value be value if given; otherwise blobValue.
-
- // 2. Let entry be the result of creating an entry with name, value, and
- // filename if given.
- const entry = makeEntry(name, value, filename)
-
- // 3. If there are entries in this’s entry list whose name is name, then
- // replace the first such entry with entry and remove the others.
- const idx = this.#state.findIndex((entry) => entry.name === name)
- if (idx !== -1) {
- this.#state = [
- ...this.#state.slice(0, idx),
- entry,
- ...this.#state.slice(idx + 1).filter((entry) => entry.name !== name)
- ]
- } else {
- // 4. Otherwise, append entry to this’s entry list.
- this.#state.push(entry)
- }
- }
-
- [nodeUtil.inspect.custom] (depth, options) {
- const state = this.#state.reduce((a, b) => {
- if (a[b.name]) {
- if (Array.isArray(a[b.name])) {
- a[b.name].push(b.value)
- } else {
- a[b.name] = [a[b.name], b.value]
- }
- } else {
- a[b.name] = b.value
- }
-
- return a
- }, { __proto__: null })
-
- options.depth ??= depth
- options.colors ??= true
-
- const output = nodeUtil.formatWithOptions(options, state)
-
- // remove [Object null prototype]
- return `FormData ${output.slice(output.indexOf(']') + 2)}`
- }
-
- /**
- * @param {FormData} formData
- */
- static getFormDataState (formData) {
- return formData.#state
- }
-
- /**
- * @param {FormData} formData
- * @param {any[]} newState
- */
- static setFormDataState (formData, newState) {
- formData.#state = newState
- }
-}
-
-const { getFormDataState, setFormDataState } = FormData
-Reflect.deleteProperty(FormData, 'getFormDataState')
-Reflect.deleteProperty(FormData, 'setFormDataState')
-
-iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value')
-
-Object.defineProperties(FormData.prototype, {
- append: kEnumerableProperty,
- delete: kEnumerableProperty,
- get: kEnumerableProperty,
- getAll: kEnumerableProperty,
- has: kEnumerableProperty,
- set: kEnumerableProperty,
- [Symbol.toStringTag]: {
- value: 'FormData',
- configurable: true
- }
-})
-
-/**
- * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
- * @param {string} name
- * @param {string|Blob} value
- * @param {?string} filename
- * @returns
- */
-function makeEntry (name, value, filename) {
- // 1. Set name to the result of converting name into a scalar value string.
- // Note: This operation was done by the webidl converter USVString.
-
- // 2. If value is a string, then set value to the result of converting
- // value into a scalar value string.
- if (typeof value === 'string') {
- // Note: This operation was done by the webidl converter USVString.
- } else {
- // 3. Otherwise:
-
- // 1. If value is not a File object, then set value to a new File object,
- // representing the same bytes, whose name attribute value is "blob"
- if (!webidl.is.File(value)) {
- value = new File([value], 'blob', { type: value.type })
- }
-
- // 2. If filename is given, then set value to a new File object,
- // representing the same bytes, whose name attribute is filename.
- if (filename !== undefined) {
- /** @type {FilePropertyBag} */
- const options = {
- type: value.type,
- lastModified: value.lastModified
- }
-
- value = new File([value], filename, options)
- }
- }
-
- // 4. Return an entry whose name is name and whose value is value.
- return { name, value }
-}
-
-webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
-
-module.exports = { FormData, makeEntry, setFormDataState }
diff --git a/vanilla/node_modules/undici/lib/web/fetch/global.js b/vanilla/node_modules/undici/lib/web/fetch/global.js
deleted file mode 100644
index 1df6f12..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/global.js
+++ /dev/null
@@ -1,40 +0,0 @@
-'use strict'
-
-// In case of breaking changes, increase the version
-// number to avoid conflicts.
-const globalOrigin = Symbol.for('undici.globalOrigin.1')
-
-function getGlobalOrigin () {
- return globalThis[globalOrigin]
-}
-
-function setGlobalOrigin (newOrigin) {
- if (newOrigin === undefined) {
- Object.defineProperty(globalThis, globalOrigin, {
- value: undefined,
- writable: true,
- enumerable: false,
- configurable: false
- })
-
- return
- }
-
- const parsedURL = new URL(newOrigin)
-
- if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
- throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`)
- }
-
- Object.defineProperty(globalThis, globalOrigin, {
- value: parsedURL,
- writable: true,
- enumerable: false,
- configurable: false
- })
-}
-
-module.exports = {
- getGlobalOrigin,
- setGlobalOrigin
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/headers.js b/vanilla/node_modules/undici/lib/web/fetch/headers.js
deleted file mode 100644
index 024d198..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/headers.js
+++ /dev/null
@@ -1,719 +0,0 @@
-// https://github.com/Ethan-Arrowood/undici-fetch
-
-'use strict'
-
-const { kConstruct } = require('../../core/symbols')
-const { kEnumerableProperty } = require('../../core/util')
-const {
- iteratorMixin,
- isValidHeaderName,
- isValidHeaderValue
-} = require('./util')
-const { webidl } = require('../webidl')
-const assert = require('node:assert')
-const util = require('node:util')
-
-/**
- * @param {number} code
- * @returns {code is (0x0a | 0x0d | 0x09 | 0x20)}
- */
-function isHTTPWhiteSpaceCharCode (code) {
- return code === 0x0a || code === 0x0d || code === 0x09 || code === 0x20
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
- * @param {string} potentialValue
- * @returns {string}
- */
-function headerValueNormalize (potentialValue) {
- // To normalize a byte sequence potentialValue, remove
- // any leading and trailing HTTP whitespace bytes from
- // potentialValue.
- let i = 0; let j = potentialValue.length
-
- while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j
- while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i
-
- return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j)
-}
-
-/**
- * @param {Headers} headers
- * @param {Array|Object} object
- */
-function fill (headers, object) {
- // To fill a Headers object headers with a given object object, run these steps:
-
- // 1. If object is a sequence, then for each header in object:
- // Note: webidl conversion to array has already been done.
- if (Array.isArray(object)) {
- for (let i = 0; i < object.length; ++i) {
- const header = object[i]
- // 1. If header does not contain exactly two items, then throw a TypeError.
- if (header.length !== 2) {
- throw webidl.errors.exception({
- header: 'Headers constructor',
- message: `expected name/value pair to be length 2, found ${header.length}.`
- })
- }
-
- // 2. Append (header’s first item, header’s second item) to headers.
- appendHeader(headers, header[0], header[1])
- }
- } else if (typeof object === 'object' && object !== null) {
- // Note: null should throw
-
- // 2. Otherwise, object is a record, then for each key → value in object,
- // append (key, value) to headers
- const keys = Object.keys(object)
- for (let i = 0; i < keys.length; ++i) {
- appendHeader(headers, keys[i], object[keys[i]])
- }
- } else {
- throw webidl.errors.conversionFailed({
- prefix: 'Headers constructor',
- argument: 'Argument 1',
- types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
- })
- }
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-headers-append
- * @param {Headers} headers
- * @param {string} name
- * @param {string} value
- */
-function appendHeader (headers, name, value) {
- // 1. Normalize value.
- value = headerValueNormalize(value)
-
- // 2. If name is not a header name or value is not a
- // header value, then throw a TypeError.
- if (!isValidHeaderName(name)) {
- throw webidl.errors.invalidArgument({
- prefix: 'Headers.append',
- value: name,
- type: 'header name'
- })
- } else if (!isValidHeaderValue(value)) {
- throw webidl.errors.invalidArgument({
- prefix: 'Headers.append',
- value,
- type: 'header value'
- })
- }
-
- // 3. If headers’s guard is "immutable", then throw a TypeError.
- // 4. Otherwise, if headers’s guard is "request" and name is a
- // forbidden header name, return.
- // 5. Otherwise, if headers’s guard is "request-no-cors":
- // TODO
- // Note: undici does not implement forbidden header names
- if (getHeadersGuard(headers) === 'immutable') {
- throw new TypeError('immutable')
- }
-
- // 6. Otherwise, if headers’s guard is "response" and name is a
- // forbidden response-header name, return.
-
- // 7. Append (name, value) to headers’s header list.
- return getHeadersList(headers).append(name, value, false)
-
- // 8. If headers’s guard is "request-no-cors", then remove
- // privileged no-CORS request headers from headers
-}
-
-// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
-/**
- * @param {Headers} target
- */
-function headersListSortAndCombine (target) {
- const headersList = getHeadersList(target)
-
- if (!headersList) {
- return []
- }
-
- if (headersList.sortedMap) {
- return headersList.sortedMap
- }
-
- // 1. Let headers be an empty list of headers with the key being the name
- // and value the value.
- const headers = []
-
- // 2. Let names be the result of convert header names to a sorted-lowercase
- // set with all the names of the headers in list.
- const names = headersList.toSortedArray()
-
- const cookies = headersList.cookies
-
- // fast-path
- if (cookies === null || cookies.length === 1) {
- // Note: The non-null assertion of value has already been done by `HeadersList#toSortedArray`
- return (headersList.sortedMap = names)
- }
-
- // 3. For each name of names:
- for (let i = 0; i < names.length; ++i) {
- const { 0: name, 1: value } = names[i]
- // 1. If name is `set-cookie`, then:
- if (name === 'set-cookie') {
- // 1. Let values be a list of all values of headers in list whose name
- // is a byte-case-insensitive match for name, in order.
-
- // 2. For each value of values:
- // 1. Append (name, value) to headers.
- for (let j = 0; j < cookies.length; ++j) {
- headers.push([name, cookies[j]])
- }
- } else {
- // 2. Otherwise:
-
- // 1. Let value be the result of getting name from list.
-
- // 2. Assert: value is non-null.
- // Note: This operation was done by `HeadersList#toSortedArray`.
-
- // 3. Append (name, value) to headers.
- headers.push([name, value])
- }
- }
-
- // 4. Return headers.
- return (headersList.sortedMap = headers)
-}
-
-function compareHeaderName (a, b) {
- return a[0] < b[0] ? -1 : 1
-}
-
-class HeadersList {
- /** @type {[string, string][]|null} */
- cookies = null
-
- sortedMap
- headersMap
-
- constructor (init) {
- if (init instanceof HeadersList) {
- this.headersMap = new Map(init.headersMap)
- this.sortedMap = init.sortedMap
- this.cookies = init.cookies === null ? null : [...init.cookies]
- } else {
- this.headersMap = new Map(init)
- this.sortedMap = null
- }
- }
-
- /**
- * @see https://fetch.spec.whatwg.org/#header-list-contains
- * @param {string} name
- * @param {boolean} isLowerCase
- */
- contains (name, isLowerCase) {
- // A header list list contains a header name name if list
- // contains a header whose name is a byte-case-insensitive
- // match for name.
-
- return this.headersMap.has(isLowerCase ? name : name.toLowerCase())
- }
-
- clear () {
- this.headersMap.clear()
- this.sortedMap = null
- this.cookies = null
- }
-
- /**
- * @see https://fetch.spec.whatwg.org/#concept-header-list-append
- * @param {string} name
- * @param {string} value
- * @param {boolean} isLowerCase
- */
- append (name, value, isLowerCase) {
- this.sortedMap = null
-
- // 1. If list contains name, then set name to the first such
- // header’s name.
- const lowercaseName = isLowerCase ? name : name.toLowerCase()
- const exists = this.headersMap.get(lowercaseName)
-
- // 2. Append (name, value) to list.
- if (exists) {
- const delimiter = lowercaseName === 'cookie' ? '; ' : ', '
- this.headersMap.set(lowercaseName, {
- name: exists.name,
- value: `${exists.value}${delimiter}${value}`
- })
- } else {
- this.headersMap.set(lowercaseName, { name, value })
- }
-
- if (lowercaseName === 'set-cookie') {
- (this.cookies ??= []).push(value)
- }
- }
-
- /**
- * @see https://fetch.spec.whatwg.org/#concept-header-list-set
- * @param {string} name
- * @param {string} value
- * @param {boolean} isLowerCase
- */
- set (name, value, isLowerCase) {
- this.sortedMap = null
- const lowercaseName = isLowerCase ? name : name.toLowerCase()
-
- if (lowercaseName === 'set-cookie') {
- this.cookies = [value]
- }
-
- // 1. If list contains name, then set the value of
- // the first such header to value and remove the
- // others.
- // 2. Otherwise, append header (name, value) to list.
- this.headersMap.set(lowercaseName, { name, value })
- }
-
- /**
- * @see https://fetch.spec.whatwg.org/#concept-header-list-delete
- * @param {string} name
- * @param {boolean} isLowerCase
- */
- delete (name, isLowerCase) {
- this.sortedMap = null
- if (!isLowerCase) name = name.toLowerCase()
-
- if (name === 'set-cookie') {
- this.cookies = null
- }
-
- this.headersMap.delete(name)
- }
-
- /**
- * @see https://fetch.spec.whatwg.org/#concept-header-list-get
- * @param {string} name
- * @param {boolean} isLowerCase
- * @returns {string | null}
- */
- get (name, isLowerCase) {
- // 1. If list does not contain name, then return null.
- // 2. Return the values of all headers in list whose name
- // is a byte-case-insensitive match for name,
- // separated from each other by 0x2C 0x20, in order.
- return this.headersMap.get(isLowerCase ? name : name.toLowerCase())?.value ?? null
- }
-
- * [Symbol.iterator] () {
- // use the lowercased name
- for (const { 0: name, 1: { value } } of this.headersMap) {
- yield [name, value]
- }
- }
-
- get entries () {
- const headers = {}
-
- if (this.headersMap.size !== 0) {
- for (const { name, value } of this.headersMap.values()) {
- headers[name] = value
- }
- }
-
- return headers
- }
-
- rawValues () {
- return this.headersMap.values()
- }
-
- get entriesList () {
- const headers = []
-
- if (this.headersMap.size !== 0) {
- for (const { 0: lowerName, 1: { name, value } } of this.headersMap) {
- if (lowerName === 'set-cookie') {
- for (const cookie of this.cookies) {
- headers.push([name, cookie])
- }
- } else {
- headers.push([name, value])
- }
- }
- }
-
- return headers
- }
-
- // https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
- toSortedArray () {
- const size = this.headersMap.size
- const array = new Array(size)
- // In most cases, you will use the fast-path.
- // fast-path: Use binary insertion sort for small arrays.
- if (size <= 32) {
- if (size === 0) {
- // If empty, it is an empty array. To avoid the first index assignment.
- return array
- }
- // Improve performance by unrolling loop and avoiding double-loop.
- // Double-loop-less version of the binary insertion sort.
- const iterator = this.headersMap[Symbol.iterator]()
- const firstValue = iterator.next().value
- // set [name, value] to first index.
- array[0] = [firstValue[0], firstValue[1].value]
- // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
- // 3.2.2. Assert: value is non-null.
- assert(firstValue[1].value !== null)
- for (
- let i = 1, j = 0, right = 0, left = 0, pivot = 0, x, value;
- i < size;
- ++i
- ) {
- // get next value
- value = iterator.next().value
- // set [name, value] to current index.
- x = array[i] = [value[0], value[1].value]
- // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
- // 3.2.2. Assert: value is non-null.
- assert(x[1] !== null)
- left = 0
- right = i
- // binary search
- while (left < right) {
- // middle index
- pivot = left + ((right - left) >> 1)
- // compare header name
- if (array[pivot][0] <= x[0]) {
- left = pivot + 1
- } else {
- right = pivot
- }
- }
- if (i !== pivot) {
- j = i
- while (j > left) {
- array[j] = array[--j]
- }
- array[left] = x
- }
- }
- /* c8 ignore next 4 */
- if (!iterator.next().done) {
- // This is for debugging and will never be called.
- throw new TypeError('Unreachable')
- }
- return array
- } else {
- // This case would be a rare occurrence.
- // slow-path: fallback
- let i = 0
- for (const { 0: name, 1: { value } } of this.headersMap) {
- array[i++] = [name, value]
- // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
- // 3.2.2. Assert: value is non-null.
- assert(value !== null)
- }
- return array.sort(compareHeaderName)
- }
- }
-}
-
-// https://fetch.spec.whatwg.org/#headers-class
-class Headers {
- #guard
- /**
- * @type {HeadersList}
- */
- #headersList
-
- /**
- * @param {HeadersInit|Symbol} [init]
- * @returns
- */
- constructor (init = undefined) {
- webidl.util.markAsUncloneable(this)
-
- if (init === kConstruct) {
- return
- }
-
- this.#headersList = new HeadersList()
-
- // The new Headers(init) constructor steps are:
-
- // 1. Set this’s guard to "none".
- this.#guard = 'none'
-
- // 2. If init is given, then fill this with init.
- if (init !== undefined) {
- init = webidl.converters.HeadersInit(init, 'Headers constructor', 'init')
- fill(this, init)
- }
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-append
- append (name, value) {
- webidl.brandCheck(this, Headers)
-
- webidl.argumentLengthCheck(arguments, 2, 'Headers.append')
-
- const prefix = 'Headers.append'
- name = webidl.converters.ByteString(name, prefix, 'name')
- value = webidl.converters.ByteString(value, prefix, 'value')
-
- return appendHeader(this, name, value)
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-delete
- delete (name) {
- webidl.brandCheck(this, Headers)
-
- webidl.argumentLengthCheck(arguments, 1, 'Headers.delete')
-
- const prefix = 'Headers.delete'
- name = webidl.converters.ByteString(name, prefix, 'name')
-
- // 1. If name is not a header name, then throw a TypeError.
- if (!isValidHeaderName(name)) {
- throw webidl.errors.invalidArgument({
- prefix: 'Headers.delete',
- value: name,
- type: 'header name'
- })
- }
-
- // 2. If this’s guard is "immutable", then throw a TypeError.
- // 3. Otherwise, if this’s guard is "request" and name is a
- // forbidden header name, return.
- // 4. Otherwise, if this’s guard is "request-no-cors", name
- // is not a no-CORS-safelisted request-header name, and
- // name is not a privileged no-CORS request-header name,
- // return.
- // 5. Otherwise, if this’s guard is "response" and name is
- // a forbidden response-header name, return.
- // Note: undici does not implement forbidden header names
- if (this.#guard === 'immutable') {
- throw new TypeError('immutable')
- }
-
- // 6. If this’s header list does not contain name, then
- // return.
- if (!this.#headersList.contains(name, false)) {
- return
- }
-
- // 7. Delete name from this’s header list.
- // 8. If this’s guard is "request-no-cors", then remove
- // privileged no-CORS request headers from this.
- this.#headersList.delete(name, false)
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-get
- get (name) {
- webidl.brandCheck(this, Headers)
-
- webidl.argumentLengthCheck(arguments, 1, 'Headers.get')
-
- const prefix = 'Headers.get'
- name = webidl.converters.ByteString(name, prefix, 'name')
-
- // 1. If name is not a header name, then throw a TypeError.
- if (!isValidHeaderName(name)) {
- throw webidl.errors.invalidArgument({
- prefix,
- value: name,
- type: 'header name'
- })
- }
-
- // 2. Return the result of getting name from this’s header
- // list.
- return this.#headersList.get(name, false)
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-has
- has (name) {
- webidl.brandCheck(this, Headers)
-
- webidl.argumentLengthCheck(arguments, 1, 'Headers.has')
-
- const prefix = 'Headers.has'
- name = webidl.converters.ByteString(name, prefix, 'name')
-
- // 1. If name is not a header name, then throw a TypeError.
- if (!isValidHeaderName(name)) {
- throw webidl.errors.invalidArgument({
- prefix,
- value: name,
- type: 'header name'
- })
- }
-
- // 2. Return true if this’s header list contains name;
- // otherwise false.
- return this.#headersList.contains(name, false)
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-set
- set (name, value) {
- webidl.brandCheck(this, Headers)
-
- webidl.argumentLengthCheck(arguments, 2, 'Headers.set')
-
- const prefix = 'Headers.set'
- name = webidl.converters.ByteString(name, prefix, 'name')
- value = webidl.converters.ByteString(value, prefix, 'value')
-
- // 1. Normalize value.
- value = headerValueNormalize(value)
-
- // 2. If name is not a header name or value is not a
- // header value, then throw a TypeError.
- if (!isValidHeaderName(name)) {
- throw webidl.errors.invalidArgument({
- prefix,
- value: name,
- type: 'header name'
- })
- } else if (!isValidHeaderValue(value)) {
- throw webidl.errors.invalidArgument({
- prefix,
- value,
- type: 'header value'
- })
- }
-
- // 3. If this’s guard is "immutable", then throw a TypeError.
- // 4. Otherwise, if this’s guard is "request" and name is a
- // forbidden header name, return.
- // 5. Otherwise, if this’s guard is "request-no-cors" and
- // name/value is not a no-CORS-safelisted request-header,
- // return.
- // 6. Otherwise, if this’s guard is "response" and name is a
- // forbidden response-header name, return.
- // Note: undici does not implement forbidden header names
- if (this.#guard === 'immutable') {
- throw new TypeError('immutable')
- }
-
- // 7. Set (name, value) in this’s header list.
- // 8. If this’s guard is "request-no-cors", then remove
- // privileged no-CORS request headers from this
- this.#headersList.set(name, value, false)
- }
-
- // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
- getSetCookie () {
- webidl.brandCheck(this, Headers)
-
- // 1. If this’s header list does not contain `Set-Cookie`, then return « ».
- // 2. Return the values of all headers in this’s header list whose name is
- // a byte-case-insensitive match for `Set-Cookie`, in order.
-
- const list = this.#headersList.cookies
-
- if (list) {
- return [...list]
- }
-
- return []
- }
-
- [util.inspect.custom] (depth, options) {
- options.depth ??= depth
-
- return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`
- }
-
- static getHeadersGuard (o) {
- return o.#guard
- }
-
- static setHeadersGuard (o, guard) {
- o.#guard = guard
- }
-
- /**
- * @param {Headers} o
- */
- static getHeadersList (o) {
- return o.#headersList
- }
-
- /**
- * @param {Headers} target
- * @param {HeadersList} list
- */
- static setHeadersList (target, list) {
- target.#headersList = list
- }
-}
-
-const { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } = Headers
-Reflect.deleteProperty(Headers, 'getHeadersGuard')
-Reflect.deleteProperty(Headers, 'setHeadersGuard')
-Reflect.deleteProperty(Headers, 'getHeadersList')
-Reflect.deleteProperty(Headers, 'setHeadersList')
-
-iteratorMixin('Headers', Headers, headersListSortAndCombine, 0, 1)
-
-Object.defineProperties(Headers.prototype, {
- append: kEnumerableProperty,
- delete: kEnumerableProperty,
- get: kEnumerableProperty,
- has: kEnumerableProperty,
- set: kEnumerableProperty,
- getSetCookie: kEnumerableProperty,
- [Symbol.toStringTag]: {
- value: 'Headers',
- configurable: true
- },
- [util.inspect.custom]: {
- enumerable: false
- }
-})
-
-webidl.converters.HeadersInit = function (V, prefix, argument) {
- if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
- const iterator = Reflect.get(V, Symbol.iterator)
-
- // A work-around to ensure we send the properly-cased Headers when V is a Headers object.
- // Read https://github.com/nodejs/undici/pull/3159#issuecomment-2075537226 before touching, please.
- if (!util.types.isProxy(V) && iterator === Headers.prototype.entries) { // Headers object
- try {
- return getHeadersList(V).entriesList
- } catch {
- // fall-through
- }
- }
-
- if (typeof iterator === 'function') {
- return webidl.converters['sequence<sequence<ByteString>>'](V, prefix, argument, iterator.bind(V))
- }
-
- return webidl.converters['record<ByteString, ByteString>'](V, prefix, argument)
- }
-
- throw webidl.errors.conversionFailed({
- prefix: 'Headers constructor',
- argument: 'Argument 1',
- types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
- })
-}
-
-module.exports = {
- fill,
- // for test.
- compareHeaderName,
- Headers,
- HeadersList,
- getHeadersGuard,
- setHeadersGuard,
- setHeadersList,
- getHeadersList
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/index.js b/vanilla/node_modules/undici/lib/web/fetch/index.js
deleted file mode 100644
index f350035..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/index.js
+++ /dev/null
@@ -1,2372 +0,0 @@
-// https://github.com/Ethan-Arrowood/undici-fetch
-
-'use strict'
-
-const {
- makeNetworkError,
- makeAppropriateNetworkError,
- filterResponse,
- makeResponse,
- fromInnerResponse,
- getResponseState
-} = require('./response')
-const { HeadersList } = require('./headers')
-const { Request, cloneRequest, getRequestDispatcher, getRequestState } = require('./request')
-const zlib = require('node:zlib')
-const {
- makePolicyContainer,
- clonePolicyContainer,
- requestBadPort,
- TAOCheck,
- appendRequestOriginHeader,
- responseLocationURL,
- requestCurrentURL,
- setRequestReferrerPolicyOnRedirect,
- tryUpgradeRequestToAPotentiallyTrustworthyURL,
- createOpaqueTimingInfo,
- appendFetchMetadata,
- corsCheck,
- crossOriginResourcePolicyCheck,
- determineRequestsReferrer,
- coarsenedSharedCurrentTime,
- sameOrigin,
- isCancelled,
- isAborted,
- isErrorLike,
- fullyReadBody,
- readableStreamClose,
- urlIsLocal,
- urlIsHttpHttpsScheme,
- urlHasHttpsScheme,
- clampAndCoarsenConnectionTimingInfo,
- simpleRangeHeaderValue,
- buildContentRange,
- createInflate,
- extractMimeType,
- hasAuthenticationEntry,
- includesCredentials,
- isTraversableNavigable
-} = require('./util')
-const assert = require('node:assert')
-const { safelyExtractBody, extractBody } = require('./body')
-const {
- redirectStatusSet,
- nullBodyStatus,
- safeMethodsSet,
- requestBodyHeader,
- subresourceSet
-} = require('./constants')
-const EE = require('node:events')
-const { Readable, pipeline, finished, isErrored, isReadable } = require('node:stream')
-const { addAbortListener, bufferToLowerCasedHeaderName } = require('../../core/util')
-const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url')
-const { getGlobalDispatcher } = require('../../global')
-const { webidl } = require('../webidl')
-const { STATUS_CODES } = require('node:http')
-const { bytesMatch } = require('../subresource-integrity/subresource-integrity')
-const { createDeferredPromise } = require('../../util/promise')
-const { isomorphicEncode } = require('../infra')
-const { runtimeFeatures } = require('../../util/runtime-features')
-
-// Node.js v23.8.0+ and v22.15.0+ supports Zstandard
-const hasZstd = runtimeFeatures.has('zstd')
-
-const GET_OR_HEAD = ['GET', 'HEAD']
-
-const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
- ? 'node'
- : 'undici'
-
-/** @type {import('buffer').resolveObjectURL} */
-let resolveObjectURL
-
-class Fetch extends EE {
- constructor (dispatcher) {
- super()
-
- this.dispatcher = dispatcher
- this.connection = null
- this.dump = false
- this.state = 'ongoing'
- }
-
- terminate (reason) {
- if (this.state !== 'ongoing') {
- return
- }
-
- this.state = 'terminated'
- this.connection?.destroy(reason)
- this.emit('terminated', reason)
- }
-
- // https://fetch.spec.whatwg.org/#fetch-controller-abort
- abort (error) {
- if (this.state !== 'ongoing') {
- return
- }
-
- // 1. Set controller’s state to "aborted".
- this.state = 'aborted'
-
- // 2. Let fallbackError be an "AbortError" DOMException.
- // 3. Set error to fallbackError if it is not given.
- if (!error) {
- error = new DOMException('The operation was aborted.', 'AbortError')
- }
-
- // 4. Let serializedError be StructuredSerialize(error).
- // If that threw an exception, catch it, and let
- // serializedError be StructuredSerialize(fallbackError).
-
- // 5. Set controller’s serialized abort reason to serializedError.
- this.serializedAbortReason = error
-
- this.connection?.destroy(error)
- this.emit('terminated', error)
- }
-}
-
-function handleFetchDone (response) {
- finalizeAndReportTiming(response, 'fetch')
-}
-
-// https://fetch.spec.whatwg.org/#fetch-method
-function fetch (input, init = undefined) {
- webidl.argumentLengthCheck(arguments, 1, 'globalThis.fetch')
-
- // 1. Let p be a new promise.
- let p = createDeferredPromise()
-
- // 2. Let requestObject be the result of invoking the initial value of
- // Request as constructor with input and init as arguments. If this throws
- // an exception, reject p with it and return p.
- let requestObject
-
- try {
- requestObject = new Request(input, init)
- } catch (e) {
- p.reject(e)
- return p.promise
- }
-
- // 3. Let request be requestObject’s request.
- const request = getRequestState(requestObject)
-
- // 4. If requestObject’s signal’s aborted flag is set, then:
- if (requestObject.signal.aborted) {
- // 1. Abort the fetch() call with p, request, null, and
- // requestObject’s signal’s abort reason.
- abortFetch(p, request, null, requestObject.signal.reason, null)
-
- // 2. Return p.
- return p.promise
- }
-
- // 5. Let globalObject be request’s client’s global object.
- const globalObject = request.client.globalObject
-
- // 6. If globalObject is a ServiceWorkerGlobalScope object, then set
- // request’s service-workers mode to "none".
- if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') {
- request.serviceWorkers = 'none'
- }
-
- // 7. Let responseObject be null.
- let responseObject = null
-
- // 8. Let relevantRealm be this’s relevant Realm.
-
- // 9. Let locallyAborted be false.
- let locallyAborted = false
-
- // 10. Let controller be null.
- let controller = null
-
- // 11. Add the following abort steps to requestObject’s signal:
- addAbortListener(
- requestObject.signal,
- () => {
- // 1. Set locallyAborted to true.
- locallyAborted = true
-
- // 2. Assert: controller is non-null.
- assert(controller != null)
-
- // 3. Abort controller with requestObject’s signal’s abort reason.
- controller.abort(requestObject.signal.reason)
-
- const realResponse = responseObject?.deref()
-
- // 4. Abort the fetch() call with p, request, responseObject,
- // and requestObject’s signal’s abort reason.
- abortFetch(p, request, realResponse, requestObject.signal.reason, controller.controller)
- }
- )
-
- // 12. Let handleFetchDone given response response be to finalize and
- // report timing with response, globalObject, and "fetch".
- // see function handleFetchDone
-
- // 13. Set controller to the result of calling fetch given request,
- // with processResponseEndOfBody set to handleFetchDone, and processResponse
- // given response being these substeps:
-
- const processResponse = (response) => {
- // 1. If locallyAborted is true, terminate these substeps.
- if (locallyAborted) {
- return
- }
-
- // 2. If response’s aborted flag is set, then:
- if (response.aborted) {
- // 1. Let deserializedError be the result of deserialize a serialized
- // abort reason given controller’s serialized abort reason and
- // relevantRealm.
-
- // 2. Abort the fetch() call with p, request, responseObject, and
- // deserializedError.
-
- abortFetch(p, request, responseObject, controller.serializedAbortReason, controller.controller)
- return
- }
-
- // 3. If response is a network error, then reject p with a TypeError
- // and terminate these substeps.
- if (response.type === 'error') {
- p.reject(new TypeError('fetch failed', { cause: response.error }))
- return
- }
-
- // 4. Set responseObject to the result of creating a Response object,
- // given response, "immutable", and relevantRealm.
- responseObject = new WeakRef(fromInnerResponse(response, 'immutable'))
-
- // 5. Resolve p with responseObject.
- p.resolve(responseObject.deref())
- p = null
- }
-
- controller = fetching({
- request,
- processResponseEndOfBody: handleFetchDone,
- processResponse,
- dispatcher: getRequestDispatcher(requestObject) // undici
- })
-
- // 14. Return p.
- return p.promise
-}
-
-// https://fetch.spec.whatwg.org/#finalize-and-report-timing
-function finalizeAndReportTiming (response, initiatorType = 'other') {
- // 1. If response is an aborted network error, then return.
- if (response.type === 'error' && response.aborted) {
- return
- }
-
- // 2. If response’s URL list is null or empty, then return.
- if (!response.urlList?.length) {
- return
- }
-
- // 3. Let originalURL be response’s URL list[0].
- const originalURL = response.urlList[0]
-
- // 4. Let timingInfo be response’s timing info.
- let timingInfo = response.timingInfo
-
- // 5. Let cacheState be response’s cache state.
- let cacheState = response.cacheState
-
- // 6. If originalURL’s scheme is not an HTTP(S) scheme, then return.
- if (!urlIsHttpHttpsScheme(originalURL)) {
- return
- }
-
- // 7. If timingInfo is null, then return.
- if (timingInfo === null) {
- return
- }
-
- // 8. If response’s timing allow passed flag is not set, then:
- if (!response.timingAllowPassed) {
- // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo.
- timingInfo = createOpaqueTimingInfo({
- startTime: timingInfo.startTime
- })
-
- // 2. Set cacheState to the empty string.
- cacheState = ''
- }
-
- // 9. Set timingInfo’s end time to the coarsened shared current time
- // given global’s relevant settings object’s cross-origin isolated
- // capability.
- // TODO: given global’s relevant settings object’s cross-origin isolated
- // capability?
- timingInfo.endTime = coarsenedSharedCurrentTime()
-
- // 10. Set response’s timing info to timingInfo.
- response.timingInfo = timingInfo
-
- // 11. Mark resource timing for timingInfo, originalURL, initiatorType,
- // global, and cacheState.
- markResourceTiming(
- timingInfo,
- originalURL.href,
- initiatorType,
- globalThis,
- cacheState,
- '', // bodyType
- response.status
- )
-}
-
-// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
-const markResourceTiming = performance.markResourceTiming
-
-// https://fetch.spec.whatwg.org/#abort-fetch
-function abortFetch (p, request, responseObject, error, controller /* undici-specific */) {
- // 1. Reject promise with error.
- if (p) {
- // We might have already resolved the promise at this stage
- p.reject(error)
- }
-
- // 2. If request’s body is not null and is readable, then cancel request’s
- // body with error.
- if (request.body?.stream != null && isReadable(request.body.stream)) {
- request.body.stream.cancel(error).catch((err) => {
- if (err.code === 'ERR_INVALID_STATE') {
- // Node bug?
- return
- }
- throw err
- })
- }
-
- // 3. If responseObject is null, then return.
- if (responseObject == null) {
- return
- }
-
- // 4. Let response be responseObject’s response.
- const response = getResponseState(responseObject)
-
- // 5. If response’s body is not null and is readable, then error response’s
- // body with error.
- if (response.body?.stream != null && isReadable(response.body.stream)) {
- controller.error(error)
- }
-}
-
-// https://fetch.spec.whatwg.org/#fetching
-function fetching ({
- request,
- processRequestBodyChunkLength,
- processRequestEndOfBody,
- processResponse,
- processResponseEndOfBody,
- processResponseConsumeBody,
- useParallelQueue = false,
- dispatcher = getGlobalDispatcher() // undici
-}) {
- // Ensure that the dispatcher is set accordingly
- assert(dispatcher)
-
- // 1. Let taskDestination be null.
- let taskDestination = null
-
- // 2. Let crossOriginIsolatedCapability be false.
- let crossOriginIsolatedCapability = false
-
- // 3. If request’s client is non-null, then:
- if (request.client != null) {
- // 1. Set taskDestination to request’s client’s global object.
- taskDestination = request.client.globalObject
-
- // 2. Set crossOriginIsolatedCapability to request’s client’s cross-origin
- // isolated capability.
- crossOriginIsolatedCapability =
- request.client.crossOriginIsolatedCapability
- }
-
- // 4. If useParallelQueue is true, then set taskDestination to the result of
- // starting a new parallel queue.
- // TODO
-
- // 5. Let timingInfo be a new fetch timing info whose start time and
- // post-redirect start time are the coarsened shared current time given
- // crossOriginIsolatedCapability.
- const currentTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability)
- const timingInfo = createOpaqueTimingInfo({
- startTime: currentTime
- })
-
- // 6. Let fetchParams be a new fetch params whose
- // request is request,
- // timing info is timingInfo,
- // process request body chunk length is processRequestBodyChunkLength,
- // process request end-of-body is processRequestEndOfBody,
- // process response is processResponse,
- // process response consume body is processResponseConsumeBody,
- // process response end-of-body is processResponseEndOfBody,
- // task destination is taskDestination,
- // and cross-origin isolated capability is crossOriginIsolatedCapability.
- const fetchParams = {
- controller: new Fetch(dispatcher),
- request,
- timingInfo,
- processRequestBodyChunkLength,
- processRequestEndOfBody,
- processResponse,
- processResponseConsumeBody,
- processResponseEndOfBody,
- taskDestination,
- crossOriginIsolatedCapability
- }
-
- // 7. If request’s body is a byte sequence, then set request’s body to
- // request’s body as a body.
- // NOTE: Since fetching is only called from fetch, body should already be
- // extracted.
- assert(!request.body || request.body.stream)
-
- // 8. If request’s window is "client", then set request’s window to request’s
- // client, if request’s client’s global object is a Window object; otherwise
- // "no-window".
- if (request.window === 'client') {
- // TODO: What if request.client is null?
- request.window =
- request.client?.globalObject?.constructor?.name === 'Window'
- ? request.client
- : 'no-window'
- }
-
- // 9. If request’s origin is "client", then set request’s origin to request’s
- // client’s origin.
- if (request.origin === 'client') {
- request.origin = request.client.origin
- }
-
- // 10. If all of the following conditions are true:
- // TODO
-
- // 11. If request’s policy container is "client", then:
- if (request.policyContainer === 'client') {
- // 1. If request’s client is non-null, then set request’s policy
- // container to a clone of request’s client’s policy container. [HTML]
- if (request.client != null) {
- request.policyContainer = clonePolicyContainer(
- request.client.policyContainer
- )
- } else {
- // 2. Otherwise, set request’s policy container to a new policy
- // container.
- request.policyContainer = makePolicyContainer()
- }
- }
-
- // 12. If request’s header list does not contain `Accept`, then:
- if (!request.headersList.contains('accept', true)) {
- // 1. Let value be `*/*`.
- const value = '*/*'
-
- // 2. A user agent should set value to the first matching statement, if
- // any, switching on request’s destination:
- // "document"
- // "frame"
- // "iframe"
- // `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`
- // "image"
- // `image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5`
- // "style"
- // `text/css,*/*;q=0.1`
- // TODO
-
- // 3. Append `Accept`/value to request’s header list.
- request.headersList.append('accept', value, true)
- }
-
- // 13. If request’s header list does not contain `Accept-Language`, then
- // user agents should append `Accept-Language`/an appropriate value to
- // request’s header list.
- if (!request.headersList.contains('accept-language', true)) {
- request.headersList.append('accept-language', '*', true)
- }
-
- // 14. If request’s priority is null, then use request’s initiator and
- // destination appropriately in setting request’s priority to a
- // user-agent-defined object.
- if (request.priority === null) {
- // TODO
- }
-
- // 15. If request is a subresource request, then:
- if (subresourceSet.has(request.destination)) {
- // TODO
- }
-
- // 16. Run main fetch given fetchParams.
- mainFetch(fetchParams, false)
-
- // 17. Return fetchParam's controller
- return fetchParams.controller
-}
-
-// https://fetch.spec.whatwg.org/#concept-main-fetch
-async function mainFetch (fetchParams, recursive) {
- try {
- // 1. Let request be fetchParams’s request.
- const request = fetchParams.request
-
- // 2. Let response be null.
- let response = null
-
- // 3. If request’s local-URLs-only flag is set and request’s current URL is
- // not local, then set response to a network error.
- if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
- response = makeNetworkError('local URLs only')
- }
-
- // 4. Run report Content Security Policy violations for request.
- // TODO
-
- // 5. Upgrade request to a potentially trustworthy URL, if appropriate.
- tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
-
- // 6. If should request be blocked due to a bad port, should fetching request
- // be blocked as mixed content, or should request be blocked by Content
- // Security Policy returns blocked, then set response to a network error.
- if (requestBadPort(request) === 'blocked') {
- response = makeNetworkError('bad port')
- }
- // TODO: should fetching request be blocked as mixed content?
- // TODO: should request be blocked by Content Security Policy?
-
- // 7. If request’s referrer policy is the empty string, then set request’s
- // referrer policy to request’s policy container’s referrer policy.
- if (request.referrerPolicy === '') {
- request.referrerPolicy = request.policyContainer.referrerPolicy
- }
-
- // 8. If request’s referrer is not "no-referrer", then set request’s
- // referrer to the result of invoking determine request’s referrer.
- if (request.referrer !== 'no-referrer') {
- request.referrer = determineRequestsReferrer(request)
- }
-
- // 9. Set request’s current URL’s scheme to "https" if all of the following
- // conditions are true:
- // - request’s current URL’s scheme is "http"
- // - request’s current URL’s host is a domain
- // - Matching request’s current URL’s host per Known HSTS Host Domain Name
- // Matching results in either a superdomain match with an asserted
- // includeSubDomains directive or a congruent match (with or without an
- // asserted includeSubDomains directive). [HSTS]
- // TODO
-
- // 10. If recursive is false, then run the remaining steps in parallel.
- // TODO
-
- // 11. If response is null, then set response to the result of running
- // the steps corresponding to the first matching statement:
- if (response === null) {
- const currentURL = requestCurrentURL(request)
- if (
- // - request’s current URL’s origin is same origin with request’s origin,
- // and request’s response tainting is "basic"
- (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
- // request’s current URL’s scheme is "data"
- (currentURL.protocol === 'data:') ||
- // - request’s mode is "navigate" or "websocket"
- (request.mode === 'navigate' || request.mode === 'websocket')
- ) {
- // 1. Set request’s response tainting to "basic".
- request.responseTainting = 'basic'
-
- // 2. Return the result of running scheme fetch given fetchParams.
- response = await schemeFetch(fetchParams)
-
- // request’s mode is "same-origin"
- } else if (request.mode === 'same-origin') {
- // 1. Return a network error.
- response = makeNetworkError('request mode cannot be "same-origin"')
-
- // request’s mode is "no-cors"
- } else if (request.mode === 'no-cors') {
- // 1. If request’s redirect mode is not "follow", then return a network
- // error.
- if (request.redirect !== 'follow') {
- response = makeNetworkError(
- 'redirect mode cannot be "follow" for "no-cors" request'
- )
- } else {
- // 2. Set request’s response tainting to "opaque".
- request.responseTainting = 'opaque'
-
- // 3. Return the result of running scheme fetch given fetchParams.
- response = await schemeFetch(fetchParams)
- }
- // request’s current URL’s scheme is not an HTTP(S) scheme
- } else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
- // Return a network error.
- response = makeNetworkError('URL scheme must be a HTTP(S) scheme')
-
- // - request’s use-CORS-preflight flag is set
- // - request’s unsafe-request flag is set and either request’s method is
- // not a CORS-safelisted method or CORS-unsafe request-header names with
- // request’s header list is not empty
- // 1. Set request’s response tainting to "cors".
- // 2. Let corsWithPreflightResponse be the result of running HTTP fetch
- // given fetchParams and true.
- // 3. If corsWithPreflightResponse is a network error, then clear cache
- // entries using request.
- // 4. Return corsWithPreflightResponse.
- // TODO
-
- // Otherwise
- } else {
- // 1. Set request’s response tainting to "cors".
- request.responseTainting = 'cors'
-
- // 2. Return the result of running HTTP fetch given fetchParams.
- response = await httpFetch(fetchParams)
- }
- }
-
- // 12. If recursive is true, then return response.
- if (recursive) {
- return response
- }
-
- // 13. If response is not a network error and response is not a filtered
- // response, then:
- if (response.status !== 0 && !response.internalResponse) {
- // If request’s response tainting is "cors", then:
- if (request.responseTainting === 'cors') {
- // 1. Let headerNames be the result of extracting header list values
- // given `Access-Control-Expose-Headers` and response’s header list.
- // TODO
- // 2. If request’s credentials mode is not "include" and headerNames
- // contains `*`, then set response’s CORS-exposed header-name list to
- // all unique header names in response’s header list.
- // TODO
- // 3. Otherwise, if headerNames is not null or failure, then set
- // response’s CORS-exposed header-name list to headerNames.
- // TODO
- }
-
- // Set response to the following filtered response with response as its
- // internal response, depending on request’s response tainting:
- if (request.responseTainting === 'basic') {
- response = filterResponse(response, 'basic')
- } else if (request.responseTainting === 'cors') {
- response = filterResponse(response, 'cors')
- } else if (request.responseTainting === 'opaque') {
- response = filterResponse(response, 'opaque')
- } else {
- assert(false)
- }
- }
-
- // 14. Let internalResponse be response, if response is a network error,
- // and response’s internal response otherwise.
- let internalResponse =
- response.status === 0 ? response : response.internalResponse
-
- // 15. If internalResponse’s URL list is empty, then set it to a clone of
- // request’s URL list.
- if (internalResponse.urlList.length === 0) {
- internalResponse.urlList.push(...request.urlList)
- }
-
- // 16. If request’s timing allow failed flag is unset, then set
- // internalResponse’s timing allow passed flag.
- if (!request.timingAllowFailed) {
- response.timingAllowPassed = true
- }
-
- // 17. If response is not a network error and any of the following returns
- // blocked
- // - should internalResponse to request be blocked as mixed content
- // - should internalResponse to request be blocked by Content Security Policy
- // - should internalResponse to request be blocked due to its MIME type
- // - should internalResponse to request be blocked due to nosniff
- // TODO
-
- // 18. If response’s type is "opaque", internalResponse’s status is 206,
- // internalResponse’s range-requested flag is set, and request’s header
- // list does not contain `Range`, then set response and internalResponse
- // to a network error.
- if (
- response.type === 'opaque' &&
- internalResponse.status === 206 &&
- internalResponse.rangeRequested &&
- !request.headers.contains('range', true)
- ) {
- response = internalResponse = makeNetworkError()
- }
-
- // 19. If response is not a network error and either request’s method is
- // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status,
- // set internalResponse’s body to null and disregard any enqueuing toward
- // it (if any).
- if (
- response.status !== 0 &&
- (request.method === 'HEAD' ||
- request.method === 'CONNECT' ||
- nullBodyStatus.includes(internalResponse.status))
- ) {
- internalResponse.body = null
- fetchParams.controller.dump = true
- }
-
- // 20. If request’s integrity metadata is not the empty string, then:
- if (request.integrity) {
- // 1. Let processBodyError be this step: run fetch finale given fetchParams
- // and a network error.
- const processBodyError = (reason) =>
- fetchFinale(fetchParams, makeNetworkError(reason))
-
- // 2. If request’s response tainting is "opaque", or response’s body is null,
- // then run processBodyError and abort these steps.
- if (request.responseTainting === 'opaque' || response.body == null) {
- processBodyError(response.error)
- return
- }
-
- // 3. Let processBody given bytes be these steps:
- const processBody = (bytes) => {
- // 1. If bytes do not match request’s integrity metadata,
- // then run processBodyError and abort these steps. [SRI]
- if (!bytesMatch(bytes, request.integrity)) {
- processBodyError('integrity mismatch')
- return
- }
-
- // 2. Set response’s body to bytes as a body.
- response.body = safelyExtractBody(bytes)[0]
-
- // 3. Run fetch finale given fetchParams and response.
- fetchFinale(fetchParams, response)
- }
-
- // 4. Fully read response’s body given processBody and processBodyError.
- fullyReadBody(response.body, processBody, processBodyError)
- } else {
- // 21. Otherwise, run fetch finale given fetchParams and response.
- fetchFinale(fetchParams, response)
- }
- } catch (err) {
- fetchParams.controller.terminate(err)
- }
-}
-
-// https://fetch.spec.whatwg.org/#concept-scheme-fetch
-// given a fetch params fetchParams
-function schemeFetch (fetchParams) {
- // Note: since the connection is destroyed on redirect, which sets fetchParams to a
- // cancelled state, we do not want this condition to trigger *unless* there have been
- // no redirects. See https://github.com/nodejs/undici/issues/1776
- // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
- if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) {
- return Promise.resolve(makeAppropriateNetworkError(fetchParams))
- }
-
- // 2. Let request be fetchParams’s request.
- const { request } = fetchParams
-
- const { protocol: scheme } = requestCurrentURL(request)
-
- // 3. Switch on request’s current URL’s scheme and run the associated steps:
- switch (scheme) {
- case 'about:': {
- // If request’s current URL’s path is the string "blank", then return a new response
- // whose status message is `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) »,
- // and body is the empty byte sequence as a body.
-
- // Otherwise, return a network error.
- return Promise.resolve(makeNetworkError('about scheme is not supported'))
- }
- case 'blob:': {
- if (!resolveObjectURL) {
- resolveObjectURL = require('node:buffer').resolveObjectURL
- }
-
- // 1. Let blobURLEntry be request’s current URL’s blob URL entry.
- const blobURLEntry = requestCurrentURL(request)
-
- // https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56
- // Buffer.resolveObjectURL does not ignore URL queries.
- if (blobURLEntry.search.length !== 0) {
- return Promise.resolve(makeNetworkError('NetworkError when attempting to fetch resource.'))
- }
-
- const blob = resolveObjectURL(blobURLEntry.toString())
-
- // 2. If request’s method is not `GET`, blobURLEntry is null, or blobURLEntry’s
- // object is not a Blob object, then return a network error.
- if (request.method !== 'GET' || !webidl.is.Blob(blob)) {
- return Promise.resolve(makeNetworkError('invalid method'))
- }
-
- // 3. Let blob be blobURLEntry’s object.
- // Note: done above
-
- // 4. Let response be a new response.
- const response = makeResponse()
-
- // 5. Let fullLength be blob’s size.
- const fullLength = blob.size
-
- // 6. Let serializedFullLength be fullLength, serialized and isomorphic encoded.
- const serializedFullLength = isomorphicEncode(`${fullLength}`)
-
- // 7. Let type be blob’s type.
- const type = blob.type
-
- // 8. If request’s header list does not contain `Range`:
- // 9. Otherwise:
- if (!request.headersList.contains('range', true)) {
- // 1. Let bodyWithType be the result of safely extracting blob.
- // Note: in the FileAPI a blob "object" is a Blob *or* a MediaSource.
- // In node, this can only ever be a Blob. Therefore we can safely
- // use extractBody directly.
- const bodyWithType = extractBody(blob)
-
- // 2. Set response’s status message to `OK`.
- response.statusText = 'OK'
-
- // 3. Set response’s body to bodyWithType’s body.
- response.body = bodyWithType[0]
-
- // 4. Set response’s header list to « (`Content-Length`, serializedFullLength), (`Content-Type`, type) ».
- response.headersList.set('content-length', serializedFullLength, true)
- response.headersList.set('content-type', type, true)
- } else {
- // 1. Set response’s range-requested flag.
- response.rangeRequested = true
-
- // 2. Let rangeHeader be the result of getting `Range` from request’s header list.
- const rangeHeader = request.headersList.get('range', true)
-
- // 3. Let rangeValue be the result of parsing a single range header value given rangeHeader and true.
- const rangeValue = simpleRangeHeaderValue(rangeHeader, true)
-
- // 4. If rangeValue is failure, then return a network error.
- if (rangeValue === 'failure') {
- return Promise.resolve(makeNetworkError('failed to fetch the data URL'))
- }
-
- // 5. Let (rangeStart, rangeEnd) be rangeValue.
- let { rangeStartValue: rangeStart, rangeEndValue: rangeEnd } = rangeValue
-
- // 6. If rangeStart is null:
- // 7. Otherwise:
- if (rangeStart === null) {
- // 1. Set rangeStart to fullLength − rangeEnd.
- rangeStart = fullLength - rangeEnd
-
- // 2. Set rangeEnd to rangeStart + rangeEnd − 1.
- rangeEnd = rangeStart + rangeEnd - 1
- } else {
- // 1. If rangeStart is greater than or equal to fullLength, then return a network error.
- if (rangeStart >= fullLength) {
- return Promise.resolve(makeNetworkError('Range start is greater than the blob\'s size.'))
- }
-
- // 2. If rangeEnd is null or rangeEnd is greater than or equal to fullLength, then set
- // rangeEnd to fullLength − 1.
- if (rangeEnd === null || rangeEnd >= fullLength) {
- rangeEnd = fullLength - 1
- }
- }
-
- // 8. Let slicedBlob be the result of invoking slice blob given blob, rangeStart,
- // rangeEnd + 1, and type.
- const slicedBlob = blob.slice(rangeStart, rangeEnd + 1, type)
-
- // 9. Let slicedBodyWithType be the result of safely extracting slicedBlob.
- // Note: same reason as mentioned above as to why we use extractBody
- const slicedBodyWithType = extractBody(slicedBlob)
-
- // 10. Set response’s body to slicedBodyWithType’s body.
- response.body = slicedBodyWithType[0]
-
- // 11. Let serializedSlicedLength be slicedBlob’s size, serialized and isomorphic encoded.
- const serializedSlicedLength = isomorphicEncode(`${slicedBlob.size}`)
-
- // 12. Let contentRange be the result of invoking build a content range given rangeStart,
- // rangeEnd, and fullLength.
- const contentRange = buildContentRange(rangeStart, rangeEnd, fullLength)
-
- // 13. Set response’s status to 206.
- response.status = 206
-
- // 14. Set response’s status message to `Partial Content`.
- response.statusText = 'Partial Content'
-
- // 15. Set response’s header list to « (`Content-Length`, serializedSlicedLength),
- // (`Content-Type`, type), (`Content-Range`, contentRange) ».
- response.headersList.set('content-length', serializedSlicedLength, true)
- response.headersList.set('content-type', type, true)
- response.headersList.set('content-range', contentRange, true)
- }
-
- // 10. Return response.
- return Promise.resolve(response)
- }
- case 'data:': {
- // 1. Let dataURLStruct be the result of running the
- // data: URL processor on request’s current URL.
- const currentURL = requestCurrentURL(request)
- const dataURLStruct = dataURLProcessor(currentURL)
-
- // 2. If dataURLStruct is failure, then return a
- // network error.
- if (dataURLStruct === 'failure') {
- return Promise.resolve(makeNetworkError('failed to fetch the data URL'))
- }
-
- // 3. Let mimeType be dataURLStruct’s MIME type, serialized.
- const mimeType = serializeAMimeType(dataURLStruct.mimeType)
-
- // 4. Return a response whose status message is `OK`,
- // header list is « (`Content-Type`, mimeType) »,
- // and body is dataURLStruct’s body as a body.
- return Promise.resolve(makeResponse({
- statusText: 'OK',
- headersList: [
- ['content-type', { name: 'Content-Type', value: mimeType }]
- ],
- body: safelyExtractBody(dataURLStruct.body)[0]
- }))
- }
- case 'file:': {
- // For now, unfortunate as it is, file URLs are left as an exercise for the reader.
- // When in doubt, return a network error.
- return Promise.resolve(makeNetworkError('not implemented... yet...'))
- }
- case 'http:':
- case 'https:': {
- // Return the result of running HTTP fetch given fetchParams.
-
- return httpFetch(fetchParams)
- .catch((err) => makeNetworkError(err))
- }
- default: {
- return Promise.resolve(makeNetworkError('unknown scheme'))
- }
- }
-}
-
-// https://fetch.spec.whatwg.org/#finalize-response
-function finalizeResponse (fetchParams, response) {
- // 1. Set fetchParams’s request’s done flag.
- fetchParams.request.done = true
-
- // 2, If fetchParams’s process response done is not null, then queue a fetch
- // task to run fetchParams’s process response done given response, with
- // fetchParams’s task destination.
- if (fetchParams.processResponseDone != null) {
- queueMicrotask(() => fetchParams.processResponseDone(response))
- }
-}
-
-// https://fetch.spec.whatwg.org/#fetch-finale
-function fetchFinale (fetchParams, response) {
- // 1. Let timingInfo be fetchParams’s timing info.
- let timingInfo = fetchParams.timingInfo
-
- // 2. If response is not a network error and fetchParams’s request’s client is a secure context,
- // then set timingInfo’s server-timing headers to the result of getting, decoding, and splitting
- // `Server-Timing` from response’s internal response’s header list.
- // TODO
-
- // 3. Let processResponseEndOfBody be the following steps:
- const processResponseEndOfBody = () => {
- // 1. Let unsafeEndTime be the unsafe shared current time.
- const unsafeEndTime = Date.now() // ?
-
- // 2. If fetchParams’s request’s destination is "document", then set fetchParams’s controller’s
- // full timing info to fetchParams’s timing info.
- if (fetchParams.request.destination === 'document') {
- fetchParams.controller.fullTimingInfo = timingInfo
- }
-
- // 3. Set fetchParams’s controller’s report timing steps to the following steps given a global object global:
- fetchParams.controller.reportTimingSteps = () => {
- // 1. If fetchParams’s request’s URL’s scheme is not an HTTP(S) scheme, then return.
- if (!urlIsHttpHttpsScheme(fetchParams.request.url)) {
- return
- }
-
- // 2. Set timingInfo’s end time to the relative high resolution time given unsafeEndTime and global.
- timingInfo.endTime = unsafeEndTime
-
- // 3. Let cacheState be response’s cache state.
- let cacheState = response.cacheState
-
- // 4. Let bodyInfo be response’s body info.
- const bodyInfo = response.bodyInfo
-
- // 5. If response’s timing allow passed flag is not set, then set timingInfo to the result of creating an
- // opaque timing info for timingInfo and set cacheState to the empty string.
- if (!response.timingAllowPassed) {
- timingInfo = createOpaqueTimingInfo(timingInfo)
-
- cacheState = ''
- }
-
- // 6. Let responseStatus be 0.
- let responseStatus = 0
-
- // 7. If fetchParams’s request’s mode is not "navigate" or response’s has-cross-origin-redirects is false:
- if (fetchParams.request.mode !== 'navigator' || !response.hasCrossOriginRedirects) {
- // 1. Set responseStatus to response’s status.
- responseStatus = response.status
-
- // 2. Let mimeType be the result of extracting a MIME type from response’s header list.
- const mimeType = extractMimeType(response.headersList)
-
- // 3. If mimeType is not failure, then set bodyInfo’s content type to the result of minimizing a supported MIME type given mimeType.
- if (mimeType !== 'failure') {
- bodyInfo.contentType = minimizeSupportedMimeType(mimeType)
- }
- }
-
- // 8. If fetchParams’s request’s initiator type is non-null, then mark resource timing given timingInfo,
- // fetchParams’s request’s URL, fetchParams’s request’s initiator type, global, cacheState, bodyInfo,
- // and responseStatus.
- if (fetchParams.request.initiatorType != null) {
- markResourceTiming(timingInfo, fetchParams.request.url.href, fetchParams.request.initiatorType, globalThis, cacheState, bodyInfo, responseStatus)
- }
- }
-
- // 4. Let processResponseEndOfBodyTask be the following steps:
- const processResponseEndOfBodyTask = () => {
- // 1. Set fetchParams’s request’s done flag.
- fetchParams.request.done = true
-
- // 2. If fetchParams’s process response end-of-body is non-null, then run fetchParams’s process
- // response end-of-body given response.
- if (fetchParams.processResponseEndOfBody != null) {
- queueMicrotask(() => fetchParams.processResponseEndOfBody(response))
- }
-
- // 3. If fetchParams’s request’s initiator type is non-null and fetchParams’s request’s client’s
- // global object is fetchParams’s task destination, then run fetchParams’s controller’s report
- // timing steps given fetchParams’s request’s client’s global object.
- if (fetchParams.request.initiatorType != null) {
- fetchParams.controller.reportTimingSteps()
- }
- }
-
- // 5. Queue a fetch task to run processResponseEndOfBodyTask with fetchParams’s task destination
- queueMicrotask(() => processResponseEndOfBodyTask())
- }
-
- // 4. If fetchParams’s process response is non-null, then queue a fetch task to run fetchParams’s
- // process response given response, with fetchParams’s task destination.
- if (fetchParams.processResponse != null) {
- queueMicrotask(() => {
- fetchParams.processResponse(response)
- fetchParams.processResponse = null
- })
- }
-
- // 5. Let internalResponse be response, if response is a network error; otherwise response’s internal response.
- const internalResponse = response.type === 'error' ? response : (response.internalResponse ?? response)
-
- // 6. If internalResponse’s body is null, then run processResponseEndOfBody.
- // 7. Otherwise:
- if (internalResponse.body == null) {
- processResponseEndOfBody()
- } else {
- // mcollina: all the following steps of the specs are skipped.
- // The internal transform stream is not needed.
- // See https://github.com/nodejs/undici/pull/3093#issuecomment-2050198541
-
- // 1. Let transformStream be a new TransformStream.
- // 2. Let identityTransformAlgorithm be an algorithm which, given chunk, enqueues chunk in transformStream.
- // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm and flushAlgorithm
- // set to processResponseEndOfBody.
- // 4. Set internalResponse’s body’s stream to the result of internalResponse’s body’s stream piped through transformStream.
-
- finished(internalResponse.body.stream, () => {
- processResponseEndOfBody()
- })
- }
-}
-
-// https://fetch.spec.whatwg.org/#http-fetch
-async function httpFetch (fetchParams) {
- // 1. Let request be fetchParams’s request.
- const request = fetchParams.request
-
- // 2. Let response be null.
- let response = null
-
- // 3. Let actualResponse be null.
- let actualResponse = null
-
- // 4. Let timingInfo be fetchParams’s timing info.
- const timingInfo = fetchParams.timingInfo
-
- // 5. If request’s service-workers mode is "all", then:
- if (request.serviceWorkers === 'all') {
- // TODO
- }
-
- // 6. If response is null, then:
- if (response === null) {
- // 1. If makeCORSPreflight is true and one of these conditions is true:
- // TODO
-
- // 2. If request’s redirect mode is "follow", then set request’s
- // service-workers mode to "none".
- if (request.redirect === 'follow') {
- request.serviceWorkers = 'none'
- }
-
- // 3. Set response and actualResponse to the result of running
- // HTTP-network-or-cache fetch given fetchParams.
- actualResponse = response = await httpNetworkOrCacheFetch(fetchParams)
-
- // 4. If request’s response tainting is "cors" and a CORS check
- // for request and response returns failure, then return a network error.
- if (
- request.responseTainting === 'cors' &&
- corsCheck(request, response) === 'failure'
- ) {
- return makeNetworkError('cors failure')
- }
-
- // 5. If the TAO check for request and response returns failure, then set
- // request’s timing allow failed flag.
- if (TAOCheck(request, response) === 'failure') {
- request.timingAllowFailed = true
- }
- }
-
- // 7. If either request’s response tainting or response’s type
- // is "opaque", and the cross-origin resource policy check with
- // request’s origin, request’s client, request’s destination,
- // and actualResponse returns blocked, then return a network error.
- if (
- (request.responseTainting === 'opaque' || response.type === 'opaque') &&
- crossOriginResourcePolicyCheck(
- request.origin,
- request.client,
- request.destination,
- actualResponse
- ) === 'blocked'
- ) {
- return makeNetworkError('blocked')
- }
-
- // 8. If actualResponse’s status is a redirect status, then:
- if (redirectStatusSet.has(actualResponse.status)) {
- // 1. If actualResponse’s status is not 303, request’s body is not null,
- // and the connection uses HTTP/2, then user agents may, and are even
- // encouraged to, transmit an RST_STREAM frame.
- // See, https://github.com/whatwg/fetch/issues/1288
- if (request.redirect !== 'manual') {
- fetchParams.controller.connection.destroy(undefined, false)
- }
-
- // 2. Switch on request’s redirect mode:
- if (request.redirect === 'error') {
- // Set response to a network error.
- response = makeNetworkError('unexpected redirect')
- } else if (request.redirect === 'manual') {
- // Set response to an opaque-redirect filtered response whose internal
- // response is actualResponse.
- // NOTE(spec): On the web this would return an `opaqueredirect` response,
- // but that doesn't make sense server side.
- // See https://github.com/nodejs/undici/issues/1193.
- response = actualResponse
- } else if (request.redirect === 'follow') {
- // Set response to the result of running HTTP-redirect fetch given
- // fetchParams and response.
- response = await httpRedirectFetch(fetchParams, response)
- } else {
- assert(false)
- }
- }
-
- // 9. Set response’s timing info to timingInfo.
- response.timingInfo = timingInfo
-
- // 10. Return response.
- return response
-}
-
-// https://fetch.spec.whatwg.org/#http-redirect-fetch
-function httpRedirectFetch (fetchParams, response) {
- // 1. Let request be fetchParams’s request.
- const request = fetchParams.request
-
- // 2. Let actualResponse be response, if response is not a filtered response,
- // and response’s internal response otherwise.
- const actualResponse = response.internalResponse
- ? response.internalResponse
- : response
-
- // 3. Let locationURL be actualResponse’s location URL given request’s current
- // URL’s fragment.
- let locationURL
-
- try {
- locationURL = responseLocationURL(
- actualResponse,
- requestCurrentURL(request).hash
- )
-
- // 4. If locationURL is null, then return response.
- if (locationURL == null) {
- return response
- }
- } catch (err) {
- // 5. If locationURL is failure, then return a network error.
- return Promise.resolve(makeNetworkError(err))
- }
-
- // 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network
- // error.
- if (!urlIsHttpHttpsScheme(locationURL)) {
- return Promise.resolve(makeNetworkError('URL scheme must be a HTTP(S) scheme'))
- }
-
- // 7. If request’s redirect count is 20, then return a network error.
- if (request.redirectCount === 20) {
- return Promise.resolve(makeNetworkError('redirect count exceeded'))
- }
-
- // 8. Increase request’s redirect count by 1.
- request.redirectCount += 1
-
- // 9. If request’s mode is "cors", locationURL includes credentials, and
- // request’s origin is not same origin with locationURL’s origin, then return
- // a network error.
- if (
- request.mode === 'cors' &&
- (locationURL.username || locationURL.password) &&
- !sameOrigin(request, locationURL)
- ) {
- return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"'))
- }
-
- // 10. If request’s response tainting is "cors" and locationURL includes
- // credentials, then return a network error.
- if (
- request.responseTainting === 'cors' &&
- (locationURL.username || locationURL.password)
- ) {
- return Promise.resolve(makeNetworkError(
- 'URL cannot contain credentials for request mode "cors"'
- ))
- }
-
- // 11. If actualResponse’s status is not 303, request’s body is non-null,
- // and request’s body’s source is null, then return a network error.
- if (
- actualResponse.status !== 303 &&
- request.body != null &&
- request.body.source == null
- ) {
- return Promise.resolve(makeNetworkError())
- }
-
- // 12. If one of the following is true
- // - actualResponse’s status is 301 or 302 and request’s method is `POST`
- // - actualResponse’s status is 303 and request’s method is not `GET` or `HEAD`
- if (
- ([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
- (actualResponse.status === 303 &&
- !GET_OR_HEAD.includes(request.method))
- ) {
- // then:
- // 1. Set request’s method to `GET` and request’s body to null.
- request.method = 'GET'
- request.body = null
-
- // 2. For each headerName of request-body-header name, delete headerName from
- // request’s header list.
- for (const headerName of requestBodyHeader) {
- request.headersList.delete(headerName)
- }
- }
-
- // 13. If request’s current URL’s origin is not same origin with locationURL’s
- // origin, then for each headerName of CORS non-wildcard request-header name,
- // delete headerName from request’s header list.
- if (!sameOrigin(requestCurrentURL(request), locationURL)) {
- // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
- request.headersList.delete('authorization', true)
-
- // https://fetch.spec.whatwg.org/#authentication-entries
- request.headersList.delete('proxy-authorization', true)
-
- // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement.
- request.headersList.delete('cookie', true)
- request.headersList.delete('host', true)
- }
-
- // 14. If request's body is non-null, then set request's body to the first return
- // value of safely extracting request's body's source.
- if (request.body != null) {
- assert(request.body.source != null)
- request.body = safelyExtractBody(request.body.source)[0]
- }
-
- // 15. Let timingInfo be fetchParams’s timing info.
- const timingInfo = fetchParams.timingInfo
-
- // 16. Set timingInfo’s redirect end time and post-redirect start time to the
- // coarsened shared current time given fetchParams’s cross-origin isolated
- // capability.
- timingInfo.redirectEndTime = timingInfo.postRedirectStartTime =
- coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
-
- // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s
- // redirect start time to timingInfo’s start time.
- if (timingInfo.redirectStartTime === 0) {
- timingInfo.redirectStartTime = timingInfo.startTime
- }
-
- // 18. Append locationURL to request’s URL list.
- request.urlList.push(locationURL)
-
- // 19. Invoke set request’s referrer policy on redirect on request and
- // actualResponse.
- setRequestReferrerPolicyOnRedirect(request, actualResponse)
-
- // 20. Return the result of running main fetch given fetchParams and true.
- return mainFetch(fetchParams, true)
-}
-
-// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
-async function httpNetworkOrCacheFetch (
- fetchParams,
- isAuthenticationFetch = false,
- isNewConnectionFetch = false
-) {
- // 1. Let request be fetchParams’s request.
- const request = fetchParams.request
-
- // 2. Let httpFetchParams be null.
- let httpFetchParams = null
-
- // 3. Let httpRequest be null.
- let httpRequest = null
-
- // 4. Let response be null.
- let response = null
-
- // 5. Let storedResponse be null.
- // TODO: cache
-
- // 6. Let httpCache be null.
- const httpCache = null
-
- // 7. Let the revalidatingFlag be unset.
- const revalidatingFlag = false
-
- // 8. Run these steps, but abort when the ongoing fetch is terminated:
-
- // 1. If request’s window is "no-window" and request’s redirect mode is
- // "error", then set httpFetchParams to fetchParams and httpRequest to
- // request.
- if (request.window === 'no-window' && request.redirect === 'error') {
- httpFetchParams = fetchParams
- httpRequest = request
- } else {
- // Otherwise:
-
- // 1. Set httpRequest to a clone of request.
- httpRequest = cloneRequest(request)
-
- // 2. Set httpFetchParams to a copy of fetchParams.
- httpFetchParams = { ...fetchParams }
-
- // 3. Set httpFetchParams’s request to httpRequest.
- httpFetchParams.request = httpRequest
- }
-
- // 3. Let includeCredentials be true if one of
- const includeCredentials =
- request.credentials === 'include' ||
- (request.credentials === 'same-origin' &&
- request.responseTainting === 'basic')
-
- // 4. Let contentLength be httpRequest’s body’s length, if httpRequest’s
- // body is non-null; otherwise null.
- const contentLength = httpRequest.body ? httpRequest.body.length : null
-
- // 5. Let contentLengthHeaderValue be null.
- let contentLengthHeaderValue = null
-
- // 6. If httpRequest’s body is null and httpRequest’s method is `POST` or
- // `PUT`, then set contentLengthHeaderValue to `0`.
- if (
- httpRequest.body == null &&
- ['POST', 'PUT'].includes(httpRequest.method)
- ) {
- contentLengthHeaderValue = '0'
- }
-
- // 7. If contentLength is non-null, then set contentLengthHeaderValue to
- // contentLength, serialized and isomorphic encoded.
- if (contentLength != null) {
- contentLengthHeaderValue = isomorphicEncode(`${contentLength}`)
- }
-
- // 8. If contentLengthHeaderValue is non-null, then append
- // `Content-Length`/contentLengthHeaderValue to httpRequest’s header
- // list.
- if (contentLengthHeaderValue != null) {
- httpRequest.headersList.append('content-length', contentLengthHeaderValue, true)
- }
-
- // 9. If contentLengthHeaderValue is non-null, then append (`Content-Length`,
- // contentLengthHeaderValue) to httpRequest’s header list.
-
- // 10. If contentLength is non-null and httpRequest’s keepalive is true,
- // then:
- if (contentLength != null && httpRequest.keepalive) {
- // NOTE: keepalive is a noop outside of browser context.
- }
-
- // 11. If httpRequest’s referrer is a URL, then append
- // `Referer`/httpRequest’s referrer, serialized and isomorphic encoded,
- // to httpRequest’s header list.
- if (webidl.is.URL(httpRequest.referrer)) {
- httpRequest.headersList.append('referer', isomorphicEncode(httpRequest.referrer.href), true)
- }
-
- // 12. Append a request `Origin` header for httpRequest.
- appendRequestOriginHeader(httpRequest)
-
- // 13. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA]
- appendFetchMetadata(httpRequest)
-
- // 14. If httpRequest’s header list does not contain `User-Agent`, then
- // user agents should append `User-Agent`/default `User-Agent` value to
- // httpRequest’s header list.
- if (!httpRequest.headersList.contains('user-agent', true)) {
- httpRequest.headersList.append('user-agent', defaultUserAgent, true)
- }
-
- // 15. If httpRequest’s cache mode is "default" and httpRequest’s header
- // list contains `If-Modified-Since`, `If-None-Match`,
- // `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set
- // httpRequest’s cache mode to "no-store".
- if (
- httpRequest.cache === 'default' &&
- (httpRequest.headersList.contains('if-modified-since', true) ||
- httpRequest.headersList.contains('if-none-match', true) ||
- httpRequest.headersList.contains('if-unmodified-since', true) ||
- httpRequest.headersList.contains('if-match', true) ||
- httpRequest.headersList.contains('if-range', true))
- ) {
- httpRequest.cache = 'no-store'
- }
-
- // 16. If httpRequest’s cache mode is "no-cache", httpRequest’s prevent
- // no-cache cache-control header modification flag is unset, and
- // httpRequest’s header list does not contain `Cache-Control`, then append
- // `Cache-Control`/`max-age=0` to httpRequest’s header list.
- if (
- httpRequest.cache === 'no-cache' &&
- !httpRequest.preventNoCacheCacheControlHeaderModification &&
- !httpRequest.headersList.contains('cache-control', true)
- ) {
- httpRequest.headersList.append('cache-control', 'max-age=0', true)
- }
-
- // 17. If httpRequest’s cache mode is "no-store" or "reload", then:
- if (httpRequest.cache === 'no-store' || httpRequest.cache === 'reload') {
- // 1. If httpRequest’s header list does not contain `Pragma`, then append
- // `Pragma`/`no-cache` to httpRequest’s header list.
- if (!httpRequest.headersList.contains('pragma', true)) {
- httpRequest.headersList.append('pragma', 'no-cache', true)
- }
-
- // 2. If httpRequest’s header list does not contain `Cache-Control`,
- // then append `Cache-Control`/`no-cache` to httpRequest’s header list.
- if (!httpRequest.headersList.contains('cache-control', true)) {
- httpRequest.headersList.append('cache-control', 'no-cache', true)
- }
- }
-
- // 18. If httpRequest’s header list contains `Range`, then append
- // `Accept-Encoding`/`identity` to httpRequest’s header list.
- if (httpRequest.headersList.contains('range', true)) {
- httpRequest.headersList.append('accept-encoding', 'identity', true)
- }
-
- // 19. Modify httpRequest’s header list per HTTP. Do not append a given
- // header if httpRequest’s header list contains that header’s name.
- // TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129
- if (!httpRequest.headersList.contains('accept-encoding', true)) {
- if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) {
- httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate', true)
- } else {
- httpRequest.headersList.append('accept-encoding', 'gzip, deflate', true)
- }
- }
-
- httpRequest.headersList.delete('host', true)
-
- // 21. If includeCredentials is true, then:
- if (includeCredentials) {
- // 1. If the user agent is not configured to block cookies for httpRequest
- // (see section 7 of [COOKIES]), then:
- // TODO: credentials
-
- // 2. If httpRequest’s header list does not contain `Authorization`, then:
- if (!httpRequest.headersList.contains('authorization', true)) {
- // 1. Let authorizationValue be null.
- let authorizationValue = null
-
- // 2. If there’s an authentication entry for httpRequest and either
- // httpRequest’s use-URL-credentials flag is unset or httpRequest’s
- // current URL does not include credentials, then set
- // authorizationValue to authentication entry.
- if (hasAuthenticationEntry(httpRequest) && (
- httpRequest.useURLCredentials === undefined || !includesCredentials(requestCurrentURL(httpRequest))
- )) {
- // TODO
- } else if (includesCredentials(requestCurrentURL(httpRequest)) && isAuthenticationFetch) {
- // 3. Otherwise, if httpRequest’s current URL does include credentials
- // and isAuthenticationFetch is true, set authorizationValue to
- // httpRequest’s current URL, converted to an `Authorization` value
- const { username, password } = requestCurrentURL(httpRequest)
- authorizationValue = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`
- }
-
- // 4. If authorizationValue is non-null, then append (`Authorization`,
- // authorizationValue) to httpRequest’s header list.
- if (authorizationValue !== null) {
- httpRequest.headersList.append('Authorization', authorizationValue, false)
- }
- }
- }
-
- // 21. If there’s a proxy-authentication entry, use it as appropriate.
- // TODO: proxy-authentication
-
- // 22. Set httpCache to the result of determining the HTTP cache
- // partition, given httpRequest.
- // TODO: cache
-
- // 23. If httpCache is null, then set httpRequest’s cache mode to
- // "no-store".
- if (httpCache == null) {
- httpRequest.cache = 'no-store'
- }
-
- // 24. If httpRequest’s cache mode is neither "no-store" nor "reload",
- // then:
- if (httpRequest.cache !== 'no-store' && httpRequest.cache !== 'reload') {
- // TODO: cache
- }
-
- // 9. If aborted, then return the appropriate network error for fetchParams.
- // TODO
-
- // 10. If response is null, then:
- if (response == null) {
- // 1. If httpRequest’s cache mode is "only-if-cached", then return a
- // network error.
- if (httpRequest.cache === 'only-if-cached') {
- return makeNetworkError('only if cached')
- }
-
- // 2. Let forwardResponse be the result of running HTTP-network fetch
- // given httpFetchParams, includeCredentials, and isNewConnectionFetch.
- const forwardResponse = await httpNetworkFetch(
- httpFetchParams,
- includeCredentials,
- isNewConnectionFetch
- )
-
- // 3. If httpRequest’s method is unsafe and forwardResponse’s status is
- // in the range 200 to 399, inclusive, invalidate appropriate stored
- // responses in httpCache, as per the "Invalidation" chapter of HTTP
- // Caching, and set storedResponse to null. [HTTP-CACHING]
- if (
- !safeMethodsSet.has(httpRequest.method) &&
- forwardResponse.status >= 200 &&
- forwardResponse.status <= 399
- ) {
- // TODO: cache
- }
-
- // 4. If the revalidatingFlag is set and forwardResponse’s status is 304,
- // then:
- if (revalidatingFlag && forwardResponse.status === 304) {
- // TODO: cache
- }
-
- // 5. If response is null, then:
- if (response == null) {
- // 1. Set response to forwardResponse.
- response = forwardResponse
-
- // 2. Store httpRequest and forwardResponse in httpCache, as per the
- // "Storing Responses in Caches" chapter of HTTP Caching. [HTTP-CACHING]
- // TODO: cache
- }
- }
-
- // 11. Set response’s URL list to a clone of httpRequest’s URL list.
- response.urlList = [...httpRequest.urlList]
-
- // 12. If httpRequest’s header list contains `Range`, then set response’s
- // range-requested flag.
- if (httpRequest.headersList.contains('range', true)) {
- response.rangeRequested = true
- }
-
- // 13. Set response’s request-includes-credentials to includeCredentials.
- response.requestIncludesCredentials = includeCredentials
-
- // 14. If response’s status is 401, httpRequest’s response tainting is not "cors",
- // includeCredentials is true, and request’s traversable for user prompts is
- // a traversable navigable:
- if (response.status === 401 && httpRequest.responseTainting !== 'cors' && includeCredentials && isTraversableNavigable(request.traversableForUserPrompts)) {
- // 2. If request’s body is non-null, then:
- if (request.body != null) {
- // 1. If request’s body’s source is null, then return a network error.
- if (request.body.source == null) {
- return makeNetworkError('expected non-null body source')
- }
-
- // 2. Set request’s body to the body of the result of safely extracting
- // request’s body’s source.
- request.body = safelyExtractBody(request.body.source)[0]
- }
-
- // 3. If request’s use-URL-credentials flag is unset or isAuthenticationFetch is
- // true, then:
- if (request.useURLCredentials === undefined || isAuthenticationFetch) {
- // 1. If fetchParams is canceled, then return the appropriate network error
- // for fetchParams.
- if (isCancelled(fetchParams)) {
- return makeAppropriateNetworkError(fetchParams)
- }
-
- // 2. Let username and password be the result of prompting the end user for a
- // username and password, respectively, in request’s traversable for user prompts.
- // TODO
-
- // 3. Set the username given request’s current URL and username.
- // requestCurrentURL(request).username = TODO
-
- // 4. Set the password given request’s current URL and password.
- // requestCurrentURL(request).password = TODO
-
- // In browsers, the user will be prompted to enter a username/password before the request
- // is re-sent. To prevent an infinite 401 loop, return the response for now.
- // https://github.com/nodejs/undici/pull/4756
- return response
- }
-
- // 4. Set response to the result of running HTTP-network-or-cache fetch given
- // fetchParams and true.
- fetchParams.controller.connection.destroy()
-
- response = await httpNetworkOrCacheFetch(fetchParams, true)
- }
-
- // 15. If response’s status is 407, then:
- if (response.status === 407) {
- // 1. If request’s window is "no-window", then return a network error.
- if (request.window === 'no-window') {
- return makeNetworkError()
- }
-
- // 2. ???
-
- // 3. If fetchParams is canceled, then return the appropriate network error for fetchParams.
- if (isCancelled(fetchParams)) {
- return makeAppropriateNetworkError(fetchParams)
- }
-
- // 4. Prompt the end user as appropriate in request’s window and store
- // the result as a proxy-authentication entry. [HTTP-AUTH]
- // TODO: Invoke some kind of callback?
-
- // 5. Set response to the result of running HTTP-network-or-cache fetch given
- // fetchParams.
- // TODO
- return makeNetworkError('proxy authentication required')
- }
-
- // 16. If all of the following are true
- if (
- // response’s status is 421
- response.status === 421 &&
- // isNewConnectionFetch is false
- !isNewConnectionFetch &&
- // request’s body is null, or request’s body is non-null and request’s body’s source is non-null
- (request.body == null || request.body.source != null)
- ) {
- // then:
-
- // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
- if (isCancelled(fetchParams)) {
- return makeAppropriateNetworkError(fetchParams)
- }
-
- // 2. Set response to the result of running HTTP-network-or-cache
- // fetch given fetchParams, isAuthenticationFetch, and true.
-
- // TODO (spec): The spec doesn't specify this but we need to cancel
- // the active response before we can start a new one.
- // https://github.com/whatwg/fetch/issues/1293
- fetchParams.controller.connection.destroy()
-
- response = await httpNetworkOrCacheFetch(
- fetchParams,
- isAuthenticationFetch,
- true
- )
- }
-
- // 17. If isAuthenticationFetch is true, then create an authentication entry
- if (isAuthenticationFetch) {
- // TODO
- }
-
- // 18. Return response.
- return response
-}
-
-// https://fetch.spec.whatwg.org/#http-network-fetch
-async function httpNetworkFetch (
- fetchParams,
- includeCredentials = false,
- forceNewConnection = false
-) {
- assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed)
-
- fetchParams.controller.connection = {
- abort: null,
- destroyed: false,
- destroy (err, abort = true) {
- if (!this.destroyed) {
- this.destroyed = true
- if (abort) {
- this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError'))
- }
- }
- }
- }
-
- // 1. Let request be fetchParams’s request.
- const request = fetchParams.request
-
- // 2. Let response be null.
- let response = null
-
- // 3. Let timingInfo be fetchParams’s timing info.
- const timingInfo = fetchParams.timingInfo
-
- // 4. Let httpCache be the result of determining the HTTP cache partition,
- // given request.
- // TODO: cache
- const httpCache = null
-
- // 5. If httpCache is null, then set request’s cache mode to "no-store".
- if (httpCache == null) {
- request.cache = 'no-store'
- }
-
- // 6. Let networkPartitionKey be the result of determining the network
- // partition key given request.
- // TODO
-
- // 7. Let newConnection be "yes" if forceNewConnection is true; otherwise
- // "no".
- const newConnection = forceNewConnection ? 'yes' : 'no' // eslint-disable-line no-unused-vars
-
- // 8. Switch on request’s mode:
- if (request.mode === 'websocket') {
- // Let connection be the result of obtaining a WebSocket connection,
- // given request’s current URL.
- // TODO
- } else {
- // Let connection be the result of obtaining a connection, given
- // networkPartitionKey, request’s current URL’s origin,
- // includeCredentials, and forceNewConnection.
- // TODO
- }
-
- // 9. Run these steps, but abort when the ongoing fetch is terminated:
-
- // 1. If connection is failure, then return a network error.
-
- // 2. Set timingInfo’s final connection timing info to the result of
- // calling clamp and coarsen connection timing info with connection’s
- // timing info, timingInfo’s post-redirect start time, and fetchParams’s
- // cross-origin isolated capability.
-
- // 3. If connection is not an HTTP/2 connection, request’s body is non-null,
- // and request’s body’s source is null, then append (`Transfer-Encoding`,
- // `chunked`) to request’s header list.
-
- // 4. Set timingInfo’s final network-request start time to the coarsened
- // shared current time given fetchParams’s cross-origin isolated
- // capability.
-
- // 5. Set response to the result of making an HTTP request over connection
- // using request with the following caveats:
-
- // - Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS]
- // [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH]
-
- // - If request’s body is non-null, and request’s body’s source is null,
- // then the user agent may have a buffer of up to 64 kibibytes and store
- // a part of request’s body in that buffer. If the user agent reads from
- // request’s body beyond that buffer’s size and the user agent needs to
- // resend request, then instead return a network error.
-
- // - Set timingInfo’s final network-response start time to the coarsened
- // shared current time given fetchParams’s cross-origin isolated capability,
- // immediately after the user agent’s HTTP parser receives the first byte
- // of the response (e.g., frame header bytes for HTTP/2 or response status
- // line for HTTP/1.x).
-
- // - Wait until all the headers are transmitted.
-
- // - Any responses whose status is in the range 100 to 199, inclusive,
- // and is not 101, are to be ignored, except for the purposes of setting
- // timingInfo’s final network-response start time above.
-
- // - If request’s header list contains `Transfer-Encoding`/`chunked` and
- // response is transferred via HTTP/1.0 or older, then return a network
- // error.
-
- // - If the HTTP request results in a TLS client certificate dialog, then:
-
- // 1. If request’s window is an environment settings object, make the
- // dialog available in request’s window.
-
- // 2. Otherwise, return a network error.
-
- // To transmit request’s body body, run these steps:
- let requestBody = null
- // 1. If body is null and fetchParams’s process request end-of-body is
- // non-null, then queue a fetch task given fetchParams’s process request
- // end-of-body and fetchParams’s task destination.
- if (request.body == null && fetchParams.processRequestEndOfBody) {
- queueMicrotask(() => fetchParams.processRequestEndOfBody())
- } else if (request.body != null) {
- // 2. Otherwise, if body is non-null:
-
- // 1. Let processBodyChunk given bytes be these steps:
- const processBodyChunk = async function * (bytes) {
- // 1. If the ongoing fetch is terminated, then abort these steps.
- if (isCancelled(fetchParams)) {
- return
- }
-
- // 2. Run this step in parallel: transmit bytes.
- yield bytes
-
- // 3. If fetchParams’s process request body is non-null, then run
- // fetchParams’s process request body given bytes’s length.
- fetchParams.processRequestBodyChunkLength?.(bytes.byteLength)
- }
-
- // 2. Let processEndOfBody be these steps:
- const processEndOfBody = () => {
- // 1. If fetchParams is canceled, then abort these steps.
- if (isCancelled(fetchParams)) {
- return
- }
-
- // 2. If fetchParams’s process request end-of-body is non-null,
- // then run fetchParams’s process request end-of-body.
- if (fetchParams.processRequestEndOfBody) {
- fetchParams.processRequestEndOfBody()
- }
- }
-
- // 3. Let processBodyError given e be these steps:
- const processBodyError = (e) => {
- // 1. If fetchParams is canceled, then abort these steps.
- if (isCancelled(fetchParams)) {
- return
- }
-
- // 2. If e is an "AbortError" DOMException, then abort fetchParams’s controller.
- if (e.name === 'AbortError') {
- fetchParams.controller.abort()
- } else {
- fetchParams.controller.terminate(e)
- }
- }
-
- // 4. Incrementally read request’s body given processBodyChunk, processEndOfBody,
- // processBodyError, and fetchParams’s task destination.
- requestBody = (async function * () {
- try {
- for await (const bytes of request.body.stream) {
- yield * processBodyChunk(bytes)
- }
- processEndOfBody()
- } catch (err) {
- processBodyError(err)
- }
- })()
- }
-
- try {
- // socket is only provided for websockets
- const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody })
-
- if (socket) {
- response = makeResponse({ status, statusText, headersList, socket })
- } else {
- const iterator = body[Symbol.asyncIterator]()
- fetchParams.controller.next = () => iterator.next()
-
- response = makeResponse({ status, statusText, headersList })
- }
- } catch (err) {
- // 10. If aborted, then:
- if (err.name === 'AbortError') {
- // 1. If connection uses HTTP/2, then transmit an RST_STREAM frame.
- fetchParams.controller.connection.destroy()
-
- // 2. Return the appropriate network error for fetchParams.
- return makeAppropriateNetworkError(fetchParams, err)
- }
-
- return makeNetworkError(err)
- }
-
- // 11. Let pullAlgorithm be an action that resumes the ongoing fetch
- // if it is suspended.
- const pullAlgorithm = () => {
- return fetchParams.controller.resume()
- }
-
- // 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’s
- // controller with reason, given reason.
- const cancelAlgorithm = (reason) => {
- // If the aborted fetch was already terminated, then we do not
- // need to do anything.
- if (!isCancelled(fetchParams)) {
- fetchParams.controller.abort(reason)
- }
- }
-
- // 13. Let highWaterMark be a non-negative, non-NaN number, chosen by
- // the user agent.
- // TODO
-
- // 14. Let sizeAlgorithm be an algorithm that accepts a chunk object
- // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent.
- // TODO
-
- // 15. Let stream be a new ReadableStream.
- // 16. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm,
- // cancelAlgorithm set to cancelAlgorithm.
- const stream = new ReadableStream(
- {
- start (controller) {
- fetchParams.controller.controller = controller
- },
- pull: pullAlgorithm,
- cancel: cancelAlgorithm,
- type: 'bytes'
- }
- )
-
- // 17. Run these steps, but abort when the ongoing fetch is terminated:
-
- // 1. Set response’s body to a new body whose stream is stream.
- response.body = { stream, source: null, length: null }
-
- // 2. If response is not a network error and request’s cache mode is
- // not "no-store", then update response in httpCache for request.
- // TODO
-
- // 3. If includeCredentials is true and the user agent is not configured
- // to block cookies for request (see section 7 of [COOKIES]), then run the
- // "set-cookie-string" parsing algorithm (see section 5.2 of [COOKIES]) on
- // the value of each header whose name is a byte-case-insensitive match for
- // `Set-Cookie` in response’s header list, if any, and request’s current URL.
- // TODO
-
- // 18. If aborted, then:
- // TODO
-
- // 19. Run these steps in parallel:
-
- // 1. Run these steps, but abort when fetchParams is canceled:
- if (!fetchParams.controller.resume) {
- fetchParams.controller.on('terminated', onAborted)
- }
-
- fetchParams.controller.resume = async () => {
- // 1. While true
- while (true) {
- // 1-3. See onData...
-
- // 4. Set bytes to the result of handling content codings given
- // codings and bytes.
- let bytes
- let isFailure
- try {
- const { done, value } = await fetchParams.controller.next()
-
- if (isAborted(fetchParams)) {
- break
- }
-
- bytes = done ? undefined : value
- } catch (err) {
- if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
- // zlib doesn't like empty streams.
- bytes = undefined
- } else {
- bytes = err
-
- // err may be propagated from the result of calling readablestream.cancel,
- // which might not be an error. https://github.com/nodejs/undici/issues/2009
- isFailure = true
- }
- }
-
- if (bytes === undefined) {
- // 2. Otherwise, if the bytes transmission for response’s message
- // body is done normally and stream is readable, then close
- // stream, finalize response for fetchParams and response, and
- // abort these in-parallel steps.
- readableStreamClose(fetchParams.controller.controller)
-
- finalizeResponse(fetchParams, response)
-
- return
- }
-
- // 5. Increase timingInfo’s decoded body size by bytes’s length.
- timingInfo.decodedBodySize += bytes?.byteLength ?? 0
-
- // 6. If bytes is failure, then terminate fetchParams’s controller.
- if (isFailure) {
- fetchParams.controller.terminate(bytes)
- return
- }
-
- // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes
- // into stream.
- const buffer = new Uint8Array(bytes)
- if (buffer.byteLength) {
- fetchParams.controller.controller.enqueue(buffer)
- }
-
- // 8. If stream is errored, then terminate the ongoing fetch.
- if (isErrored(stream)) {
- fetchParams.controller.terminate()
- return
- }
-
- // 9. If stream doesn’t need more data ask the user agent to suspend
- // the ongoing fetch.
- if (fetchParams.controller.controller.desiredSize <= 0) {
- return
- }
- }
- }
-
- // 2. If aborted, then:
- function onAborted (reason) {
- // 2. If fetchParams is aborted, then:
- if (isAborted(fetchParams)) {
- // 1. Set response’s aborted flag.
- response.aborted = true
-
- // 2. If stream is readable, then error stream with the result of
- // deserialize a serialized abort reason given fetchParams’s
- // controller’s serialized abort reason and an
- // implementation-defined realm.
- if (isReadable(stream)) {
- fetchParams.controller.controller.error(
- fetchParams.controller.serializedAbortReason
- )
- }
- } else {
- // 3. Otherwise, if stream is readable, error stream with a TypeError.
- if (isReadable(stream)) {
- fetchParams.controller.controller.error(new TypeError('terminated', {
- cause: isErrorLike(reason) ? reason : undefined
- }))
- }
- }
-
- // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame.
- // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
- fetchParams.controller.connection.destroy()
- }
-
- // 20. Return response.
- return response
-
- function dispatch ({ body }) {
- const url = requestCurrentURL(request)
- /** @type {import('../../..').Agent} */
- const agent = fetchParams.controller.dispatcher
-
- return new Promise((resolve, reject) => agent.dispatch(
- {
- path: url.pathname + url.search,
- origin: url.origin,
- method: request.method,
- body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
- headers: request.headersList.entries,
- maxRedirections: 0,
- upgrade: request.mode === 'websocket' ? 'websocket' : undefined
- },
- {
- body: null,
- abort: null,
-
- onConnect (abort) {
- // TODO (fix): Do we need connection here?
- const { connection } = fetchParams.controller
-
- // Set timingInfo’s final connection timing info to the result of calling clamp and coarsen
- // connection timing info with connection’s timing info, timingInfo’s post-redirect start
- // time, and fetchParams’s cross-origin isolated capability.
- // TODO: implement connection timing
- timingInfo.finalConnectionTimingInfo = clampAndCoarsenConnectionTimingInfo(undefined, timingInfo.postRedirectStartTime, fetchParams.crossOriginIsolatedCapability)
-
- if (connection.destroyed) {
- abort(new DOMException('The operation was aborted.', 'AbortError'))
- } else {
- fetchParams.controller.on('terminated', abort)
- this.abort = connection.abort = abort
- }
-
- // Set timingInfo’s final network-request start time to the coarsened shared current time given
- // fetchParams’s cross-origin isolated capability.
- timingInfo.finalNetworkRequestStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
- },
-
- onResponseStarted () {
- // Set timingInfo’s final network-response start time to the coarsened shared current
- // time given fetchParams’s cross-origin isolated capability, immediately after the
- // user agent’s HTTP parser receives the first byte of the response (e.g., frame header
- // bytes for HTTP/2 or response status line for HTTP/1.x).
- timingInfo.finalNetworkResponseStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
- },
-
- onHeaders (status, rawHeaders, resume, statusText) {
- if (status < 200) {
- return false
- }
-
- const headersList = new HeadersList()
-
- for (let i = 0; i < rawHeaders.length; i += 2) {
- headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
- }
- const location = headersList.get('location', true)
-
- this.body = new Readable({ read: resume })
-
- const willFollow = location && request.redirect === 'follow' &&
- redirectStatusSet.has(status)
-
- const decoders = []
-
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
- if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
- // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
- const contentEncoding = headersList.get('content-encoding', true)
- // "All content-coding values are case-insensitive..."
- /** @type {string[]} */
- const codings = contentEncoding ? contentEncoding.toLowerCase().split(',') : []
-
- // Limit the number of content-encodings to prevent resource exhaustion.
- // CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
- const maxContentEncodings = 5
- if (codings.length > maxContentEncodings) {
- reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
- return true
- }
-
- for (let i = codings.length - 1; i >= 0; --i) {
- const coding = codings[i].trim()
- // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
- if (coding === 'x-gzip' || coding === 'gzip') {
- decoders.push(zlib.createGunzip({
- // Be less strict when decoding compressed responses, since sometimes
- // servers send slightly invalid responses that are still accepted
- // by common browsers.
- // Always using Z_SYNC_FLUSH is what cURL does.
- flush: zlib.constants.Z_SYNC_FLUSH,
- finishFlush: zlib.constants.Z_SYNC_FLUSH
- }))
- } else if (coding === 'deflate') {
- decoders.push(createInflate({
- flush: zlib.constants.Z_SYNC_FLUSH,
- finishFlush: zlib.constants.Z_SYNC_FLUSH
- }))
- } else if (coding === 'br') {
- decoders.push(zlib.createBrotliDecompress({
- flush: zlib.constants.BROTLI_OPERATION_FLUSH,
- finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
- }))
- } else if (coding === 'zstd' && hasZstd) {
- decoders.push(zlib.createZstdDecompress({
- flush: zlib.constants.ZSTD_e_continue,
- finishFlush: zlib.constants.ZSTD_e_end
- }))
- } else {
- decoders.length = 0
- break
- }
- }
- }
-
- const onError = this.onError.bind(this)
-
- resolve({
- status,
- statusText,
- headersList,
- body: decoders.length
- ? pipeline(this.body, ...decoders, (err) => {
- if (err) {
- this.onError(err)
- }
- }).on('error', onError)
- : this.body.on('error', onError)
- })
-
- return true
- },
-
- onData (chunk) {
- if (fetchParams.controller.dump) {
- return
- }
-
- // 1. If one or more bytes have been transmitted from response’s
- // message body, then:
-
- // 1. Let bytes be the transmitted bytes.
- const bytes = chunk
-
- // 2. Let codings be the result of extracting header list values
- // given `Content-Encoding` and response’s header list.
- // See pullAlgorithm.
-
- // 3. Increase timingInfo’s encoded body size by bytes’s length.
- timingInfo.encodedBodySize += bytes.byteLength
-
- // 4. See pullAlgorithm...
-
- return this.body.push(bytes)
- },
-
- onComplete () {
- if (this.abort) {
- fetchParams.controller.off('terminated', this.abort)
- }
-
- fetchParams.controller.ended = true
-
- this.body.push(null)
- },
-
- onError (error) {
- if (this.abort) {
- fetchParams.controller.off('terminated', this.abort)
- }
-
- this.body?.destroy(error)
-
- fetchParams.controller.terminate(error)
-
- reject(error)
- },
-
- onRequestUpgrade (_controller, status, headers, socket) {
- // We need to support 200 for websocket over h2 as per RFC-8441
- // Absence of session means H1
- if ((socket.session != null && status !== 200) || (socket.session == null && status !== 101)) {
- return false
- }
-
- const headersList = new HeadersList()
-
- for (const [name, value] of Object.entries(headers)) {
- if (value == null) {
- continue
- }
-
- const headerName = name.toLowerCase()
-
- if (Array.isArray(value)) {
- for (const entry of value) {
- headersList.append(headerName, String(entry), true)
- }
- } else {
- headersList.append(headerName, String(value), true)
- }
- }
-
- resolve({
- status,
- statusText: STATUS_CODES[status],
- headersList,
- socket
- })
-
- return true
- },
-
- onUpgrade (status, rawHeaders, socket) {
- // We need to support 200 for websocket over h2 as per RFC-8441
- // Absence of session means H1
- if ((socket.session != null && status !== 200) || (socket.session == null && status !== 101)) {
- return false
- }
-
- const headersList = new HeadersList()
-
- for (let i = 0; i < rawHeaders.length; i += 2) {
- headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
- }
-
- resolve({
- status,
- statusText: STATUS_CODES[status],
- headersList,
- socket
- })
-
- return true
- }
- }
- ))
- }
-}
-
-module.exports = {
- fetch,
- Fetch,
- fetching,
- finalizeAndReportTiming
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/request.js b/vanilla/node_modules/undici/lib/web/fetch/request.js
deleted file mode 100644
index 6ef40f9..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/request.js
+++ /dev/null
@@ -1,1115 +0,0 @@
-/* globals AbortController */
-
-'use strict'
-
-const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body')
-const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
-const util = require('../../core/util')
-const nodeUtil = require('node:util')
-const {
- isValidHTTPToken,
- sameOrigin,
- environmentSettingsObject
-} = require('./util')
-const {
- forbiddenMethodsSet,
- corsSafeListedMethodsSet,
- referrerPolicy,
- requestRedirect,
- requestMode,
- requestCredentials,
- requestCache,
- requestDuplex
-} = require('./constants')
-const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
-const { webidl } = require('../webidl')
-const { URLSerializer } = require('./data-url')
-const { kConstruct } = require('../../core/symbols')
-const assert = require('node:assert')
-const { getMaxListeners, setMaxListeners, defaultMaxListeners } = require('node:events')
-
-const kAbortController = Symbol('abortController')
-
-const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
- signal.removeEventListener('abort', abort)
-})
-
-const dependentControllerMap = new WeakMap()
-
-let abortSignalHasEventHandlerLeakWarning
-
-try {
- abortSignalHasEventHandlerLeakWarning = getMaxListeners(new AbortController().signal) > 0
-} catch {
- abortSignalHasEventHandlerLeakWarning = false
-}
-
-function buildAbort (acRef) {
- return abort
-
- function abort () {
- const ac = acRef.deref()
- if (ac !== undefined) {
- // Currently, there is a problem with FinalizationRegistry.
- // https://github.com/nodejs/node/issues/49344
- // https://github.com/nodejs/node/issues/47748
- // In the case of abort, the first step is to unregister from it.
- // If the controller can refer to it, it is still registered.
- // It will be removed in the future.
- requestFinalizer.unregister(abort)
-
- // Unsubscribe a listener.
- // FinalizationRegistry will no longer be called, so this must be done.
- this.removeEventListener('abort', abort)
-
- ac.abort(this.reason)
-
- const controllerList = dependentControllerMap.get(ac.signal)
-
- if (controllerList !== undefined) {
- if (controllerList.size !== 0) {
- for (const ref of controllerList) {
- const ctrl = ref.deref()
- if (ctrl !== undefined) {
- ctrl.abort(this.reason)
- }
- }
- controllerList.clear()
- }
- dependentControllerMap.delete(ac.signal)
- }
- }
- }
-}
-
-let patchMethodWarning = false
-
-// https://fetch.spec.whatwg.org/#request-class
-class Request {
- /** @type {AbortSignal} */
- #signal
-
- /** @type {import('../../dispatcher/dispatcher')} */
- #dispatcher
-
- /** @type {Headers} */
- #headers
-
- #state
-
- // https://fetch.spec.whatwg.org/#dom-request
- constructor (input, init = undefined) {
- webidl.util.markAsUncloneable(this)
-
- if (input === kConstruct) {
- return
- }
-
- const prefix = 'Request constructor'
- webidl.argumentLengthCheck(arguments, 1, prefix)
-
- input = webidl.converters.RequestInfo(input)
- init = webidl.converters.RequestInit(init)
-
- // 1. Let request be null.
- let request = null
-
- // 2. Let fallbackMode be null.
- let fallbackMode = null
-
- // 3. Let baseURL be this’s relevant settings object’s API base URL.
- const baseUrl = environmentSettingsObject.settingsObject.baseUrl
-
- // 4. Let signal be null.
- let signal = null
-
- // 5. If input is a string, then:
- if (typeof input === 'string') {
- this.#dispatcher = init.dispatcher
-
- // 1. Let parsedURL be the result of parsing input with baseURL.
- // 2. If parsedURL is failure, then throw a TypeError.
- let parsedURL
- try {
- parsedURL = new URL(input, baseUrl)
- } catch (err) {
- throw new TypeError('Failed to parse URL from ' + input, { cause: err })
- }
-
- // 3. If parsedURL includes credentials, then throw a TypeError.
- if (parsedURL.username || parsedURL.password) {
- throw new TypeError(
- 'Request cannot be constructed from a URL that includes credentials: ' +
- input
- )
- }
-
- // 4. Set request to a new request whose URL is parsedURL.
- request = makeRequest({ urlList: [parsedURL] })
-
- // 5. Set fallbackMode to "cors".
- fallbackMode = 'cors'
- } else {
- // 6. Otherwise:
-
- // 7. Assert: input is a Request object.
- assert(webidl.is.Request(input))
-
- // 8. Set request to input’s request.
- request = input.#state
-
- // 9. Set signal to input’s signal.
- signal = input.#signal
-
- this.#dispatcher = init.dispatcher || input.#dispatcher
- }
-
- // 7. Let origin be this’s relevant settings object’s origin.
- const origin = environmentSettingsObject.settingsObject.origin
-
- // 8. Let window be "client".
- let window = 'client'
-
- // 9. If request’s window is an environment settings object and its origin
- // is same origin with origin, then set window to request’s window.
- if (
- request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
- sameOrigin(request.window, origin)
- ) {
- window = request.window
- }
-
- // 10. If init["window"] exists and is non-null, then throw a TypeError.
- if (init.window != null) {
- throw new TypeError(`'window' option '${window}' must be null`)
- }
-
- // 11. If init["window"] exists, then set window to "no-window".
- if ('window' in init) {
- window = 'no-window'
- }
-
- // 12. Set request to a new request with the following properties:
- request = makeRequest({
- // URL request’s URL.
- // undici implementation note: this is set as the first item in request's urlList in makeRequest
- // method request’s method.
- method: request.method,
- // header list A copy of request’s header list.
- // undici implementation note: headersList is cloned in makeRequest
- headersList: request.headersList,
- // unsafe-request flag Set.
- unsafeRequest: request.unsafeRequest,
- // client This’s relevant settings object.
- client: environmentSettingsObject.settingsObject,
- // window window.
- window,
- // priority request’s priority.
- priority: request.priority,
- // origin request’s origin. The propagation of the origin is only significant for navigation requests
- // being handled by a service worker. In this scenario a request can have an origin that is different
- // from the current client.
- origin: request.origin,
- // referrer request’s referrer.
- referrer: request.referrer,
- // referrer policy request’s referrer policy.
- referrerPolicy: request.referrerPolicy,
- // mode request’s mode.
- mode: request.mode,
- // credentials mode request’s credentials mode.
- credentials: request.credentials,
- // cache mode request’s cache mode.
- cache: request.cache,
- // redirect mode request’s redirect mode.
- redirect: request.redirect,
- // integrity metadata request’s integrity metadata.
- integrity: request.integrity,
- // keepalive request’s keepalive.
- keepalive: request.keepalive,
- // reload-navigation flag request’s reload-navigation flag.
- reloadNavigation: request.reloadNavigation,
- // history-navigation flag request’s history-navigation flag.
- historyNavigation: request.historyNavigation,
- // URL list A clone of request’s URL list.
- urlList: [...request.urlList]
- })
-
- const initHasKey = Object.keys(init).length !== 0
-
- // 13. If init is not empty, then:
- if (initHasKey) {
- // 1. If request’s mode is "navigate", then set it to "same-origin".
- if (request.mode === 'navigate') {
- request.mode = 'same-origin'
- }
-
- // 2. Unset request’s reload-navigation flag.
- request.reloadNavigation = false
-
- // 3. Unset request’s history-navigation flag.
- request.historyNavigation = false
-
- // 4. Set request’s origin to "client".
- request.origin = 'client'
-
- // 5. Set request’s referrer to "client"
- request.referrer = 'client'
-
- // 6. Set request’s referrer policy to the empty string.
- request.referrerPolicy = ''
-
- // 7. Set request’s URL to request’s current URL.
- request.url = request.urlList[request.urlList.length - 1]
-
- // 8. Set request’s URL list to « request’s URL ».
- request.urlList = [request.url]
- }
-
- // 14. If init["referrer"] exists, then:
- if (init.referrer !== undefined) {
- // 1. Let referrer be init["referrer"].
- const referrer = init.referrer
-
- // 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
- if (referrer === '') {
- request.referrer = 'no-referrer'
- } else {
- // 1. Let parsedReferrer be the result of parsing referrer with
- // baseURL.
- // 2. If parsedReferrer is failure, then throw a TypeError.
- let parsedReferrer
- try {
- parsedReferrer = new URL(referrer, baseUrl)
- } catch (err) {
- throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
- }
-
- // 3. If one of the following is true
- // - parsedReferrer’s scheme is "about" and path is the string "client"
- // - parsedReferrer’s origin is not same origin with origin
- // then set request’s referrer to "client".
- if (
- (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') ||
- (origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl))
- ) {
- request.referrer = 'client'
- } else {
- // 4. Otherwise, set request’s referrer to parsedReferrer.
- request.referrer = parsedReferrer
- }
- }
- }
-
- // 15. If init["referrerPolicy"] exists, then set request’s referrer policy
- // to it.
- if (init.referrerPolicy !== undefined) {
- request.referrerPolicy = init.referrerPolicy
- }
-
- // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
- let mode
- if (init.mode !== undefined) {
- mode = init.mode
- } else {
- mode = fallbackMode
- }
-
- // 17. If mode is "navigate", then throw a TypeError.
- if (mode === 'navigate') {
- throw webidl.errors.exception({
- header: 'Request constructor',
- message: 'invalid request mode navigate.'
- })
- }
-
- // 18. If mode is non-null, set request’s mode to mode.
- if (mode != null) {
- request.mode = mode
- }
-
- // 19. If init["credentials"] exists, then set request’s credentials mode
- // to it.
- if (init.credentials !== undefined) {
- request.credentials = init.credentials
- }
-
- // 18. If init["cache"] exists, then set request’s cache mode to it.
- if (init.cache !== undefined) {
- request.cache = init.cache
- }
-
- // 21. If request’s cache mode is "only-if-cached" and request’s mode is
- // not "same-origin", then throw a TypeError.
- if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
- throw new TypeError(
- "'only-if-cached' can be set only with 'same-origin' mode"
- )
- }
-
- // 22. If init["redirect"] exists, then set request’s redirect mode to it.
- if (init.redirect !== undefined) {
- request.redirect = init.redirect
- }
-
- // 23. If init["integrity"] exists, then set request’s integrity metadata to it.
- if (init.integrity != null) {
- request.integrity = String(init.integrity)
- }
-
- // 24. If init["keepalive"] exists, then set request’s keepalive to it.
- if (init.keepalive !== undefined) {
- request.keepalive = Boolean(init.keepalive)
- }
-
- // 25. If init["method"] exists, then:
- if (init.method !== undefined) {
- // 1. Let method be init["method"].
- let method = init.method
-
- const mayBeNormalized = normalizedMethodRecords[method]
-
- if (mayBeNormalized !== undefined) {
- // Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones
- request.method = mayBeNormalized
- } else {
- // 2. If method is not a method or method is a forbidden method, then
- // throw a TypeError.
- if (!isValidHTTPToken(method)) {
- throw new TypeError(`'${method}' is not a valid HTTP method.`)
- }
-
- const upperCase = method.toUpperCase()
-
- if (forbiddenMethodsSet.has(upperCase)) {
- throw new TypeError(`'${method}' HTTP method is unsupported.`)
- }
-
- // 3. Normalize method.
- // https://fetch.spec.whatwg.org/#concept-method-normalize
- // Note: must be in uppercase
- method = normalizedMethodRecordsBase[upperCase] ?? method
-
- // 4. Set request’s method to method.
- request.method = method
- }
-
- if (!patchMethodWarning && request.method === 'patch') {
- process.emitWarning('Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.', {
- code: 'UNDICI-FETCH-patch'
- })
-
- patchMethodWarning = true
- }
- }
-
- // 26. If init["signal"] exists, then set signal to it.
- if (init.signal !== undefined) {
- signal = init.signal
- }
-
- // 27. Set this’s request to request.
- this.#state = request
-
- // 28. Set this’s signal to a new AbortSignal object with this’s relevant
- // Realm.
- // TODO: could this be simplified with AbortSignal.any
- // (https://dom.spec.whatwg.org/#dom-abortsignal-any)
- const ac = new AbortController()
- this.#signal = ac.signal
-
- // 29. If signal is not null, then make this’s signal follow signal.
- if (signal != null) {
- if (signal.aborted) {
- ac.abort(signal.reason)
- } else {
- // Keep a strong ref to ac while request object
- // is alive. This is needed to prevent AbortController
- // from being prematurely garbage collected.
- // See, https://github.com/nodejs/undici/issues/1926.
- this[kAbortController] = ac
-
- const acRef = new WeakRef(ac)
- const abort = buildAbort(acRef)
-
- // If the max amount of listeners is equal to the default, increase it
- if (abortSignalHasEventHandlerLeakWarning && getMaxListeners(signal) === defaultMaxListeners) {
- setMaxListeners(1500, signal)
- }
-
- util.addAbortListener(signal, abort)
- // The third argument must be a registry key to be unregistered.
- // Without it, you cannot unregister.
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
- // abort is used as the unregister key. (because it is unique)
- requestFinalizer.register(ac, { signal, abort }, abort)
- }
- }
-
- // 30. Set this’s headers to a new Headers object with this’s relevant
- // Realm, whose header list is request’s header list and guard is
- // "request".
- this.#headers = new Headers(kConstruct)
- setHeadersList(this.#headers, request.headersList)
- setHeadersGuard(this.#headers, 'request')
-
- // 31. If this’s request’s mode is "no-cors", then:
- if (mode === 'no-cors') {
- // 1. If this’s request’s method is not a CORS-safelisted method,
- // then throw a TypeError.
- if (!corsSafeListedMethodsSet.has(request.method)) {
- throw new TypeError(
- `'${request.method} is unsupported in no-cors mode.`
- )
- }
-
- // 2. Set this’s headers’s guard to "request-no-cors".
- setHeadersGuard(this.#headers, 'request-no-cors')
- }
-
- // 32. If init is not empty, then:
- if (initHasKey) {
- /** @type {HeadersList} */
- const headersList = getHeadersList(this.#headers)
- // 1. Let headers be a copy of this’s headers and its associated header
- // list.
- // 2. If init["headers"] exists, then set headers to init["headers"].
- const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList)
-
- // 3. Empty this’s headers’s header list.
- headersList.clear()
-
- // 4. If headers is a Headers object, then for each header in its header
- // list, append header’s name/header’s value to this’s headers.
- if (headers instanceof HeadersList) {
- for (const { name, value } of headers.rawValues()) {
- headersList.append(name, value, false)
- }
- // Note: Copy the `set-cookie` meta-data.
- headersList.cookies = headers.cookies
- } else {
- // 5. Otherwise, fill this’s headers with headers.
- fillHeaders(this.#headers, headers)
- }
- }
-
- // 33. Let inputBody be input’s request’s body if input is a Request
- // object; otherwise null.
- const inputBody = webidl.is.Request(input) ? input.#state.body : null
-
- // 34. If either init["body"] exists and is non-null or inputBody is
- // non-null, and request’s method is `GET` or `HEAD`, then throw a
- // TypeError.
- if (
- (init.body != null || inputBody != null) &&
- (request.method === 'GET' || request.method === 'HEAD')
- ) {
- throw new TypeError('Request with GET/HEAD method cannot have body.')
- }
-
- // 35. Let initBody be null.
- let initBody = null
-
- // 36. If init["body"] exists and is non-null, then:
- if (init.body != null) {
- // 1. Let Content-Type be null.
- // 2. Set initBody and Content-Type to the result of extracting
- // init["body"], with keepalive set to request’s keepalive.
- const [extractedBody, contentType] = extractBody(
- init.body,
- request.keepalive
- )
- initBody = extractedBody
-
- // 3, If Content-Type is non-null and this’s headers’s header list does
- // not contain `Content-Type`, then append `Content-Type`/Content-Type to
- // this’s headers.
- if (contentType && !getHeadersList(this.#headers).contains('content-type', true)) {
- this.#headers.append('content-type', contentType, true)
- }
- }
-
- // 37. Let inputOrInitBody be initBody if it is non-null; otherwise
- // inputBody.
- const inputOrInitBody = initBody ?? inputBody
-
- // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
- // null, then:
- if (inputOrInitBody != null && inputOrInitBody.source == null) {
- // 1. If initBody is non-null and init["duplex"] does not exist,
- // then throw a TypeError.
- if (initBody != null && init.duplex == null) {
- throw new TypeError('RequestInit: duplex option is required when sending a body.')
- }
-
- // 2. If this’s request’s mode is neither "same-origin" nor "cors",
- // then throw a TypeError.
- if (request.mode !== 'same-origin' && request.mode !== 'cors') {
- throw new TypeError(
- 'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
- )
- }
-
- // 3. Set this’s request’s use-CORS-preflight flag.
- request.useCORSPreflightFlag = true
- }
-
- // 39. Let finalBody be inputOrInitBody.
- let finalBody = inputOrInitBody
-
- // 40. If initBody is null and inputBody is non-null, then:
- if (initBody == null && inputBody != null) {
- // 1. If input is unusable, then throw a TypeError.
- if (bodyUnusable(input.#state)) {
- throw new TypeError(
- 'Cannot construct a Request with a Request object that has already been used.'
- )
- }
-
- // 2. Set finalBody to the result of creating a proxy for inputBody.
- // https://streams.spec.whatwg.org/#readablestream-create-a-proxy
- const identityTransform = new TransformStream()
- inputBody.stream.pipeThrough(identityTransform)
- finalBody = {
- source: inputBody.source,
- length: inputBody.length,
- stream: identityTransform.readable
- }
- }
-
- // 41. Set this’s request’s body to finalBody.
- this.#state.body = finalBody
- }
-
- // Returns request’s HTTP method, which is "GET" by default.
- get method () {
- webidl.brandCheck(this, Request)
-
- // The method getter steps are to return this’s request’s method.
- return this.#state.method
- }
-
- // Returns the URL of request as a string.
- get url () {
- webidl.brandCheck(this, Request)
-
- // The url getter steps are to return this’s request’s URL, serialized.
- return URLSerializer(this.#state.url)
- }
-
- // Returns a Headers object consisting of the headers associated with request.
- // Note that headers added in the network layer by the user agent will not
- // be accounted for in this object, e.g., the "Host" header.
- get headers () {
- webidl.brandCheck(this, Request)
-
- // The headers getter steps are to return this’s headers.
- return this.#headers
- }
-
- // Returns the kind of resource requested by request, e.g., "document"
- // or "script".
- get destination () {
- webidl.brandCheck(this, Request)
-
- // The destination getter are to return this’s request’s destination.
- return this.#state.destination
- }
-
- // Returns the referrer of request. Its value can be a same-origin URL if
- // explicitly set in init, the empty string to indicate no referrer, and
- // "about:client" when defaulting to the global’s default. This is used
- // during fetching to determine the value of the `Referer` header of the
- // request being made.
- get referrer () {
- webidl.brandCheck(this, Request)
-
- // 1. If this’s request’s referrer is "no-referrer", then return the
- // empty string.
- if (this.#state.referrer === 'no-referrer') {
- return ''
- }
-
- // 2. If this’s request’s referrer is "client", then return
- // "about:client".
- if (this.#state.referrer === 'client') {
- return 'about:client'
- }
-
- // Return this’s request’s referrer, serialized.
- return this.#state.referrer.toString()
- }
-
- // Returns the referrer policy associated with request.
- // This is used during fetching to compute the value of the request’s
- // referrer.
- get referrerPolicy () {
- webidl.brandCheck(this, Request)
-
- // The referrerPolicy getter steps are to return this’s request’s referrer policy.
- return this.#state.referrerPolicy
- }
-
- // Returns the mode associated with request, which is a string indicating
- // whether the request will use CORS, or will be restricted to same-origin
- // URLs.
- get mode () {
- webidl.brandCheck(this, Request)
-
- // The mode getter steps are to return this’s request’s mode.
- return this.#state.mode
- }
-
- // Returns the credentials mode associated with request,
- // which is a string indicating whether credentials will be sent with the
- // request always, never, or only when sent to a same-origin URL.
- get credentials () {
- webidl.brandCheck(this, Request)
-
- // The credentials getter steps are to return this’s request’s credentials mode.
- return this.#state.credentials
- }
-
- // Returns the cache mode associated with request,
- // which is a string indicating how the request will
- // interact with the browser’s cache when fetching.
- get cache () {
- webidl.brandCheck(this, Request)
-
- // The cache getter steps are to return this’s request’s cache mode.
- return this.#state.cache
- }
-
- // Returns the redirect mode associated with request,
- // which is a string indicating how redirects for the
- // request will be handled during fetching. A request
- // will follow redirects by default.
- get redirect () {
- webidl.brandCheck(this, Request)
-
- // The redirect getter steps are to return this’s request’s redirect mode.
- return this.#state.redirect
- }
-
- // Returns request’s subresource integrity metadata, which is a
- // cryptographic hash of the resource being fetched. Its value
- // consists of multiple hashes separated by whitespace. [SRI]
- get integrity () {
- webidl.brandCheck(this, Request)
-
- // The integrity getter steps are to return this’s request’s integrity
- // metadata.
- return this.#state.integrity
- }
-
- // Returns a boolean indicating whether or not request can outlive the
- // global in which it was created.
- get keepalive () {
- webidl.brandCheck(this, Request)
-
- // The keepalive getter steps are to return this’s request’s keepalive.
- return this.#state.keepalive
- }
-
- // Returns a boolean indicating whether or not request is for a reload
- // navigation.
- get isReloadNavigation () {
- webidl.brandCheck(this, Request)
-
- // The isReloadNavigation getter steps are to return true if this’s
- // request’s reload-navigation flag is set; otherwise false.
- return this.#state.reloadNavigation
- }
-
- // Returns a boolean indicating whether or not request is for a history
- // navigation (a.k.a. back-forward navigation).
- get isHistoryNavigation () {
- webidl.brandCheck(this, Request)
-
- // The isHistoryNavigation getter steps are to return true if this’s request’s
- // history-navigation flag is set; otherwise false.
- return this.#state.historyNavigation
- }
-
- // Returns the signal associated with request, which is an AbortSignal
- // object indicating whether or not request has been aborted, and its
- // abort event handler.
- get signal () {
- webidl.brandCheck(this, Request)
-
- // The signal getter steps are to return this’s signal.
- return this.#signal
- }
-
- get body () {
- webidl.brandCheck(this, Request)
-
- return this.#state.body ? this.#state.body.stream : null
- }
-
- get bodyUsed () {
- webidl.brandCheck(this, Request)
-
- return !!this.#state.body && util.isDisturbed(this.#state.body.stream)
- }
-
- get duplex () {
- webidl.brandCheck(this, Request)
-
- return 'half'
- }
-
- // Returns a clone of request.
- clone () {
- webidl.brandCheck(this, Request)
-
- // 1. If this is unusable, then throw a TypeError.
- if (bodyUnusable(this.#state)) {
- throw new TypeError('unusable')
- }
-
- // 2. Let clonedRequest be the result of cloning this’s request.
- const clonedRequest = cloneRequest(this.#state)
-
- // 3. Let clonedRequestObject be the result of creating a Request object,
- // given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
- // 4. Make clonedRequestObject’s signal follow this’s signal.
- const ac = new AbortController()
- if (this.signal.aborted) {
- ac.abort(this.signal.reason)
- } else {
- let list = dependentControllerMap.get(this.signal)
- if (list === undefined) {
- list = new Set()
- dependentControllerMap.set(this.signal, list)
- }
- const acRef = new WeakRef(ac)
- list.add(acRef)
- util.addAbortListener(
- ac.signal,
- buildAbort(acRef)
- )
- }
-
- // 4. Return clonedRequestObject.
- return fromInnerRequest(clonedRequest, this.#dispatcher, ac.signal, getHeadersGuard(this.#headers))
- }
-
- [nodeUtil.inspect.custom] (depth, options) {
- if (options.depth === null) {
- options.depth = 2
- }
-
- options.colors ??= true
-
- const properties = {
- method: this.method,
- url: this.url,
- headers: this.headers,
- destination: this.destination,
- referrer: this.referrer,
- referrerPolicy: this.referrerPolicy,
- mode: this.mode,
- credentials: this.credentials,
- cache: this.cache,
- redirect: this.redirect,
- integrity: this.integrity,
- keepalive: this.keepalive,
- isReloadNavigation: this.isReloadNavigation,
- isHistoryNavigation: this.isHistoryNavigation,
- signal: this.signal
- }
-
- return `Request ${nodeUtil.formatWithOptions(options, properties)}`
- }
-
- /**
- * @param {Request} request
- * @param {AbortSignal} newSignal
- */
- static setRequestSignal (request, newSignal) {
- request.#signal = newSignal
- return request
- }
-
- /**
- * @param {Request} request
- */
- static getRequestDispatcher (request) {
- return request.#dispatcher
- }
-
- /**
- * @param {Request} request
- * @param {import('../../dispatcher/dispatcher')} newDispatcher
- */
- static setRequestDispatcher (request, newDispatcher) {
- request.#dispatcher = newDispatcher
- }
-
- /**
- * @param {Request} request
- * @param {Headers} newHeaders
- */
- static setRequestHeaders (request, newHeaders) {
- request.#headers = newHeaders
- }
-
- /**
- * @param {Request} request
- */
- static getRequestState (request) {
- return request.#state
- }
-
- /**
- * @param {Request} request
- * @param {any} newState
- */
- static setRequestState (request, newState) {
- request.#state = newState
- }
-}
-
-const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState } = Request
-Reflect.deleteProperty(Request, 'setRequestSignal')
-Reflect.deleteProperty(Request, 'getRequestDispatcher')
-Reflect.deleteProperty(Request, 'setRequestDispatcher')
-Reflect.deleteProperty(Request, 'setRequestHeaders')
-Reflect.deleteProperty(Request, 'getRequestState')
-Reflect.deleteProperty(Request, 'setRequestState')
-
-mixinBody(Request, getRequestState)
-
-// https://fetch.spec.whatwg.org/#requests
-function makeRequest (init) {
- return {
- method: init.method ?? 'GET',
- localURLsOnly: init.localURLsOnly ?? false,
- unsafeRequest: init.unsafeRequest ?? false,
- body: init.body ?? null,
- client: init.client ?? null,
- reservedClient: init.reservedClient ?? null,
- replacesClientId: init.replacesClientId ?? '',
- window: init.window ?? 'client',
- keepalive: init.keepalive ?? false,
- serviceWorkers: init.serviceWorkers ?? 'all',
- initiator: init.initiator ?? '',
- destination: init.destination ?? '',
- priority: init.priority ?? null,
- origin: init.origin ?? 'client',
- policyContainer: init.policyContainer ?? 'client',
- referrer: init.referrer ?? 'client',
- referrerPolicy: init.referrerPolicy ?? '',
- mode: init.mode ?? 'no-cors',
- useCORSPreflightFlag: init.useCORSPreflightFlag ?? false,
- credentials: init.credentials ?? 'same-origin',
- useCredentials: init.useCredentials ?? false,
- cache: init.cache ?? 'default',
- redirect: init.redirect ?? 'follow',
- integrity: init.integrity ?? '',
- cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '',
- parserMetadata: init.parserMetadata ?? '',
- reloadNavigation: init.reloadNavigation ?? false,
- historyNavigation: init.historyNavigation ?? false,
- userActivation: init.userActivation ?? false,
- taintedOrigin: init.taintedOrigin ?? false,
- redirectCount: init.redirectCount ?? 0,
- responseTainting: init.responseTainting ?? 'basic',
- preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false,
- done: init.done ?? false,
- timingAllowFailed: init.timingAllowFailed ?? false,
- useURLCredentials: init.useURLCredentials ?? undefined,
- traversableForUserPrompts: init.traversableForUserPrompts ?? 'client',
- urlList: init.urlList,
- url: init.urlList[0],
- headersList: init.headersList
- ? new HeadersList(init.headersList)
- : new HeadersList()
- }
-}
-
-// https://fetch.spec.whatwg.org/#concept-request-clone
-function cloneRequest (request) {
- // To clone a request request, run these steps:
-
- // 1. Let newRequest be a copy of request, except for its body.
- const newRequest = makeRequest({ ...request, body: null })
-
- // 2. If request’s body is non-null, set newRequest’s body to the
- // result of cloning request’s body.
- if (request.body != null) {
- newRequest.body = cloneBody(request.body)
- }
-
- // 3. Return newRequest.
- return newRequest
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#request-create
- * @param {any} innerRequest
- * @param {import('../../dispatcher/agent')} dispatcher
- * @param {AbortSignal} signal
- * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
- * @returns {Request}
- */
-function fromInnerRequest (innerRequest, dispatcher, signal, guard) {
- const request = new Request(kConstruct)
- setRequestState(request, innerRequest)
- setRequestDispatcher(request, dispatcher)
- setRequestSignal(request, signal)
- const headers = new Headers(kConstruct)
- setRequestHeaders(request, headers)
- setHeadersList(headers, innerRequest.headersList)
- setHeadersGuard(headers, guard)
- return request
-}
-
-Object.defineProperties(Request.prototype, {
- method: kEnumerableProperty,
- url: kEnumerableProperty,
- headers: kEnumerableProperty,
- redirect: kEnumerableProperty,
- clone: kEnumerableProperty,
- signal: kEnumerableProperty,
- duplex: kEnumerableProperty,
- destination: kEnumerableProperty,
- body: kEnumerableProperty,
- bodyUsed: kEnumerableProperty,
- isHistoryNavigation: kEnumerableProperty,
- isReloadNavigation: kEnumerableProperty,
- keepalive: kEnumerableProperty,
- integrity: kEnumerableProperty,
- cache: kEnumerableProperty,
- credentials: kEnumerableProperty,
- attribute: kEnumerableProperty,
- referrerPolicy: kEnumerableProperty,
- referrer: kEnumerableProperty,
- mode: kEnumerableProperty,
- [Symbol.toStringTag]: {
- value: 'Request',
- configurable: true
- }
-})
-
-webidl.is.Request = webidl.util.MakeTypeAssertion(Request)
-
-/**
- * @param {*} V
- * @returns {import('../../../types/fetch').Request|string}
- *
- * @see https://fetch.spec.whatwg.org/#requestinfo
- */
-webidl.converters.RequestInfo = function (V) {
- if (typeof V === 'string') {
- return webidl.converters.USVString(V)
- }
-
- if (webidl.is.Request(V)) {
- return V
- }
-
- return webidl.converters.USVString(V)
-}
-
-/**
- * @param {*} V
- * @returns {import('../../../types/fetch').RequestInit}
- * @see https://fetch.spec.whatwg.org/#requestinit
- */
-webidl.converters.RequestInit = webidl.dictionaryConverter([
- {
- key: 'method',
- converter: webidl.converters.ByteString
- },
- {
- key: 'headers',
- converter: webidl.converters.HeadersInit
- },
- {
- key: 'body',
- converter: webidl.nullableConverter(
- webidl.converters.BodyInit
- )
- },
- {
- key: 'referrer',
- converter: webidl.converters.USVString
- },
- {
- key: 'referrerPolicy',
- converter: webidl.converters.DOMString,
- // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
- allowedValues: referrerPolicy
- },
- {
- key: 'mode',
- converter: webidl.converters.DOMString,
- // https://fetch.spec.whatwg.org/#concept-request-mode
- allowedValues: requestMode
- },
- {
- key: 'credentials',
- converter: webidl.converters.DOMString,
- // https://fetch.spec.whatwg.org/#requestcredentials
- allowedValues: requestCredentials
- },
- {
- key: 'cache',
- converter: webidl.converters.DOMString,
- // https://fetch.spec.whatwg.org/#requestcache
- allowedValues: requestCache
- },
- {
- key: 'redirect',
- converter: webidl.converters.DOMString,
- // https://fetch.spec.whatwg.org/#requestredirect
- allowedValues: requestRedirect
- },
- {
- key: 'integrity',
- converter: webidl.converters.DOMString
- },
- {
- key: 'keepalive',
- converter: webidl.converters.boolean
- },
- {
- key: 'signal',
- converter: webidl.nullableConverter(
- (signal) => webidl.converters.AbortSignal(
- signal,
- 'RequestInit',
- 'signal'
- )
- )
- },
- {
- key: 'window',
- converter: webidl.converters.any
- },
- {
- key: 'duplex',
- converter: webidl.converters.DOMString,
- allowedValues: requestDuplex
- },
- {
- key: 'dispatcher', // undici specific option
- converter: webidl.converters.any
- },
- {
- key: 'priority',
- converter: webidl.converters.DOMString,
- allowedValues: ['high', 'low', 'auto'],
- defaultValue: () => 'auto'
- }
-])
-
-module.exports = {
- Request,
- makeRequest,
- fromInnerRequest,
- cloneRequest,
- getRequestDispatcher,
- getRequestState
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/response.js b/vanilla/node_modules/undici/lib/web/fetch/response.js
deleted file mode 100644
index ffb7ce1..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/response.js
+++ /dev/null
@@ -1,641 +0,0 @@
-'use strict'
-
-const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
-const { extractBody, cloneBody, mixinBody, streamRegistry, bodyUnusable } = require('./body')
-const util = require('../../core/util')
-const nodeUtil = require('node:util')
-const { kEnumerableProperty } = util
-const {
- isValidReasonPhrase,
- isCancelled,
- isAborted,
- isErrorLike,
- environmentSettingsObject: relevantRealm
-} = require('./util')
-const {
- redirectStatusSet,
- nullBodyStatus
-} = require('./constants')
-const { webidl } = require('../webidl')
-const { URLSerializer } = require('./data-url')
-const { kConstruct } = require('../../core/symbols')
-const assert = require('node:assert')
-const { isomorphicEncode, serializeJavascriptValueToJSONString } = require('../infra')
-
-const textEncoder = new TextEncoder('utf-8')
-
-// https://fetch.spec.whatwg.org/#response-class
-class Response {
- /** @type {Headers} */
- #headers
-
- #state
-
- // Creates network error Response.
- static error () {
- // The static error() method steps are to return the result of creating a
- // Response object, given a new network error, "immutable", and this’s
- // relevant Realm.
- const responseObject = fromInnerResponse(makeNetworkError(), 'immutable')
-
- return responseObject
- }
-
- // https://fetch.spec.whatwg.org/#dom-response-json
- static json (data, init = undefined) {
- webidl.argumentLengthCheck(arguments, 1, 'Response.json')
-
- if (init !== null) {
- init = webidl.converters.ResponseInit(init)
- }
-
- // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
- const bytes = textEncoder.encode(
- serializeJavascriptValueToJSONString(data)
- )
-
- // 2. Let body be the result of extracting bytes.
- const body = extractBody(bytes)
-
- // 3. Let responseObject be the result of creating a Response object, given a new response,
- // "response", and this’s relevant Realm.
- const responseObject = fromInnerResponse(makeResponse({}), 'response')
-
- // 4. Perform initialize a response given responseObject, init, and (body, "application/json").
- initializeResponse(responseObject, init, { body: body[0], type: 'application/json' })
-
- // 5. Return responseObject.
- return responseObject
- }
-
- // Creates a redirect Response that redirects to url with status status.
- static redirect (url, status = 302) {
- webidl.argumentLengthCheck(arguments, 1, 'Response.redirect')
-
- url = webidl.converters.USVString(url)
- status = webidl.converters['unsigned short'](status)
-
- // 1. Let parsedURL be the result of parsing url with current settings
- // object’s API base URL.
- // 2. If parsedURL is failure, then throw a TypeError.
- // TODO: base-URL?
- let parsedURL
- try {
- parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl)
- } catch (err) {
- throw new TypeError(`Failed to parse URL from ${url}`, { cause: err })
- }
-
- // 3. If status is not a redirect status, then throw a RangeError.
- if (!redirectStatusSet.has(status)) {
- throw new RangeError(`Invalid status code ${status}`)
- }
-
- // 4. Let responseObject be the result of creating a Response object,
- // given a new response, "immutable", and this’s relevant Realm.
- const responseObject = fromInnerResponse(makeResponse({}), 'immutable')
-
- // 5. Set responseObject’s response’s status to status.
- responseObject.#state.status = status
-
- // 6. Let value be parsedURL, serialized and isomorphic encoded.
- const value = isomorphicEncode(URLSerializer(parsedURL))
-
- // 7. Append `Location`/value to responseObject’s response’s header list.
- responseObject.#state.headersList.append('location', value, true)
-
- // 8. Return responseObject.
- return responseObject
- }
-
- // https://fetch.spec.whatwg.org/#dom-response
- constructor (body = null, init = undefined) {
- webidl.util.markAsUncloneable(this)
-
- if (body === kConstruct) {
- return
- }
-
- if (body !== null) {
- body = webidl.converters.BodyInit(body, 'Response', 'body')
- }
-
- init = webidl.converters.ResponseInit(init)
-
- // 1. Set this’s response to a new response.
- this.#state = makeResponse({})
-
- // 2. Set this’s headers to a new Headers object with this’s relevant
- // Realm, whose header list is this’s response’s header list and guard
- // is "response".
- this.#headers = new Headers(kConstruct)
- setHeadersGuard(this.#headers, 'response')
- setHeadersList(this.#headers, this.#state.headersList)
-
- // 3. Let bodyWithType be null.
- let bodyWithType = null
-
- // 4. If body is non-null, then set bodyWithType to the result of extracting body.
- if (body != null) {
- const [extractedBody, type] = extractBody(body)
- bodyWithType = { body: extractedBody, type }
- }
-
- // 5. Perform initialize a response given this, init, and bodyWithType.
- initializeResponse(this, init, bodyWithType)
- }
-
- // Returns response’s type, e.g., "cors".
- get type () {
- webidl.brandCheck(this, Response)
-
- // The type getter steps are to return this’s response’s type.
- return this.#state.type
- }
-
- // Returns response’s URL, if it has one; otherwise the empty string.
- get url () {
- webidl.brandCheck(this, Response)
-
- const urlList = this.#state.urlList
-
- // The url getter steps are to return the empty string if this’s
- // response’s URL is null; otherwise this’s response’s URL,
- // serialized with exclude fragment set to true.
- const url = urlList[urlList.length - 1] ?? null
-
- if (url === null) {
- return ''
- }
-
- return URLSerializer(url, true)
- }
-
- // Returns whether response was obtained through a redirect.
- get redirected () {
- webidl.brandCheck(this, Response)
-
- // The redirected getter steps are to return true if this’s response’s URL
- // list has more than one item; otherwise false.
- return this.#state.urlList.length > 1
- }
-
- // Returns response’s status.
- get status () {
- webidl.brandCheck(this, Response)
-
- // The status getter steps are to return this’s response’s status.
- return this.#state.status
- }
-
- // Returns whether response’s status is an ok status.
- get ok () {
- webidl.brandCheck(this, Response)
-
- // The ok getter steps are to return true if this’s response’s status is an
- // ok status; otherwise false.
- return this.#state.status >= 200 && this.#state.status <= 299
- }
-
- // Returns response’s status message.
- get statusText () {
- webidl.brandCheck(this, Response)
-
- // The statusText getter steps are to return this’s response’s status
- // message.
- return this.#state.statusText
- }
-
- // Returns response’s headers as Headers.
- get headers () {
- webidl.brandCheck(this, Response)
-
- // The headers getter steps are to return this’s headers.
- return this.#headers
- }
-
- get body () {
- webidl.brandCheck(this, Response)
-
- return this.#state.body ? this.#state.body.stream : null
- }
-
- get bodyUsed () {
- webidl.brandCheck(this, Response)
-
- return !!this.#state.body && util.isDisturbed(this.#state.body.stream)
- }
-
- // Returns a clone of response.
- clone () {
- webidl.brandCheck(this, Response)
-
- // 1. If this is unusable, then throw a TypeError.
- if (bodyUnusable(this.#state)) {
- throw webidl.errors.exception({
- header: 'Response.clone',
- message: 'Body has already been consumed.'
- })
- }
-
- // 2. Let clonedResponse be the result of cloning this’s response.
- const clonedResponse = cloneResponse(this.#state)
-
- // Note: To re-register because of a new stream.
- // Don't set finalizers other than for fetch responses.
- if (this.#state.urlList.length !== 0 && this.#state.body?.stream) {
- streamRegistry.register(this, new WeakRef(this.#state.body.stream))
- }
-
- // 3. Return the result of creating a Response object, given
- // clonedResponse, this’s headers’s guard, and this’s relevant Realm.
- return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers))
- }
-
- [nodeUtil.inspect.custom] (depth, options) {
- if (options.depth === null) {
- options.depth = 2
- }
-
- options.colors ??= true
-
- const properties = {
- status: this.status,
- statusText: this.statusText,
- headers: this.headers,
- body: this.body,
- bodyUsed: this.bodyUsed,
- ok: this.ok,
- redirected: this.redirected,
- type: this.type,
- url: this.url
- }
-
- return `Response ${nodeUtil.formatWithOptions(options, properties)}`
- }
-
- /**
- * @param {Response} response
- */
- static getResponseHeaders (response) {
- return response.#headers
- }
-
- /**
- * @param {Response} response
- * @param {Headers} newHeaders
- */
- static setResponseHeaders (response, newHeaders) {
- response.#headers = newHeaders
- }
-
- /**
- * @param {Response} response
- */
- static getResponseState (response) {
- return response.#state
- }
-
- /**
- * @param {Response} response
- * @param {any} newState
- */
- static setResponseState (response, newState) {
- response.#state = newState
- }
-}
-
-const { getResponseHeaders, setResponseHeaders, getResponseState, setResponseState } = Response
-Reflect.deleteProperty(Response, 'getResponseHeaders')
-Reflect.deleteProperty(Response, 'setResponseHeaders')
-Reflect.deleteProperty(Response, 'getResponseState')
-Reflect.deleteProperty(Response, 'setResponseState')
-
-mixinBody(Response, getResponseState)
-
-Object.defineProperties(Response.prototype, {
- type: kEnumerableProperty,
- url: kEnumerableProperty,
- status: kEnumerableProperty,
- ok: kEnumerableProperty,
- redirected: kEnumerableProperty,
- statusText: kEnumerableProperty,
- headers: kEnumerableProperty,
- clone: kEnumerableProperty,
- body: kEnumerableProperty,
- bodyUsed: kEnumerableProperty,
- [Symbol.toStringTag]: {
- value: 'Response',
- configurable: true
- }
-})
-
-Object.defineProperties(Response, {
- json: kEnumerableProperty,
- redirect: kEnumerableProperty,
- error: kEnumerableProperty
-})
-
-// https://fetch.spec.whatwg.org/#concept-response-clone
-function cloneResponse (response) {
- // To clone a response response, run these steps:
-
- // 1. If response is a filtered response, then return a new identical
- // filtered response whose internal response is a clone of response’s
- // internal response.
- if (response.internalResponse) {
- return filterResponse(
- cloneResponse(response.internalResponse),
- response.type
- )
- }
-
- // 2. Let newResponse be a copy of response, except for its body.
- const newResponse = makeResponse({ ...response, body: null })
-
- // 3. If response’s body is non-null, then set newResponse’s body to the
- // result of cloning response’s body.
- if (response.body != null) {
- newResponse.body = cloneBody(response.body)
- }
-
- // 4. Return newResponse.
- return newResponse
-}
-
-function makeResponse (init) {
- return {
- aborted: false,
- rangeRequested: false,
- timingAllowPassed: false,
- requestIncludesCredentials: false,
- type: 'default',
- status: 200,
- timingInfo: null,
- cacheState: '',
- statusText: '',
- ...init,
- headersList: init?.headersList
- ? new HeadersList(init?.headersList)
- : new HeadersList(),
- urlList: init?.urlList ? [...init.urlList] : []
- }
-}
-
-function makeNetworkError (reason) {
- const isError = isErrorLike(reason)
- return makeResponse({
- type: 'error',
- status: 0,
- error: isError
- ? reason
- : new Error(reason ? String(reason) : reason),
- aborted: reason && reason.name === 'AbortError'
- })
-}
-
-// @see https://fetch.spec.whatwg.org/#concept-network-error
-function isNetworkError (response) {
- return (
- // A network error is a response whose type is "error",
- response.type === 'error' &&
- // status is 0
- response.status === 0
- )
-}
-
-function makeFilteredResponse (response, state) {
- state = {
- internalResponse: response,
- ...state
- }
-
- return new Proxy(response, {
- get (target, p) {
- return p in state ? state[p] : target[p]
- },
- set (target, p, value) {
- assert(!(p in state))
- target[p] = value
- return true
- }
- })
-}
-
-// https://fetch.spec.whatwg.org/#concept-filtered-response
-function filterResponse (response, type) {
- // Set response to the following filtered response with response as its
- // internal response, depending on request’s response tainting:
- if (type === 'basic') {
- // A basic filtered response is a filtered response whose type is "basic"
- // and header list excludes any headers in internal response’s header list
- // whose name is a forbidden response-header name.
-
- // Note: undici does not implement forbidden response-header names
- return makeFilteredResponse(response, {
- type: 'basic',
- headersList: response.headersList
- })
- } else if (type === 'cors') {
- // A CORS filtered response is a filtered response whose type is "cors"
- // and header list excludes any headers in internal response’s header
- // list whose name is not a CORS-safelisted response-header name, given
- // internal response’s CORS-exposed header-name list.
-
- // Note: undici does not implement CORS-safelisted response-header names
- return makeFilteredResponse(response, {
- type: 'cors',
- headersList: response.headersList
- })
- } else if (type === 'opaque') {
- // An opaque filtered response is a filtered response whose type is
- // "opaque", URL list is the empty list, status is 0, status message
- // is the empty byte sequence, header list is empty, and body is null.
-
- return makeFilteredResponse(response, {
- type: 'opaque',
- urlList: [],
- status: 0,
- statusText: '',
- body: null
- })
- } else if (type === 'opaqueredirect') {
- // An opaque-redirect filtered response is a filtered response whose type
- // is "opaqueredirect", status is 0, status message is the empty byte
- // sequence, header list is empty, and body is null.
-
- return makeFilteredResponse(response, {
- type: 'opaqueredirect',
- status: 0,
- statusText: '',
- headersList: [],
- body: null
- })
- } else {
- assert(false)
- }
-}
-
-// https://fetch.spec.whatwg.org/#appropriate-network-error
-function makeAppropriateNetworkError (fetchParams, err = null) {
- // 1. Assert: fetchParams is canceled.
- assert(isCancelled(fetchParams))
-
- // 2. Return an aborted network error if fetchParams is aborted;
- // otherwise return a network error.
- return isAborted(fetchParams)
- ? makeNetworkError(Object.assign(new DOMException('The operation was aborted.', 'AbortError'), { cause: err }))
- : makeNetworkError(Object.assign(new DOMException('Request was cancelled.'), { cause: err }))
-}
-
-// https://whatpr.org/fetch/1392.html#initialize-a-response
-function initializeResponse (response, init, body) {
- // 1. If init["status"] is not in the range 200 to 599, inclusive, then
- // throw a RangeError.
- if (init.status !== null && (init.status < 200 || init.status > 599)) {
- throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.')
- }
-
- // 2. If init["statusText"] does not match the reason-phrase token production,
- // then throw a TypeError.
- if ('statusText' in init && init.statusText != null) {
- // See, https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2:
- // reason-phrase = *( HTAB / SP / VCHAR / obs-text )
- if (!isValidReasonPhrase(String(init.statusText))) {
- throw new TypeError('Invalid statusText')
- }
- }
-
- // 3. Set response’s response’s status to init["status"].
- if ('status' in init && init.status != null) {
- getResponseState(response).status = init.status
- }
-
- // 4. Set response’s response’s status message to init["statusText"].
- if ('statusText' in init && init.statusText != null) {
- getResponseState(response).statusText = init.statusText
- }
-
- // 5. If init["headers"] exists, then fill response’s headers with init["headers"].
- if ('headers' in init && init.headers != null) {
- fill(getResponseHeaders(response), init.headers)
- }
-
- // 6. If body was given, then:
- if (body) {
- // 1. If response's status is a null body status, then throw a TypeError.
- if (nullBodyStatus.includes(response.status)) {
- throw webidl.errors.exception({
- header: 'Response constructor',
- message: `Invalid response status code ${response.status}`
- })
- }
-
- // 2. Set response's body to body's body.
- getResponseState(response).body = body.body
-
- // 3. If body's type is non-null and response's header list does not contain
- // `Content-Type`, then append (`Content-Type`, body's type) to response's header list.
- if (body.type != null && !getResponseState(response).headersList.contains('content-type', true)) {
- getResponseState(response).headersList.append('content-type', body.type, true)
- }
- }
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#response-create
- * @param {any} innerResponse
- * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
- * @returns {Response}
- */
-function fromInnerResponse (innerResponse, guard) {
- const response = new Response(kConstruct)
- setResponseState(response, innerResponse)
- const headers = new Headers(kConstruct)
- setResponseHeaders(response, headers)
- setHeadersList(headers, innerResponse.headersList)
- setHeadersGuard(headers, guard)
-
- // Note: If innerResponse's urlList contains a URL, it is a fetch response.
- if (innerResponse.urlList.length !== 0 && innerResponse.body?.stream) {
- // If the target (response) is reclaimed, the cleanup callback may be called at some point with
- // the held value provided for it (innerResponse.body.stream). The held value can be any value:
- // a primitive or an object, even undefined. If the held value is an object, the registry keeps
- // a strong reference to it (so it can pass it to the cleanup callback later). Reworded from
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
- streamRegistry.register(response, new WeakRef(innerResponse.body.stream))
- }
-
- return response
-}
-
-// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
-webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) {
- if (typeof V === 'string') {
- return webidl.converters.USVString(V, prefix, name)
- }
-
- if (webidl.is.Blob(V)) {
- return V
- }
-
- if (webidl.is.BufferSource(V)) {
- return V
- }
-
- if (webidl.is.FormData(V)) {
- return V
- }
-
- if (webidl.is.URLSearchParams(V)) {
- return V
- }
-
- return webidl.converters.DOMString(V, prefix, name)
-}
-
-// https://fetch.spec.whatwg.org/#bodyinit
-webidl.converters.BodyInit = function (V, prefix, argument) {
- if (webidl.is.ReadableStream(V)) {
- return V
- }
-
- // Note: the spec doesn't include async iterables,
- // this is an undici extension.
- if (V?.[Symbol.asyncIterator]) {
- return V
- }
-
- return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument)
-}
-
-webidl.converters.ResponseInit = webidl.dictionaryConverter([
- {
- key: 'status',
- converter: webidl.converters['unsigned short'],
- defaultValue: () => 200
- },
- {
- key: 'statusText',
- converter: webidl.converters.ByteString,
- defaultValue: () => ''
- },
- {
- key: 'headers',
- converter: webidl.converters.HeadersInit
- }
-])
-
-webidl.is.Response = webidl.util.MakeTypeAssertion(Response)
-
-module.exports = {
- isNetworkError,
- makeNetworkError,
- makeResponse,
- makeAppropriateNetworkError,
- filterResponse,
- Response,
- cloneResponse,
- fromInnerResponse,
- getResponseState
-}
diff --git a/vanilla/node_modules/undici/lib/web/fetch/util.js b/vanilla/node_modules/undici/lib/web/fetch/util.js
deleted file mode 100644
index fe63cb3..0000000
--- a/vanilla/node_modules/undici/lib/web/fetch/util.js
+++ /dev/null
@@ -1,1520 +0,0 @@
-'use strict'
-
-const { Transform } = require('node:stream')
-const zlib = require('node:zlib')
-const { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require('./constants')
-const { getGlobalOrigin } = require('./global')
-const { collectAnHTTPQuotedString, parseMIMEType } = require('./data-url')
-const { performance } = require('node:perf_hooks')
-const { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
-const assert = require('node:assert')
-const { isUint8Array } = require('node:util/types')
-const { webidl } = require('../webidl')
-const { isomorphicEncode, collectASequenceOfCodePoints, removeChars } = require('../infra')
-
-function responseURL (response) {
- // https://fetch.spec.whatwg.org/#responses
- // A response has an associated URL. It is a pointer to the last URL
- // in response’s URL list and null if response’s URL list is empty.
- const urlList = response.urlList
- const length = urlList.length
- return length === 0 ? null : urlList[length - 1].toString()
-}
-
-// https://fetch.spec.whatwg.org/#concept-response-location-url
-function responseLocationURL (response, requestFragment) {
- // 1. If response’s status is not a redirect status, then return null.
- if (!redirectStatusSet.has(response.status)) {
- return null
- }
-
- // 2. Let location be the result of extracting header list values given
- // `Location` and response’s header list.
- let location = response.headersList.get('location', true)
-
- // 3. If location is a header value, then set location to the result of
- // parsing location with response’s URL.
- if (location !== null && isValidHeaderValue(location)) {
- if (!isValidEncodedURL(location)) {
- // Some websites respond location header in UTF-8 form without encoding them as ASCII
- // and major browsers redirect them to correctly UTF-8 encoded addresses.
- // Here, we handle that behavior in the same way.
- location = normalizeBinaryStringToUtf8(location)
- }
- location = new URL(location, responseURL(response))
- }
-
- // 4. If location is a URL whose fragment is null, then set location’s
- // fragment to requestFragment.
- if (location && !location.hash) {
- location.hash = requestFragment
- }
-
- // 5. Return location.
- return location
-}
-
-/**
- * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2
- * @param {string} url
- * @returns {boolean}
- */
-function isValidEncodedURL (url) {
- for (let i = 0; i < url.length; ++i) {
- const code = url.charCodeAt(i)
-
- if (
- code > 0x7E || // Non-US-ASCII + DEL
- code < 0x20 // Control characters NUL - US
- ) {
- return false
- }
- }
- return true
-}
-
-/**
- * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it.
- * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well.
- * @param {string} value
- * @returns {string}
- */
-function normalizeBinaryStringToUtf8 (value) {
- return Buffer.from(value, 'binary').toString('utf8')
-}
-
-/** @returns {URL} */
-function requestCurrentURL (request) {
- return request.urlList[request.urlList.length - 1]
-}
-
-function requestBadPort (request) {
- // 1. Let url be request’s current URL.
- const url = requestCurrentURL(request)
-
- // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
- // then return blocked.
- if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
- return 'blocked'
- }
-
- // 3. Return allowed.
- return 'allowed'
-}
-
-function isErrorLike (object) {
- return object instanceof Error || (
- object?.constructor?.name === 'Error' ||
- object?.constructor?.name === 'DOMException'
- )
-}
-
-// Check whether |statusText| is a ByteString and
-// matches the Reason-Phrase token production.
-// RFC 2616: https://tools.ietf.org/html/rfc2616
-// RFC 7230: https://tools.ietf.org/html/rfc7230
-// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
-// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
-function isValidReasonPhrase (statusText) {
- for (let i = 0; i < statusText.length; ++i) {
- const c = statusText.charCodeAt(i)
- if (
- !(
- (
- c === 0x09 || // HTAB
- (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
- (c >= 0x80 && c <= 0xff)
- ) // obs-text
- )
- ) {
- return false
- }
- }
- return true
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#header-name
- * @param {string} potentialValue
- */
-const isValidHeaderName = isValidHTTPToken
-
-/**
- * @see https://fetch.spec.whatwg.org/#header-value
- * @param {string} potentialValue
- */
-function isValidHeaderValue (potentialValue) {
- // - Has no leading or trailing HTTP tab or space bytes.
- // - Contains no 0x00 (NUL) or HTTP newline bytes.
- return (
- potentialValue[0] === '\t' ||
- potentialValue[0] === ' ' ||
- potentialValue[potentialValue.length - 1] === '\t' ||
- potentialValue[potentialValue.length - 1] === ' ' ||
- potentialValue.includes('\n') ||
- potentialValue.includes('\r') ||
- potentialValue.includes('\0')
- ) === false
-}
-
-/**
- * Parse a referrer policy from a Referrer-Policy header
- * @see https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header
- */
-function parseReferrerPolicy (actualResponse) {
- // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
- const policyHeader = (actualResponse.headersList.get('referrer-policy', true) ?? '').split(',')
-
- // 2. Let policy be the empty string.
- let policy = ''
-
- // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
-
- // Note: As the referrer-policy can contain multiple policies
- // separated by comma, we need to loop through all of them
- // and pick the first valid one.
- // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
- if (policyHeader.length) {
- // The right-most policy takes precedence.
- // The left-most policy is the fallback.
- for (let i = policyHeader.length; i !== 0; i--) {
- const token = policyHeader[i - 1].trim()
- if (referrerPolicyTokens.has(token)) {
- policy = token
- break
- }
- }
- }
-
- // 4. Return policy.
- return policy
-}
-
-/**
- * Given a request request and a response actualResponse, this algorithm
- * updates request’s referrer policy according to the Referrer-Policy
- * header (if any) in actualResponse.
- * @see https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
- * @param {import('./request').Request} request
- * @param {import('./response').Response} actualResponse
- */
-function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
- // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
- // from a Referrer-Policy header on actualResponse.
- const policy = parseReferrerPolicy(actualResponse)
-
- // 2. If policy is not the empty string, then set request’s referrer policy to policy.
- if (policy !== '') {
- request.referrerPolicy = policy
- }
-}
-
-// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
-function crossOriginResourcePolicyCheck () {
- // TODO
- return 'allowed'
-}
-
-// https://fetch.spec.whatwg.org/#concept-cors-check
-function corsCheck () {
- // TODO
- return 'success'
-}
-
-// https://fetch.spec.whatwg.org/#concept-tao-check
-function TAOCheck () {
- // TODO
- return 'success'
-}
-
-function appendFetchMetadata (httpRequest) {
- // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
- // TODO
-
- // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
-
- // 1. Assert: r’s url is a potentially trustworthy URL.
- // TODO
-
- // 2. Let header be a Structured Header whose value is a token.
- let header = null
-
- // 3. Set header’s value to r’s mode.
- header = httpRequest.mode
-
- // 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
- httpRequest.headersList.set('sec-fetch-mode', header, true)
-
- // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
- // TODO
-
- // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
- // TODO
-}
-
-// https://fetch.spec.whatwg.org/#append-a-request-origin-header
-function appendRequestOriginHeader (request) {
- // 1. Let serializedOrigin be the result of byte-serializing a request origin
- // with request.
- // TODO: implement "byte-serializing a request origin"
- let serializedOrigin = request.origin
-
- // - "'client' is changed to an origin during fetching."
- // This doesn't happen in undici (in most cases) because undici, by default,
- // has no concept of origin.
- // - request.origin can also be set to request.client.origin (client being
- // an environment settings object), which is undefined without using
- // setGlobalOrigin.
- if (serializedOrigin === 'client' || serializedOrigin === undefined) {
- return
- }
-
- // 2. If request’s response tainting is "cors" or request’s mode is "websocket",
- // then append (`Origin`, serializedOrigin) to request’s header list.
- // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
- if (request.responseTainting === 'cors' || request.mode === 'websocket') {
- request.headersList.append('origin', serializedOrigin, true)
- } else if (request.method !== 'GET' && request.method !== 'HEAD') {
- // 1. Switch on request’s referrer policy:
- switch (request.referrerPolicy) {
- case 'no-referrer':
- // Set serializedOrigin to `null`.
- serializedOrigin = null
- break
- case 'no-referrer-when-downgrade':
- case 'strict-origin':
- case 'strict-origin-when-cross-origin':
- // If request’s origin is a tuple origin, its scheme is "https", and
- // request’s current URL’s scheme is not "https", then set
- // serializedOrigin to `null`.
- if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) {
- serializedOrigin = null
- }
- break
- case 'same-origin':
- // If request’s origin is not same origin with request’s current URL’s
- // origin, then set serializedOrigin to `null`.
- if (!sameOrigin(request, requestCurrentURL(request))) {
- serializedOrigin = null
- }
- break
- default:
- // Do nothing.
- }
-
- // 2. Append (`Origin`, serializedOrigin) to request’s header list.
- request.headersList.append('origin', serializedOrigin, true)
- }
-}
-
-// https://w3c.github.io/hr-time/#dfn-coarsen-time
-function coarsenTime (timestamp, crossOriginIsolatedCapability) {
- // TODO
- return timestamp
-}
-
-// https://fetch.spec.whatwg.org/#clamp-and-coarsen-connection-timing-info
-function clampAndCoarsenConnectionTimingInfo (connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
- if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
- return {
- domainLookupStartTime: defaultStartTime,
- domainLookupEndTime: defaultStartTime,
- connectionStartTime: defaultStartTime,
- connectionEndTime: defaultStartTime,
- secureConnectionStartTime: defaultStartTime,
- ALPNNegotiatedProtocol: connectionTimingInfo?.ALPNNegotiatedProtocol
- }
- }
-
- return {
- domainLookupStartTime: coarsenTime(connectionTimingInfo.domainLookupStartTime, crossOriginIsolatedCapability),
- domainLookupEndTime: coarsenTime(connectionTimingInfo.domainLookupEndTime, crossOriginIsolatedCapability),
- connectionStartTime: coarsenTime(connectionTimingInfo.connectionStartTime, crossOriginIsolatedCapability),
- connectionEndTime: coarsenTime(connectionTimingInfo.connectionEndTime, crossOriginIsolatedCapability),
- secureConnectionStartTime: coarsenTime(connectionTimingInfo.secureConnectionStartTime, crossOriginIsolatedCapability),
- ALPNNegotiatedProtocol: connectionTimingInfo.ALPNNegotiatedProtocol
- }
-}
-
-// https://w3c.github.io/hr-time/#dfn-coarsened-shared-current-time
-function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
- return coarsenTime(performance.now(), crossOriginIsolatedCapability)
-}
-
-// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
-function createOpaqueTimingInfo (timingInfo) {
- return {
- startTime: timingInfo.startTime ?? 0,
- redirectStartTime: 0,
- redirectEndTime: 0,
- postRedirectStartTime: timingInfo.startTime ?? 0,
- finalServiceWorkerStartTime: 0,
- finalNetworkResponseStartTime: 0,
- finalNetworkRequestStartTime: 0,
- endTime: 0,
- encodedBodySize: 0,
- decodedBodySize: 0,
- finalConnectionTimingInfo: null
- }
-}
-
-// https://html.spec.whatwg.org/multipage/origin.html#policy-container
-function makePolicyContainer () {
- // Note: the fetch spec doesn't make use of embedder policy or CSP list
- return {
- referrerPolicy: 'strict-origin-when-cross-origin'
- }
-}
-
-// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
-function clonePolicyContainer (policyContainer) {
- return {
- referrerPolicy: policyContainer.referrerPolicy
- }
-}
-
-/**
- * Determine request’s Referrer
- *
- * @see https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
- */
-function determineRequestsReferrer (request) {
- // Given a request request, we can determine the correct referrer information
- // to send by examining its referrer policy as detailed in the following
- // steps, which return either no referrer or a URL:
-
- // 1. Let policy be request's referrer policy.
- const policy = request.referrerPolicy
-
- // Note: policy cannot (shouldn't) be null or an empty string.
- assert(policy)
-
- // 2. Let environment be request’s client.
-
- let referrerSource = null
-
- // 3. Switch on request’s referrer:
-
- // "client"
- if (request.referrer === 'client') {
- // Note: node isn't a browser and doesn't implement document/iframes,
- // so we bypass this step and replace it with our own.
-
- const globalOrigin = getGlobalOrigin()
-
- if (!globalOrigin || globalOrigin.origin === 'null') {
- return 'no-referrer'
- }
-
- // Note: we need to clone it as it's mutated
- referrerSource = new URL(globalOrigin)
- // a URL
- } else if (webidl.is.URL(request.referrer)) {
- // Let referrerSource be request’s referrer.
- referrerSource = request.referrer
- }
-
- // 4. Let request’s referrerURL be the result of stripping referrerSource for
- // use as a referrer.
- let referrerURL = stripURLForReferrer(referrerSource)
-
- // 5. Let referrerOrigin be the result of stripping referrerSource for use as
- // a referrer, with the origin-only flag set to true.
- const referrerOrigin = stripURLForReferrer(referrerSource, true)
-
- // 6. If the result of serializing referrerURL is a string whose length is
- // greater than 4096, set referrerURL to referrerOrigin.
- if (referrerURL.toString().length > 4096) {
- referrerURL = referrerOrigin
- }
-
- // 7. The user agent MAY alter referrerURL or referrerOrigin at this point
- // to enforce arbitrary policy considerations in the interests of minimizing
- // data leakage. For example, the user agent could strip the URL down to an
- // origin, modify its host, replace it with an empty string, etc.
-
- // 8. Execute the switch statements corresponding to the value of policy:
- switch (policy) {
- case 'no-referrer':
- // Return no referrer
- return 'no-referrer'
- case 'origin':
- // Return referrerOrigin
- if (referrerOrigin != null) {
- return referrerOrigin
- }
- return stripURLForReferrer(referrerSource, true)
- case 'unsafe-url':
- // Return referrerURL.
- return referrerURL
- case 'strict-origin': {
- const currentURL = requestCurrentURL(request)
-
- // 1. If referrerURL is a potentially trustworthy URL and request’s
- // current URL is not a potentially trustworthy URL, then return no
- // referrer.
- if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
- return 'no-referrer'
- }
- // 2. Return referrerOrigin
- return referrerOrigin
- }
- case 'strict-origin-when-cross-origin': {
- const currentURL = requestCurrentURL(request)
-
- // 1. If the origin of referrerURL and the origin of request’s current
- // URL are the same, then return referrerURL.
- if (sameOrigin(referrerURL, currentURL)) {
- return referrerURL
- }
-
- // 2. If referrerURL is a potentially trustworthy URL and request’s
- // current URL is not a potentially trustworthy URL, then return no
- // referrer.
- if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
- return 'no-referrer'
- }
-
- // 3. Return referrerOrigin.
- return referrerOrigin
- }
- case 'same-origin':
- // 1. If the origin of referrerURL and the origin of request’s current
- // URL are the same, then return referrerURL.
- if (sameOrigin(request, referrerURL)) {
- return referrerURL
- }
- // 2. Return no referrer.
- return 'no-referrer'
- case 'origin-when-cross-origin':
- // 1. If the origin of referrerURL and the origin of request’s current
- // URL are the same, then return referrerURL.
- if (sameOrigin(request, referrerURL)) {
- return referrerURL
- }
- // 2. Return referrerOrigin.
- return referrerOrigin
- case 'no-referrer-when-downgrade': {
- const currentURL = requestCurrentURL(request)
-
- // 1. If referrerURL is a potentially trustworthy URL and request’s
- // current URL is not a potentially trustworthy URL, then return no
- // referrer.
- if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
- return 'no-referrer'
- }
- // 2. Return referrerURL.
- return referrerURL
- }
- }
-}
-
-/**
- * Certain portions of URLs must not be included when sending a URL as the
- * value of a `Referer` header: a URLs fragment, username, and password
- * components must be stripped from the URL before it’s sent out. This
- * algorithm accepts a origin-only flag, which defaults to false. If set to
- * true, the algorithm will additionally remove the URL’s path and query
- * components, leaving only the scheme, host, and port.
- *
- * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
- * @param {URL} url
- * @param {boolean} [originOnly=false]
- */
-function stripURLForReferrer (url, originOnly = false) {
- // 1. Assert: url is a URL.
- assert(webidl.is.URL(url))
-
- // Note: Create a new URL instance to avoid mutating the original URL.
- url = new URL(url)
-
- // 2. If url’s scheme is a local scheme, then return no referrer.
- if (urlIsLocal(url)) {
- return 'no-referrer'
- }
-
- // 3. Set url’s username to the empty string.
- url.username = ''
-
- // 4. Set url’s password to the empty string.
- url.password = ''
-
- // 5. Set url’s fragment to null.
- url.hash = ''
-
- // 6. If the origin-only flag is true, then:
- if (originOnly === true) {
- // 1. Set url’s path to « the empty string ».
- url.pathname = ''
-
- // 2. Set url’s query to null.
- url.search = ''
- }
-
- // 7. Return url.
- return url
-}
-
-const isPotentialleTrustworthyIPv4 = RegExp.prototype.test
- .bind(/^127\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/)
-
-const isPotentiallyTrustworthyIPv6 = RegExp.prototype.test
- .bind(/^(?:(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:|::)0{0,3}1$/)
-
-/**
- * Check if host matches one of the CIDR notations 127.0.0.0/8 or ::1/128.
- *
- * @param {string} origin
- * @returns {boolean}
- */
-function isOriginIPPotentiallyTrustworthy (origin) {
- // IPv6
- if (origin.includes(':')) {
- // Remove brackets from IPv6 addresses
- if (origin[0] === '[' && origin[origin.length - 1] === ']') {
- origin = origin.slice(1, -1)
- }
- return isPotentiallyTrustworthyIPv6(origin)
- }
-
- // IPv4
- return isPotentialleTrustworthyIPv4(origin)
-}
-
-/**
- * A potentially trustworthy origin is one which a user agent can generally
- * trust as delivering data securely.
- *
- * Return value `true` means `Potentially Trustworthy`.
- * Return value `false` means `Not Trustworthy`.
- *
- * @see https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
- * @param {string} origin
- * @returns {boolean}
- */
-function isOriginPotentiallyTrustworthy (origin) {
- // 1. If origin is an opaque origin, return "Not Trustworthy".
- if (origin == null || origin === 'null') {
- return false
- }
-
- // 2. Assert: origin is a tuple origin.
- origin = new URL(origin)
-
- // 3. If origin’s scheme is either "https" or "wss",
- // return "Potentially Trustworthy".
- if (origin.protocol === 'https:' || origin.protocol === 'wss:') {
- return true
- }
-
- // 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or
- // ::1/128 [RFC4632], return "Potentially Trustworthy".
- if (isOriginIPPotentiallyTrustworthy(origin.hostname)) {
- return true
- }
-
- // 5. If the user agent conforms to the name resolution rules in
- // [let-localhost-be-localhost] and one of the following is true:
-
- // origin’s host is "localhost" or "localhost."
- if (origin.hostname === 'localhost' || origin.hostname === 'localhost.') {
- return true
- }
-
- // origin’s host ends with ".localhost" or ".localhost."
- if (origin.hostname.endsWith('.localhost') || origin.hostname.endsWith('.localhost.')) {
- return true
- }
-
- // 6. If origin’s scheme is "file", return "Potentially Trustworthy".
- if (origin.protocol === 'file:') {
- return true
- }
-
- // 7. If origin’s scheme component is one which the user agent considers to
- // be authenticated, return "Potentially Trustworthy".
-
- // 8. If origin has been configured as a trustworthy origin, return
- // "Potentially Trustworthy".
-
- // 9. Return "Not Trustworthy".
- return false
-}
-
-/**
- * A potentially trustworthy URL is one which either inherits context from its
- * creator (about:blank, about:srcdoc, data) or one whose origin is a
- * potentially trustworthy origin.
- *
- * Return value `true` means `Potentially Trustworthy`.
- * Return value `false` means `Not Trustworthy`.
- *
- * @see https://www.w3.org/TR/secure-contexts/#is-url-trustworthy
- * @param {URL} url
- * @returns {boolean}
- */
-function isURLPotentiallyTrustworthy (url) {
- // Given a URL record (url), the following algorithm returns "Potentially
- // Trustworthy" or "Not Trustworthy" as appropriate:
- if (!webidl.is.URL(url)) {
- return false
- }
-
- // 1. If url is "about:blank" or "about:srcdoc",
- // return "Potentially Trustworthy".
- if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
- return true
- }
-
- // 2. If url’s scheme is "data", return "Potentially Trustworthy".
- if (url.protocol === 'data:') return true
-
- // Note: The origin of blob: URLs is the origin of the context in which they
- // were created. Therefore, blobs created in a trustworthy origin will
- // themselves be potentially trustworthy.
- if (url.protocol === 'blob:') return true
-
- // 3. Return the result of executing § 3.1 Is origin potentially trustworthy?
- // on url’s origin.
- return isOriginPotentiallyTrustworthy(url.origin)
-}
-
-// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
-function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
- // TODO
-}
-
-/**
- * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
- * @param {URL} A
- * @param {URL} B
- */
-function sameOrigin (A, B) {
- // 1. If A and B are the same opaque origin, then return true.
- if (A.origin === B.origin && A.origin === 'null') {
- return true
- }
-
- // 2. If A and B are both tuple origins and their schemes,
- // hosts, and port are identical, then return true.
- if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
- return true
- }
-
- // 3. Return false.
- return false
-}
-
-function isAborted (fetchParams) {
- return fetchParams.controller.state === 'aborted'
-}
-
-function isCancelled (fetchParams) {
- return fetchParams.controller.state === 'aborted' ||
- fetchParams.controller.state === 'terminated'
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-method-normalize
- * @param {string} method
- */
-function normalizeMethod (method) {
- return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
-}
-
-// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
-const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
-
-/**
- * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
- * @param {string} name name of the instance
- * @param {((target: any) => any)} kInternalIterator
- * @param {string | number} [keyIndex]
- * @param {string | number} [valueIndex]
- */
-function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) {
- class FastIterableIterator {
- /** @type {any} */
- #target
- /** @type {'key' | 'value' | 'key+value'} */
- #kind
- /** @type {number} */
- #index
-
- /**
- * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object
- * @param {unknown} target
- * @param {'key' | 'value' | 'key+value'} kind
- */
- constructor (target, kind) {
- this.#target = target
- this.#kind = kind
- this.#index = 0
- }
-
- next () {
- // 1. Let interface be the interface for which the iterator prototype object exists.
- // 2. Let thisValue be the this value.
- // 3. Let object be ? ToObject(thisValue).
- // 4. If object is a platform object, then perform a security
- // check, passing:
- // 5. If object is not a default iterator object for interface,
- // then throw a TypeError.
- if (typeof this !== 'object' || this === null || !(#target in this)) {
- throw new TypeError(
- `'next' called on an object that does not implement interface ${name} Iterator.`
- )
- }
-
- // 6. Let index be object’s index.
- // 7. Let kind be object’s kind.
- // 8. Let values be object’s target's value pairs to iterate over.
- const index = this.#index
- const values = kInternalIterator(this.#target)
-
- // 9. Let len be the length of values.
- const len = values.length
-
- // 10. If index is greater than or equal to len, then return
- // CreateIterResultObject(undefined, true).
- if (index >= len) {
- return {
- value: undefined,
- done: true
- }
- }
-
- // 11. Let pair be the entry in values at index index.
- const { [keyIndex]: key, [valueIndex]: value } = values[index]
-
- // 12. Set object’s index to index + 1.
- this.#index = index + 1
-
- // 13. Return the iterator result for pair and kind.
-
- // https://webidl.spec.whatwg.org/#iterator-result
-
- // 1. Let result be a value determined by the value of kind:
- let result
- switch (this.#kind) {
- case 'key':
- // 1. Let idlKey be pair’s key.
- // 2. Let key be the result of converting idlKey to an
- // ECMAScript value.
- // 3. result is key.
- result = key
- break
- case 'value':
- // 1. Let idlValue be pair’s value.
- // 2. Let value be the result of converting idlValue to
- // an ECMAScript value.
- // 3. result is value.
- result = value
- break
- case 'key+value':
- // 1. Let idlKey be pair’s key.
- // 2. Let idlValue be pair’s value.
- // 3. Let key be the result of converting idlKey to an
- // ECMAScript value.
- // 4. Let value be the result of converting idlValue to
- // an ECMAScript value.
- // 5. Let array be ! ArrayCreate(2).
- // 6. Call ! CreateDataProperty(array, "0", key).
- // 7. Call ! CreateDataProperty(array, "1", value).
- // 8. result is array.
- result = [key, value]
- break
- }
-
- // 2. Return CreateIterResultObject(result, false).
- return {
- value: result,
- done: false
- }
- }
- }
-
- // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
- // @ts-ignore
- delete FastIterableIterator.prototype.constructor
-
- Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype)
-
- Object.defineProperties(FastIterableIterator.prototype, {
- [Symbol.toStringTag]: {
- writable: false,
- enumerable: false,
- configurable: true,
- value: `${name} Iterator`
- },
- next: { writable: true, enumerable: true, configurable: true }
- })
-
- /**
- * @param {unknown} target
- * @param {'key' | 'value' | 'key+value'} kind
- * @returns {IterableIterator<any>}
- */
- return function (target, kind) {
- return new FastIterableIterator(target, kind)
- }
-}
-
-/**
- * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
- * @param {string} name name of the instance
- * @param {any} object class
- * @param {(target: any) => any} kInternalIterator
- * @param {string | number} [keyIndex]
- * @param {string | number} [valueIndex]
- */
-function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) {
- const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex)
-
- const properties = {
- keys: {
- writable: true,
- enumerable: true,
- configurable: true,
- value: function keys () {
- webidl.brandCheck(this, object)
- return makeIterator(this, 'key')
- }
- },
- values: {
- writable: true,
- enumerable: true,
- configurable: true,
- value: function values () {
- webidl.brandCheck(this, object)
- return makeIterator(this, 'value')
- }
- },
- entries: {
- writable: true,
- enumerable: true,
- configurable: true,
- value: function entries () {
- webidl.brandCheck(this, object)
- return makeIterator(this, 'key+value')
- }
- },
- forEach: {
- writable: true,
- enumerable: true,
- configurable: true,
- value: function forEach (callbackfn, thisArg = globalThis) {
- webidl.brandCheck(this, object)
- webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`)
- if (typeof callbackfn !== 'function') {
- throw new TypeError(
- `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.`
- )
- }
- for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) {
- callbackfn.call(thisArg, value, key, this)
- }
- }
- }
- }
-
- return Object.defineProperties(object.prototype, {
- ...properties,
- [Symbol.iterator]: {
- writable: true,
- enumerable: false,
- configurable: true,
- value: properties.entries.value
- }
- })
-}
-
-/**
- * @param {import('./body').ExtractBodyResult} body
- * @param {(bytes: Uint8Array) => void} processBody
- * @param {(error: Error) => void} processBodyError
- * @returns {void}
- *
- * @see https://fetch.spec.whatwg.org/#body-fully-read
- */
-function fullyReadBody (body, processBody, processBodyError) {
- // 1. If taskDestination is null, then set taskDestination to
- // the result of starting a new parallel queue.
-
- // 2. Let successSteps given a byte sequence bytes be to queue a
- // fetch task to run processBody given bytes, with taskDestination.
- const successSteps = processBody
-
- // 3. Let errorSteps be to queue a fetch task to run processBodyError,
- // with taskDestination.
- const errorSteps = processBodyError
-
- try {
- // 4. Let reader be the result of getting a reader for body’s stream.
- // If that threw an exception, then run errorSteps with that
- // exception and return.
- const reader = body.stream.getReader()
-
- // 5. Read all bytes from reader, given successSteps and errorSteps.
- readAllBytes(reader, successSteps, errorSteps)
- } catch (e) {
- errorSteps(e)
- }
-}
-
-/**
- * @param {ReadableStreamController<Uint8Array>} controller
- */
-function readableStreamClose (controller) {
- try {
- controller.close()
- controller.byobRequest?.respond(0)
- } catch (err) {
- // TODO: add comment explaining why this error occurs.
- if (!err.message.includes('Controller is already closed') && !err.message.includes('ReadableStream is already closed')) {
- throw err
- }
- }
-}
-
-/**
- * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
- * @see https://streams.spec.whatwg.org/#read-loop
- * @param {ReadableStream<Uint8Array<ArrayBuffer>>} reader
- * @param {(bytes: Uint8Array) => void} successSteps
- * @param {(error: Error) => void} failureSteps
- * @returns {Promise<void>}
- */
-async function readAllBytes (reader, successSteps, failureSteps) {
- try {
- const bytes = []
- let byteLength = 0
-
- do {
- const { done, value: chunk } = await reader.read()
-
- if (done) {
- // 1. Call successSteps with bytes.
- successSteps(Buffer.concat(bytes, byteLength))
- return
- }
-
- // 1. If chunk is not a Uint8Array object, call failureSteps
- // with a TypeError and abort these steps.
- if (!isUint8Array(chunk)) {
- failureSteps(new TypeError('Received non-Uint8Array chunk'))
- return
- }
-
- // 2. Append the bytes represented by chunk to bytes.
- bytes.push(chunk)
- byteLength += chunk.length
-
- // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
- } while (true)
- } catch (e) {
- // 1. Call failureSteps with e.
- failureSteps(e)
- }
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#is-local
- * @param {URL} url
- * @returns {boolean}
- */
-function urlIsLocal (url) {
- assert('protocol' in url) // ensure it's a url object
-
- const protocol = url.protocol
-
- // A URL is local if its scheme is a local scheme.
- // A local scheme is "about", "blob", or "data".
- return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
-}
-
-/**
- * @param {string|URL} url
- * @returns {boolean}
- */
-function urlHasHttpsScheme (url) {
- return (
- (
- typeof url === 'string' &&
- url[5] === ':' &&
- url[0] === 'h' &&
- url[1] === 't' &&
- url[2] === 't' &&
- url[3] === 'p' &&
- url[4] === 's'
- ) ||
- url.protocol === 'https:'
- )
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#http-scheme
- * @param {URL} url
- */
-function urlIsHttpHttpsScheme (url) {
- assert('protocol' in url) // ensure it's a url object
-
- const protocol = url.protocol
-
- return protocol === 'http:' || protocol === 'https:'
-}
-
-/**
- * @typedef {Object} RangeHeaderValue
- * @property {number|null} rangeStartValue
- * @property {number|null} rangeEndValue
- */
-
-/**
- * @see https://fetch.spec.whatwg.org/#simple-range-header-value
- * @param {string} value
- * @param {boolean} allowWhitespace
- * @return {RangeHeaderValue|'failure'}
- */
-function simpleRangeHeaderValue (value, allowWhitespace) {
- // 1. Let data be the isomorphic decoding of value.
- // Note: isomorphic decoding takes a sequence of bytes (ie. a Uint8Array) and turns it into a string,
- // nothing more. We obviously don't need to do that if value is a string already.
- const data = value
-
- // 2. If data does not start with "bytes", then return failure.
- if (!data.startsWith('bytes')) {
- return 'failure'
- }
-
- // 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
- const position = { position: 5 }
-
- // 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
- // from data given position.
- if (allowWhitespace) {
- collectASequenceOfCodePoints(
- (char) => char === '\t' || char === ' ',
- data,
- position
- )
- }
-
- // 5. If the code point at position within data is not U+003D (=), then return failure.
- if (data.charCodeAt(position.position) !== 0x3D) {
- return 'failure'
- }
-
- // 6. Advance position by 1.
- position.position++
-
- // 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from
- // data given position.
- if (allowWhitespace) {
- collectASequenceOfCodePoints(
- (char) => char === '\t' || char === ' ',
- data,
- position
- )
- }
-
- // 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits,
- // from data given position.
- const rangeStart = collectASequenceOfCodePoints(
- (char) => {
- const code = char.charCodeAt(0)
-
- return code >= 0x30 && code <= 0x39
- },
- data,
- position
- )
-
- // 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the
- // empty string; otherwise null.
- const rangeStartValue = rangeStart.length ? Number(rangeStart) : null
-
- // 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
- // from data given position.
- if (allowWhitespace) {
- collectASequenceOfCodePoints(
- (char) => char === '\t' || char === ' ',
- data,
- position
- )
- }
-
- // 11. If the code point at position within data is not U+002D (-), then return failure.
- if (data.charCodeAt(position.position) !== 0x2D) {
- return 'failure'
- }
-
- // 12. Advance position by 1.
- position.position++
-
- // 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab
- // or space, from data given position.
- // Note from Khafra: its the same step as in #8 again lol
- if (allowWhitespace) {
- collectASequenceOfCodePoints(
- (char) => char === '\t' || char === ' ',
- data,
- position
- )
- }
-
- // 14. Let rangeEnd be the result of collecting a sequence of code points that are
- // ASCII digits, from data given position.
- // Note from Khafra: you wouldn't guess it, but this is also the same step as #8
- const rangeEnd = collectASequenceOfCodePoints(
- (char) => {
- const code = char.charCodeAt(0)
-
- return code >= 0x30 && code <= 0x39
- },
- data,
- position
- )
-
- // 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd
- // is not the empty string; otherwise null.
- // Note from Khafra: THE SAME STEP, AGAIN!!!
- // Note: why interpret as a decimal if we only collect ascii digits?
- const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null
-
- // 16. If position is not past the end of data, then return failure.
- if (position.position < data.length) {
- return 'failure'
- }
-
- // 17. If rangeEndValue and rangeStartValue are null, then return failure.
- if (rangeEndValue === null && rangeStartValue === null) {
- return 'failure'
- }
-
- // 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is
- // greater than rangeEndValue, then return failure.
- // Note: ... when can they not be numbers?
- if (rangeStartValue > rangeEndValue) {
- return 'failure'
- }
-
- // 19. Return (rangeStartValue, rangeEndValue).
- return { rangeStartValue, rangeEndValue }
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#build-a-content-range
- * @param {number} rangeStart
- * @param {number} rangeEnd
- * @param {number} fullLength
- */
-function buildContentRange (rangeStart, rangeEnd, fullLength) {
- // 1. Let contentRange be `bytes `.
- let contentRange = 'bytes '
-
- // 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
- contentRange += isomorphicEncode(`${rangeStart}`)
-
- // 3. Append 0x2D (-) to contentRange.
- contentRange += '-'
-
- // 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
- contentRange += isomorphicEncode(`${rangeEnd}`)
-
- // 5. Append 0x2F (/) to contentRange.
- contentRange += '/'
-
- // 6. Append fullLength, serialized and isomorphic encoded to contentRange.
- contentRange += isomorphicEncode(`${fullLength}`)
-
- // 7. Return contentRange.
- return contentRange
-}
-
-// A Stream, which pipes the response to zlib.createInflate() or
-// zlib.createInflateRaw() depending on the first byte of the Buffer.
-// If the lower byte of the first byte is 0x08, then the stream is
-// interpreted as a zlib stream, otherwise it's interpreted as a
-// raw deflate stream.
-class InflateStream extends Transform {
- #zlibOptions
-
- /** @param {zlib.ZlibOptions} [zlibOptions] */
- constructor (zlibOptions) {
- super()
- this.#zlibOptions = zlibOptions
- }
-
- _transform (chunk, encoding, callback) {
- if (!this._inflateStream) {
- if (chunk.length === 0) {
- callback()
- return
- }
- this._inflateStream = (chunk[0] & 0x0F) === 0x08
- ? zlib.createInflate(this.#zlibOptions)
- : zlib.createInflateRaw(this.#zlibOptions)
-
- this._inflateStream.on('data', this.push.bind(this))
- this._inflateStream.on('end', () => this.push(null))
- this._inflateStream.on('error', (err) => this.destroy(err))
- }
-
- this._inflateStream.write(chunk, encoding, callback)
- }
-
- _final (callback) {
- if (this._inflateStream) {
- this._inflateStream.end()
- this._inflateStream = null
- }
- callback()
- }
-}
-
-/**
- * @param {zlib.ZlibOptions} [zlibOptions]
- * @returns {InflateStream}
- */
-function createInflate (zlibOptions) {
- return new InflateStream(zlibOptions)
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
- * @param {import('./headers').HeadersList} headers
- */
-function extractMimeType (headers) {
- // 1. Let charset be null.
- let charset = null
-
- // 2. Let essence be null.
- let essence = null
-
- // 3. Let mimeType be null.
- let mimeType = null
-
- // 4. Let values be the result of getting, decoding, and splitting `Content-Type` from headers.
- const values = getDecodeSplit('content-type', headers)
-
- // 5. If values is null, then return failure.
- if (values === null) {
- return 'failure'
- }
-
- // 6. For each value of values:
- for (const value of values) {
- // 6.1. Let temporaryMimeType be the result of parsing value.
- const temporaryMimeType = parseMIMEType(value)
-
- // 6.2. If temporaryMimeType is failure or its essence is "*/*", then continue.
- if (temporaryMimeType === 'failure' || temporaryMimeType.essence === '*/*') {
- continue
- }
-
- // 6.3. Set mimeType to temporaryMimeType.
- mimeType = temporaryMimeType
-
- // 6.4. If mimeType’s essence is not essence, then:
- if (mimeType.essence !== essence) {
- // 6.4.1. Set charset to null.
- charset = null
-
- // 6.4.2. If mimeType’s parameters["charset"] exists, then set charset to
- // mimeType’s parameters["charset"].
- if (mimeType.parameters.has('charset')) {
- charset = mimeType.parameters.get('charset')
- }
-
- // 6.4.3. Set essence to mimeType’s essence.
- essence = mimeType.essence
- } else if (!mimeType.parameters.has('charset') && charset !== null) {
- // 6.5. Otherwise, if mimeType’s parameters["charset"] does not exist, and
- // charset is non-null, set mimeType’s parameters["charset"] to charset.
- mimeType.parameters.set('charset', charset)
- }
- }
-
- // 7. If mimeType is null, then return failure.
- if (mimeType == null) {
- return 'failure'
- }
-
- // 8. Return mimeType.
- return mimeType
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#header-value-get-decode-and-split
- * @param {string|null} value
- */
-function gettingDecodingSplitting (value) {
- // 1. Let input be the result of isomorphic decoding value.
- const input = value
-
- // 2. Let position be a position variable for input, initially pointing at the start of input.
- const position = { position: 0 }
-
- // 3. Let values be a list of strings, initially empty.
- const values = []
-
- // 4. Let temporaryValue be the empty string.
- let temporaryValue = ''
-
- // 5. While position is not past the end of input:
- while (position.position < input.length) {
- // 5.1. Append the result of collecting a sequence of code points that are not U+0022 (")
- // or U+002C (,) from input, given position, to temporaryValue.
- temporaryValue += collectASequenceOfCodePoints(
- (char) => char !== '"' && char !== ',',
- input,
- position
- )
-
- // 5.2. If position is not past the end of input, then:
- if (position.position < input.length) {
- // 5.2.1. If the code point at position within input is U+0022 ("), then:
- if (input.charCodeAt(position.position) === 0x22) {
- // 5.2.1.1. Append the result of collecting an HTTP quoted string from input, given position, to temporaryValue.
- temporaryValue += collectAnHTTPQuotedString(
- input,
- position
- )
-
- // 5.2.1.2. If position is not past the end of input, then continue.
- if (position.position < input.length) {
- continue
- }
- } else {
- // 5.2.2. Otherwise:
-
- // 5.2.2.1. Assert: the code point at position within input is U+002C (,).
- assert(input.charCodeAt(position.position) === 0x2C)
-
- // 5.2.2.2. Advance position by 1.
- position.position++
- }
- }
-
- // 5.3. Remove all HTTP tab or space from the start and end of temporaryValue.
- temporaryValue = removeChars(temporaryValue, true, true, (char) => char === 0x9 || char === 0x20)
-
- // 5.4. Append temporaryValue to values.
- values.push(temporaryValue)
-
- // 5.6. Set temporaryValue to the empty string.
- temporaryValue = ''
- }
-
- // 6. Return values.
- return values
-}
-
-/**
- * @see https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
- * @param {string} name lowercase header name
- * @param {import('./headers').HeadersList} list
- */
-function getDecodeSplit (name, list) {
- // 1. Let value be the result of getting name from list.
- const value = list.get(name, true)
-
- // 2. If value is null, then return null.
- if (value === null) {
- return null
- }
-
- // 3. Return the result of getting, decoding, and splitting value.
- return gettingDecodingSplitting(value)
-}
-
-function hasAuthenticationEntry (request) {
- return false
-}
-
-/**
- * @see https://url.spec.whatwg.org/#include-credentials
- * @param {URL} url
- */
-function includesCredentials (url) {
- // A URL includes credentials if its username or password is not the empty string.
- return !!(url.username || url.password)
-}
-
-/**
- * @see https://html.spec.whatwg.org/multipage/document-sequences.html#traversable-navigable
- * @param {object|string} navigable
- */
-function isTraversableNavigable (navigable) {
- // TODO
- return true
-}
-
-class EnvironmentSettingsObjectBase {
- get baseUrl () {
- return getGlobalOrigin()
- }
-
- get origin () {
- return this.baseUrl?.origin
- }
-
- policyContainer = makePolicyContainer()
-}
-
-class EnvironmentSettingsObject {
- settingsObject = new EnvironmentSettingsObjectBase()
-}
-
-const environmentSettingsObject = new EnvironmentSettingsObject()
-
-module.exports = {
- isAborted,
- isCancelled,
- isValidEncodedURL,
- ReadableStreamFrom,
- tryUpgradeRequestToAPotentiallyTrustworthyURL,
- clampAndCoarsenConnectionTimingInfo,
- coarsenedSharedCurrentTime,
- determineRequestsReferrer,
- makePolicyContainer,
- clonePolicyContainer,
- appendFetchMetadata,
- appendRequestOriginHeader,
- TAOCheck,
- corsCheck,
- crossOriginResourcePolicyCheck,
- createOpaqueTimingInfo,
- setRequestReferrerPolicyOnRedirect,
- isValidHTTPToken,
- requestBadPort,
- requestCurrentURL,
- responseURL,
- responseLocationURL,
- isURLPotentiallyTrustworthy,
- isValidReasonPhrase,
- sameOrigin,
- normalizeMethod,
- iteratorMixin,
- createIterator,
- isValidHeaderName,
- isValidHeaderValue,
- isErrorLike,
- fullyReadBody,
- readableStreamClose,
- urlIsLocal,
- urlHasHttpsScheme,
- urlIsHttpHttpsScheme,
- readAllBytes,
- simpleRangeHeaderValue,
- buildContentRange,
- createInflate,
- extractMimeType,
- getDecodeSplit,
- environmentSettingsObject,
- isOriginIPPotentiallyTrustworthy,
- hasAuthenticationEntry,
- includesCredentials,
- isTraversableNavigable
-}