fix
This commit is contained in:
21
book/node_modules/undici/lib/web/fetch/LICENSE
generated
vendored
Normal file
21
book/node_modules/undici/lib/web/fetch/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
530
book/node_modules/undici/lib/web/fetch/body.js
generated
vendored
Normal file
530
book/node_modules/undici/lib/web/fetch/body.js
generated
vendored
Normal file
@ -0,0 +1,530 @@
|
||||
'use strict'
|
||||
|
||||
const util = require('../../core/util')
|
||||
const {
|
||||
ReadableStreamFrom,
|
||||
isBlobLike,
|
||||
isReadableStreamLike,
|
||||
readableStreamClose,
|
||||
createDeferredPromise,
|
||||
fullyReadBody,
|
||||
extractMimeType,
|
||||
utf8DecodeBytes
|
||||
} = require('./util')
|
||||
const { FormData } = require('./formdata')
|
||||
const { kState } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { Blob } = require('node:buffer')
|
||||
const assert = require('node:assert')
|
||||
const { isErrored, isDisturbed } = require('node:stream')
|
||||
const { isArrayBuffer } = require('node:util/types')
|
||||
const { serializeAMimeType } = require('./data-url')
|
||||
const { multipartFormDataParser } = require('./formdata-parser')
|
||||
let random
|
||||
|
||||
try {
|
||||
const crypto = require('node:crypto')
|
||||
random = (max) => crypto.randomInt(0, max)
|
||||
} catch {
|
||||
random = (max) => Math.floor(Math.random(max))
|
||||
}
|
||||
|
||||
const textEncoder = new TextEncoder()
|
||||
function noop () {}
|
||||
|
||||
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
|
||||
let streamRegistry
|
||||
|
||||
if (hasFinalizationRegistry) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||||
function extractBody (object, keepalive = false) {
|
||||
// 1. Let stream be null.
|
||||
let stream = null
|
||||
|
||||
// 2. If object is a ReadableStream object, then set stream to object.
|
||||
if (object instanceof ReadableStream) {
|
||||
stream = object
|
||||
} else if (isBlobLike(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({
|
||||
async pull (controller) {
|
||||
const buffer = typeof source === 'string' ? textEncoder.encode(source) : source
|
||||
|
||||
if (buffer.byteLength) {
|
||||
controller.enqueue(buffer)
|
||||
}
|
||||
|
||||
queueMicrotask(() => readableStreamClose(controller))
|
||||
},
|
||||
start () {},
|
||||
type: 'bytes'
|
||||
})
|
||||
}
|
||||
|
||||
// 5. Assert: stream is a ReadableStream object.
|
||||
assert(isReadableStreamLike(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 (object instanceof URLSearchParams) {
|
||||
// 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 (isArrayBuffer(object)) {
|
||||
// BufferSource/ArrayBuffer
|
||||
|
||||
// Set source to a copy of the bytes held by object.
|
||||
source = new Uint8Array(object.slice())
|
||||
} else if (ArrayBuffer.isView(object)) {
|
||||
// BufferSource/ArrayBufferView
|
||||
|
||||
// Set source to a copy of the bytes held by object.
|
||||
source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
|
||||
} else if (util.isFormDataLike(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 escape = (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="${escape(normalizeLinefeeds(name))}"` +
|
||||
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
||||
blobParts.push(chunk)
|
||||
length += chunk.byteLength
|
||||
} else {
|
||||
const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
(value.name ? `; filename="${escape(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chunk = textEncoder.encode(`--${boundary}--`)
|
||||
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 (isBlobLike(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 =
|
||||
object instanceof ReadableStream ? 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' || util.isBuffer(source)) {
|
||||
length = Buffer.byteLength(source)
|
||||
}
|
||||
|
||||
// 12. If action is non-null, then run these steps in in parallel:
|
||||
if (action != null) {
|
||||
// Run action.
|
||||
let iterator
|
||||
stream = new ReadableStream({
|
||||
async start () {
|
||||
iterator = action(object)[Symbol.asyncIterator]()
|
||||
},
|
||||
async pull (controller) {
|
||||
const { value, done } = await iterator.next()
|
||||
if (done) {
|
||||
// When running action is done, close stream.
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
controller.byobRequest?.respond(0)
|
||||
})
|
||||
} else {
|
||||
// Whenever one or more bytes are available and stream is not errored,
|
||||
// enqueue a Uint8Array wrapping an ArrayBuffer containing the available
|
||||
// bytes into stream.
|
||||
if (!isErrored(stream)) {
|
||||
const buffer = new Uint8Array(value)
|
||||
if (buffer.byteLength) {
|
||||
controller.enqueue(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
},
|
||||
async cancel (reason) {
|
||||
await iterator.return()
|
||||
},
|
||||
type: 'bytes'
|
||||
})
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
|
||||
// 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 (object instanceof ReadableStream) {
|
||||
// Assert: object is neither disturbed nor locked.
|
||||
// istanbul ignore next
|
||||
assert(!util.isDisturbed(object), 'The body has already been consumed.')
|
||||
// istanbul ignore next
|
||||
assert(!object.locked, 'The stream is locked.')
|
||||
}
|
||||
|
||||
// 2. Return the results of extracting object.
|
||||
return extractBody(object, keepalive)
|
||||
}
|
||||
|
||||
function cloneBody (instance, 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 [out1, out2] = body.stream.tee()
|
||||
|
||||
if (hasFinalizationRegistry) {
|
||||
streamRegistry.register(instance, new WeakRef(out1))
|
||||
}
|
||||
|
||||
// 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 throwIfAborted (state) {
|
||||
if (state.aborted) {
|
||||
throw new DOMException('The operation was aborted.', 'AbortError')
|
||||
}
|
||||
}
|
||||
|
||||
function bodyMixinMethods (instance) {
|
||||
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(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)
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
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(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]
|
||||
const parsed = multipartFormDataParser(value, mimeType)
|
||||
|
||||
// 2. If that fails for some reason, then throw a TypeError.
|
||||
if (parsed === 'failure') {
|
||||
throw new TypeError('Failed to parse body as FormData.')
|
||||
}
|
||||
|
||||
// 3. Return a new FormData object, appending each entry,
|
||||
// resulting from the parsing operation, to its entry list.
|
||||
const fd = new FormData()
|
||||
fd[kState] = 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)
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
function mixinBody (prototype) {
|
||||
Object.assign(prototype.prototype, bodyMixinMethods(prototype))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-body-consume-body
|
||||
* @param {Response|Request} object
|
||||
* @param {(value: unknown) => unknown} convertBytesToJSValue
|
||||
* @param {Response|Request} instance
|
||||
*/
|
||||
async function consumeBody (object, convertBytesToJSValue, instance) {
|
||||
webidl.brandCheck(object, instance)
|
||||
|
||||
// 1. If object is unusable, then return a promise rejected
|
||||
// with a TypeError.
|
||||
if (bodyUnusable(object)) {
|
||||
throw new TypeError('Body is unusable: Body has already been read')
|
||||
}
|
||||
|
||||
throwIfAborted(object[kState])
|
||||
|
||||
// 2. Let promise be a new promise.
|
||||
const promise = createDeferredPromise()
|
||||
|
||||
// 3. Let errorSteps given error be to reject promise with error.
|
||||
const errorSteps = (error) => promise.reject(error)
|
||||
|
||||
// 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[kState].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.
|
||||
await fullyReadBody(object[kState].body, successSteps, errorSteps)
|
||||
|
||||
// 7. Return promise.
|
||||
return promise.promise
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#body-unusable
|
||||
function bodyUnusable (object) {
|
||||
const body = object[kState].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://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function parseJSONFromBytes (bytes) {
|
||||
return JSON.parse(utf8DecodeBytes(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-body-mime-type
|
||||
* @param {import('./response').Response|import('./request').Request} requestOrResponse
|
||||
*/
|
||||
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[kState].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,
|
||||
hasFinalizationRegistry,
|
||||
bodyUnusable
|
||||
}
|
124
book/node_modules/undici/lib/web/fetch/constants.js
generated
vendored
Normal file
124
book/node_modules/undici/lib/web/fetch/constants.js
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
'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-policies
|
||||
*/
|
||||
const referrerPolicy = /** @type {const} */ ([
|
||||
'',
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'same-origin',
|
||||
'origin',
|
||||
'strict-origin',
|
||||
'origin-when-cross-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url'
|
||||
])
|
||||
const referrerPolicySet = new Set(referrerPolicy)
|
||||
|
||||
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,
|
||||
referrerPolicySet
|
||||
}
|
744
book/node_modules/undici/lib/web/fetch/data-url.js
generated
vendored
Normal file
744
book/node_modules/undici/lib/web/fetch/data-url.js
generated
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('node:assert')
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
/**
|
||||
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
|
||||
*/
|
||||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/
|
||||
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line
|
||||
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // 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]+$/ // 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){0,}base64$/i.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)+$/, '')
|
||||
|
||||
// 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://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
||||
/**
|
||||
* @param {(char: string) => boolean} condition
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfCodePoints (condition, input, position) {
|
||||
// 1. Let result be the empty string.
|
||||
let result = ''
|
||||
|
||||
// 2. While position doesn’t point past the end of input and the
|
||||
// code point at position within input meets the condition condition:
|
||||
while (position.position < input.length && condition(input[position.position])) {
|
||||
// 1. Append that code point to the end of result.
|
||||
result += input[position.position]
|
||||
|
||||
// 2. Advance position by 1.
|
||||
position.position++
|
||||
}
|
||||
|
||||
// 3. Return result.
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* A faster collectASequenceOfCodePoints that only works when comparing a single character.
|
||||
* @param {string} char
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfCodePointsFast (char, input, position) {
|
||||
const idx = input.indexOf(char, position.position)
|
||||
const start = position.position
|
||||
|
||||
if (idx === -1) {
|
||||
position.position = input.length
|
||||
return input.slice(start)
|
||||
}
|
||||
|
||||
position.position = idx
|
||||
return input.slice(start, position.position)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 2. For each byte byte in input:
|
||||
for (let i = 0; i < length; ++i) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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://infra.spec.whatwg.org/#forgiving-base64-decode
|
||||
/** @param {string} data */
|
||||
function forgivingBase64 (data) {
|
||||
// 1. Remove all ASCII whitespace from data.
|
||||
data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '') // eslint-disable-line
|
||||
|
||||
let dataLength = data.length
|
||||
// 2. If data’s code point length divides by 4 leaving
|
||||
// no remainder, then:
|
||||
if (dataLength % 4 === 0) {
|
||||
// 1. If data ends with one or two U+003D (=) code points,
|
||||
// then remove them from data.
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
||||
--dataLength
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
||||
--dataLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If data’s code point length divides by 4 leaving
|
||||
// a remainder of 1, then return failure.
|
||||
if (dataLength % 4 === 1) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 4. If data contains a code point that is not one of
|
||||
// U+002B (+)
|
||||
// U+002F (/)
|
||||
// ASCII alphanumeric
|
||||
// then return failure.
|
||||
if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(data, 'base64')
|
||||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
||||
}
|
||||
|
||||
// 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
|
||||
*/
|
||||
function collectAnHTTPQuotedString (input, position, extractValue) {
|
||||
// 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(/(\\|")/g, '\\$1')
|
||||
|
||||
// 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://infra.spec.whatwg.org/#ascii-whitespace
|
||||
* @param {number} char
|
||||
*/
|
||||
function isASCIIWhitespace (char) {
|
||||
// "\r\n\t\f "
|
||||
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x00c || char === 0x020
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
|
||||
* @param {string} str
|
||||
* @param {boolean} [leading=true]
|
||||
* @param {boolean} [trailing=true]
|
||||
*/
|
||||
function removeASCIIWhitespace (str, leading = true, trailing = true) {
|
||||
return removeChars(str, leading, trailing, isASCIIWhitespace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {boolean} leading
|
||||
* @param {boolean} trailing
|
||||
* @param {(charCode: number) => boolean} predicate
|
||||
* @returns
|
||||
*/
|
||||
function removeChars (str, leading, trailing, predicate) {
|
||||
let lead = 0
|
||||
let trail = str.length - 1
|
||||
|
||||
if (leading) {
|
||||
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
|
||||
}
|
||||
|
||||
if (trailing) {
|
||||
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
|
||||
}
|
||||
|
||||
return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#isomorphic-decode
|
||||
* @param {Uint8Array} input
|
||||
* @returns {string}
|
||||
*/
|
||||
function isomorphicDecode (input) {
|
||||
// 1. To isomorphic decode a byte sequence input, return a string whose code point
|
||||
// length is equal to input’s length and whose code points have the same values
|
||||
// as the values of input’s bytes, in the same order.
|
||||
const length = input.length
|
||||
if ((2 << 15) - 1 > length) {
|
||||
return String.fromCharCode.apply(null, input)
|
||||
}
|
||||
let result = ''; let i = 0
|
||||
let addition = (2 << 15) - 1
|
||||
while (i < length) {
|
||||
if (i + addition > length) {
|
||||
addition = length - i
|
||||
}
|
||||
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
collectASequenceOfCodePoints,
|
||||
collectASequenceOfCodePointsFast,
|
||||
stringPercentDecode,
|
||||
parseMIMEType,
|
||||
collectAnHTTPQuotedString,
|
||||
serializeAMimeType,
|
||||
removeChars,
|
||||
removeHTTPWhitespace,
|
||||
minimizeSupportedMimeType,
|
||||
HTTP_TOKEN_CODEPOINTS,
|
||||
isomorphicDecode
|
||||
}
|
46
book/node_modules/undici/lib/web/fetch/dispatcher-weakref.js
generated
vendored
Normal file
46
book/node_modules/undici/lib/web/fetch/dispatcher-weakref.js
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
'use strict'
|
||||
|
||||
const { kConnected, kSize } = require('../../core/symbols')
|
||||
|
||||
class CompatWeakRef {
|
||||
constructor (value) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
deref () {
|
||||
return this.value[kConnected] === 0 && this.value[kSize] === 0
|
||||
? undefined
|
||||
: this.value
|
||||
}
|
||||
}
|
||||
|
||||
class CompatFinalizer {
|
||||
constructor (finalizer) {
|
||||
this.finalizer = finalizer
|
||||
}
|
||||
|
||||
register (dispatcher, key) {
|
||||
if (dispatcher.on) {
|
||||
dispatcher.on('disconnect', () => {
|
||||
if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) {
|
||||
this.finalizer(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unregister (key) {}
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
// FIXME: remove workaround when the Node bug is backported to v18
|
||||
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
|
||||
if (process.env.NODE_V8_COVERAGE && process.version.startsWith('v18')) {
|
||||
process._rawDebug('Using compatibility WeakRef and FinalizationRegistry')
|
||||
return {
|
||||
WeakRef: CompatWeakRef,
|
||||
FinalizationRegistry: CompatFinalizer
|
||||
}
|
||||
}
|
||||
return { WeakRef, FinalizationRegistry }
|
||||
}
|
126
book/node_modules/undici/lib/web/fetch/file.js
generated
vendored
Normal file
126
book/node_modules/undici/lib/web/fetch/file.js
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
'use strict'
|
||||
|
||||
const { Blob, File } = require('node:buffer')
|
||||
const { kState } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
|
||||
// TODO(@KhafraDev): remove
|
||||
class FileLike {
|
||||
constructor (blobLike, fileName, options = {}) {
|
||||
// TODO: argument idl type check
|
||||
|
||||
// The File constructor is invoked with two or three parameters, depending
|
||||
// on whether the optional dictionary parameter is used. When the File()
|
||||
// constructor is invoked, user agents must run the following steps:
|
||||
|
||||
// 1. Let bytes be the result of processing blob parts given fileBits and
|
||||
// options.
|
||||
|
||||
// 2. Let n be the fileName argument to the constructor.
|
||||
const n = fileName
|
||||
|
||||
// 3. Process FilePropertyBag dictionary argument by running the following
|
||||
// substeps:
|
||||
|
||||
// 1. If the type member is provided and is not the empty string, let t
|
||||
// be set to the type dictionary member. If t contains any characters
|
||||
// outside the range U+0020 to U+007E, then set t to the empty string
|
||||
// and return from these substeps.
|
||||
// TODO
|
||||
const t = options.type
|
||||
|
||||
// 2. Convert every character in t to ASCII lowercase.
|
||||
// TODO
|
||||
|
||||
// 3. If the lastModified member is provided, let d be set to the
|
||||
// lastModified dictionary member. If it is not provided, set d to the
|
||||
// current date and time represented as the number of milliseconds since
|
||||
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
|
||||
const d = options.lastModified ?? Date.now()
|
||||
|
||||
// 4. Return a new File object F such that:
|
||||
// F refers to the bytes byte sequence.
|
||||
// F.size is set to the number of total bytes in bytes.
|
||||
// F.name is set to n.
|
||||
// F.type is set to t.
|
||||
// F.lastModified is set to d.
|
||||
|
||||
this[kState] = {
|
||||
blobLike,
|
||||
name: n,
|
||||
type: t,
|
||||
lastModified: d
|
||||
}
|
||||
}
|
||||
|
||||
stream (...args) {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.stream(...args)
|
||||
}
|
||||
|
||||
arrayBuffer (...args) {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.arrayBuffer(...args)
|
||||
}
|
||||
|
||||
slice (...args) {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.slice(...args)
|
||||
}
|
||||
|
||||
text (...args) {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.text(...args)
|
||||
}
|
||||
|
||||
get size () {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.size
|
||||
}
|
||||
|
||||
get type () {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].blobLike.type
|
||||
}
|
||||
|
||||
get name () {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].name
|
||||
}
|
||||
|
||||
get lastModified () {
|
||||
webidl.brandCheck(this, FileLike)
|
||||
|
||||
return this[kState].lastModified
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'File'
|
||||
}
|
||||
}
|
||||
|
||||
webidl.converters.Blob = webidl.interfaceConverter(Blob)
|
||||
|
||||
// If this function is moved to ./util.js, some tools (such as
|
||||
// rollup) will warn about circular dependencies. See:
|
||||
// https://github.com/nodejs/undici/issues/1629
|
||||
function isFileLike (object) {
|
||||
return (
|
||||
(object instanceof File) ||
|
||||
(
|
||||
object &&
|
||||
(typeof object.stream === 'function' ||
|
||||
typeof object.arrayBuffer === 'function') &&
|
||||
object[Symbol.toStringTag] === 'File'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { FileLike, isFileLike }
|
474
book/node_modules/undici/lib/web/fetch/formdata-parser.js
generated
vendored
Normal file
474
book/node_modules/undici/lib/web/fetch/formdata-parser.js
generated
vendored
Normal file
@ -0,0 +1,474 @@
|
||||
'use strict'
|
||||
|
||||
const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
|
||||
const { utf8DecodeBytes } = require('./util')
|
||||
const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
|
||||
const { isFileLike } = require('./file')
|
||||
const { makeEntry } = require('./formdata')
|
||||
const assert = require('node:assert')
|
||||
const { File: NodeFile } = require('node:buffer')
|
||||
|
||||
const File = globalThis.File ?? NodeFile
|
||||
|
||||
const formDataNameBuffer = Buffer.from('form-data; name="')
|
||||
const filenameBuffer = Buffer.from('; filename')
|
||||
const dd = Buffer.from('--')
|
||||
const ddcrlf = Buffer.from('--\r\n')
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
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: undici addition, allows leading and trailing CRLFs.
|
||||
while (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
||||
position.position += 2
|
||||
}
|
||||
|
||||
let trailing = input.length
|
||||
|
||||
while (input[trailing - 1] === 0x0a && input[trailing - 2] === 0x0d) {
|
||||
trailing -= 2
|
||||
}
|
||||
|
||||
if (trailing !== input.length) {
|
||||
input = input.subarray(0, trailing)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 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: a body does NOT need to end with CRLF. It can end with --.
|
||||
if (
|
||||
(position.position === input.length - 2 && bufferStartsWith(input, dd, position)) ||
|
||||
(position.position === input.length - 4 && bufferStartsWith(input, ddcrlf, position))
|
||||
) {
|
||||
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) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if (result === 'failure') {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
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) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
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) {
|
||||
return 'failure'
|
||||
} 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(isUSVString(name))
|
||||
assert((typeof value === 'string' && isUSVString(value)) || isFileLike(value))
|
||||
|
||||
// 5.13. Create an entry with name and value, and append it to entry list.
|
||||
entryList.push(makeEntry(name, value, filename))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 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())) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 2.5. If the byte at position is not 0x3A (:), return failure.
|
||||
if (input[position.position] !== 0x3a) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 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': {
|
||||
// 1. Set name and filename to null.
|
||||
name = filename = null
|
||||
|
||||
// 2. If position does not point to a sequence of bytes starting with
|
||||
// `form-data; name="`, return failure.
|
||||
if (!bufferStartsWith(input, formDataNameBuffer, position)) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 3. Advance position so it points at the byte after the next 0x22 (")
|
||||
// byte (the one in the sequence of bytes matched above).
|
||||
position.position += 17
|
||||
|
||||
// 4. Set name to the result of parsing a multipart/form-data name given
|
||||
// input and position, if the result is not failure. Otherwise, return
|
||||
// failure.
|
||||
name = parseMultipartFormDataName(input, position)
|
||||
|
||||
if (name === null) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 5. If position points to a sequence of bytes starting with `; filename="`:
|
||||
if (bufferStartsWith(input, filenameBuffer, position)) {
|
||||
// Note: undici also handles filename*
|
||||
let check = position.position + filenameBuffer.length
|
||||
|
||||
if (input[check] === 0x2a) {
|
||||
position.position += 1
|
||||
check += 1
|
||||
}
|
||||
|
||||
if (input[check] !== 0x3d || input[check + 1] !== 0x22) { // ="
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 1. Advance position so it points at the byte after the next 0x22 (") byte
|
||||
// (the one in the sequence of bytes matched above).
|
||||
position.position += 12
|
||||
|
||||
// 2. Set filename to the result of parsing a multipart/form-data name given
|
||||
// input and position, if the result is not failure. Otherwise, return failure.
|
||||
filename = parseMultipartFormDataName(input, position)
|
||||
|
||||
if (filename === null) {
|
||||
return 'failure'
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return 'failure'
|
||||
} else {
|
||||
position.position += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://andreubotella.github.io/multipart-form-data/#parse-a-multipart-form-data-name
|
||||
* @param {Buffer} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function parseMultipartFormDataName (input, position) {
|
||||
// 1. Assert: The byte at (position - 1) is 0x22 (").
|
||||
assert(input[position.position - 1] === 0x22)
|
||||
|
||||
// 2. Let name be the result of collecting a sequence of bytes that are not 0x0A (LF), 0x0D (CR) or 0x22 ("), given position.
|
||||
/** @type {string | Buffer} */
|
||||
let name = collectASequenceOfBytes(
|
||||
(char) => char !== 0x0a && char !== 0x0d && char !== 0x22,
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 3. If the byte at position is not 0x22 ("), return failure. Otherwise, advance position by 1.
|
||||
if (input[position.position] !== 0x22) {
|
||||
return null // name could be 'failure'
|
||||
} else {
|
||||
position.position++
|
||||
}
|
||||
|
||||
// 4. Replace any occurrence of the following subsequences in name with the given byte:
|
||||
// - `%0A`: 0x0A (LF)
|
||||
// - `%0D`: 0x0D (CR)
|
||||
// - `%22`: 0x22 (")
|
||||
name = new TextDecoder().decode(name)
|
||||
.replace(/%0A/ig, '\n')
|
||||
.replace(/%0D/ig, '\r')
|
||||
.replace(/%22/g, '"')
|
||||
|
||||
// 5. Return the UTF-8 decoding without BOM of name.
|
||||
return name
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
multipartFormDataParser,
|
||||
validateBoundary
|
||||
}
|
252
book/node_modules/undici/lib/web/fetch/formdata.js
generated
vendored
Normal file
252
book/node_modules/undici/lib/web/fetch/formdata.js
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
'use strict'
|
||||
|
||||
const { isBlobLike, iteratorMixin } = require('./util')
|
||||
const { kState } = require('./symbols')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { FileLike, isFileLike } = require('./file')
|
||||
const { webidl } = require('./webidl')
|
||||
const { File: NativeFile } = require('node:buffer')
|
||||
const nodeUtil = require('node:util')
|
||||
|
||||
/** @type {globalThis['File']} */
|
||||
const File = globalThis.File ?? NativeFile
|
||||
|
||||
// https://xhr.spec.whatwg.org/#formdata
|
||||
class FormData {
|
||||
constructor (form) {
|
||||
webidl.util.markAsUncloneable(this)
|
||||
|
||||
if (form !== undefined) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'FormData constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['undefined']
|
||||
})
|
||||
}
|
||||
|
||||
this[kState] = []
|
||||
}
|
||||
|
||||
append (name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
const prefix = 'FormData.append'
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix)
|
||||
|
||||
if (arguments.length === 3 && !isBlobLike(value)) {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'"
|
||||
)
|
||||
}
|
||||
|
||||
// 1. Let value be value if given; otherwise blobValue.
|
||||
|
||||
name = webidl.converters.USVString(name, prefix, 'name')
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, prefix, 'value', { strict: false })
|
||||
: webidl.converters.USVString(value, prefix, 'value')
|
||||
filename = arguments.length === 3
|
||||
? webidl.converters.USVString(filename, prefix, 'filename')
|
||||
: undefined
|
||||
|
||||
// 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[kState].push(entry)
|
||||
}
|
||||
|
||||
delete (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
const prefix = 'FormData.delete'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
|
||||
name = webidl.converters.USVString(name, prefix, 'name')
|
||||
|
||||
// The delete(name) method steps are to remove all entries whose name
|
||||
// is name from this’s entry list.
|
||||
this[kState] = this[kState].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, prefix, 'name')
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return null.
|
||||
const idx = this[kState].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[kState][idx].value
|
||||
}
|
||||
|
||||
getAll (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
const prefix = 'FormData.getAll'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
|
||||
name = webidl.converters.USVString(name, prefix, '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[kState]
|
||||
.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, prefix, '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[kState].findIndex((entry) => entry.name === name) !== -1
|
||||
}
|
||||
|
||||
set (name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
const prefix = 'FormData.set'
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix)
|
||||
|
||||
if (arguments.length === 3 && !isBlobLike(value)) {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'"
|
||||
)
|
||||
}
|
||||
|
||||
// The set(name, value) and set(name, blobValue, filename) method steps
|
||||
// are:
|
||||
|
||||
// 1. Let value be value if given; otherwise blobValue.
|
||||
|
||||
name = webidl.converters.USVString(name, prefix, 'name')
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, prefix, 'name', { strict: false })
|
||||
: webidl.converters.USVString(value, prefix, 'name')
|
||||
filename = arguments.length === 3
|
||||
? webidl.converters.USVString(filename, prefix, 'name')
|
||||
: undefined
|
||||
|
||||
// 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[kState].findIndex((entry) => entry.name === name)
|
||||
if (idx !== -1) {
|
||||
this[kState] = [
|
||||
...this[kState].slice(0, idx),
|
||||
entry,
|
||||
...this[kState].slice(idx + 1).filter((entry) => entry.name !== name)
|
||||
]
|
||||
} else {
|
||||
// 4. Otherwise, append entry to this’s entry list.
|
||||
this[kState].push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
[nodeUtil.inspect.custom] (depth, options) {
|
||||
const state = this[kState].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)}`
|
||||
}
|
||||
}
|
||||
|
||||
iteratorMixin('FormData', FormData, kState, '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 (!isFileLike(value)) {
|
||||
value = value instanceof Blob
|
||||
? new File([value], 'blob', { type: value.type })
|
||||
: new FileLike(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 = value instanceof NativeFile
|
||||
? new File([value], filename, options)
|
||||
: new FileLike(value, filename, options)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return an entry whose name is name and whose value is value.
|
||||
return { name, value }
|
||||
}
|
||||
|
||||
module.exports = { FormData, makeEntry }
|
40
book/node_modules/undici/lib/web/fetch/global.js
generated
vendored
Normal file
40
book/node_modules/undici/lib/web/fetch/global.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
'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
|
||||
}
|
687
book/node_modules/undici/lib/web/fetch/headers.js
generated
vendored
Normal file
687
book/node_modules/undici/lib/web/fetch/headers.js
generated
vendored
Normal file
@ -0,0 +1,687 @@
|
||||
// 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')
|
||||
|
||||
const kHeadersMap = Symbol('headers map')
|
||||
const kHeadersSortedMap = Symbol('headers map sorted')
|
||||
|
||||
/**
|
||||
* @param {number} code
|
||||
*/
|
||||
function isHTTPWhiteSpaceCharCode (code) {
|
||||
return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
||||
* @param {string} potentialValue
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
function compareHeaderName (a, b) {
|
||||
return a[0] < b[0] ? -1 : 1
|
||||
}
|
||||
|
||||
class HeadersList {
|
||||
/** @type {[string, string][]|null} */
|
||||
cookies = null
|
||||
|
||||
constructor (init) {
|
||||
if (init instanceof HeadersList) {
|
||||
this[kHeadersMap] = new Map(init[kHeadersMap])
|
||||
this[kHeadersSortedMap] = init[kHeadersSortedMap]
|
||||
this.cookies = init.cookies === null ? null : [...init.cookies]
|
||||
} else {
|
||||
this[kHeadersMap] = new Map(init)
|
||||
this[kHeadersSortedMap] = 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[kHeadersMap].has(isLowerCase ? name : name.toLowerCase())
|
||||
}
|
||||
|
||||
clear () {
|
||||
this[kHeadersMap].clear()
|
||||
this[kHeadersSortedMap] = 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[kHeadersSortedMap] = 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[kHeadersMap].get(lowercaseName)
|
||||
|
||||
// 2. Append (name, value) to list.
|
||||
if (exists) {
|
||||
const delimiter = lowercaseName === 'cookie' ? '; ' : ', '
|
||||
this[kHeadersMap].set(lowercaseName, {
|
||||
name: exists.name,
|
||||
value: `${exists.value}${delimiter}${value}`
|
||||
})
|
||||
} else {
|
||||
this[kHeadersMap].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[kHeadersSortedMap] = 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[kHeadersMap].set(lowercaseName, { name, value })
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-header-list-delete
|
||||
* @param {string} name
|
||||
* @param {boolean} isLowerCase
|
||||
*/
|
||||
delete (name, isLowerCase) {
|
||||
this[kHeadersSortedMap] = null
|
||||
if (!isLowerCase) name = name.toLowerCase()
|
||||
|
||||
if (name === 'set-cookie') {
|
||||
this.cookies = null
|
||||
}
|
||||
|
||||
this[kHeadersMap].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[kHeadersMap].get(isLowerCase ? name : name.toLowerCase())?.value ?? null
|
||||
}
|
||||
|
||||
* [Symbol.iterator] () {
|
||||
// use the lowercased name
|
||||
for (const { 0: name, 1: { value } } of this[kHeadersMap]) {
|
||||
yield [name, value]
|
||||
}
|
||||
}
|
||||
|
||||
get entries () {
|
||||
const headers = {}
|
||||
|
||||
if (this[kHeadersMap].size !== 0) {
|
||||
for (const { name, value } of this[kHeadersMap].values()) {
|
||||
headers[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
rawValues () {
|
||||
return this[kHeadersMap].values()
|
||||
}
|
||||
|
||||
get entriesList () {
|
||||
const headers = []
|
||||
|
||||
if (this[kHeadersMap].size !== 0) {
|
||||
for (const { 0: lowerName, 1: { name, value } } of this[kHeadersMap]) {
|
||||
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[kHeadersMap].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[kHeadersMap][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[kHeadersMap]) {
|
||||
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
|
||||
#headersList
|
||||
|
||||
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 contructor', '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 []
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
||||
get [kHeadersSortedMap] () {
|
||||
if (this.#headersList[kHeadersSortedMap]) {
|
||||
return this.#headersList[kHeadersSortedMap]
|
||||
}
|
||||
|
||||
// 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 = this.#headersList.toSortedArray()
|
||||
|
||||
const cookies = this.#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 (this.#headersList[kHeadersSortedMap] = 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 (this.#headersList[kHeadersSortedMap] = headers)
|
||||
}
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
static getHeadersList (o) {
|
||||
return o.#headersList
|
||||
}
|
||||
|
||||
static setHeadersList (o, list) {
|
||||
o.#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, kHeadersSortedMap, 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) === '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
|
||||
}
|
2266
book/node_modules/undici/lib/web/fetch/index.js
generated
vendored
Normal file
2266
book/node_modules/undici/lib/web/fetch/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1037
book/node_modules/undici/lib/web/fetch/request.js
generated
vendored
Normal file
1037
book/node_modules/undici/lib/web/fetch/request.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
605
book/node_modules/undici/lib/web/fetch/response.js
generated
vendored
Normal file
605
book/node_modules/undici/lib/web/fetch/response.js
generated
vendored
Normal file
@ -0,0 +1,605 @@
|
||||
'use strict'
|
||||
|
||||
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
|
||||
const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require('./body')
|
||||
const util = require('../../core/util')
|
||||
const nodeUtil = require('node:util')
|
||||
const { kEnumerableProperty } = util
|
||||
const {
|
||||
isValidReasonPhrase,
|
||||
isCancelled,
|
||||
isAborted,
|
||||
isBlobLike,
|
||||
serializeJavascriptValueToJSONString,
|
||||
isErrorLike,
|
||||
isomorphicEncode,
|
||||
environmentSettingsObject: relevantRealm
|
||||
} = require('./util')
|
||||
const {
|
||||
redirectStatusSet,
|
||||
nullBodyStatus
|
||||
} = require('./constants')
|
||||
const { kState, kHeaders } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { FormData } = require('./formdata')
|
||||
const { URLSerializer } = require('./data-url')
|
||||
const { kConstruct } = require('../../core/symbols')
|
||||
const assert = require('node:assert')
|
||||
const { types } = require('node:util')
|
||||
|
||||
const textEncoder = new TextEncoder('utf-8')
|
||||
|
||||
// https://fetch.spec.whatwg.org/#response-class
|
||||
class Response {
|
||||
// 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 = {}) {
|
||||
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[kState].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[kState].headersList.append('location', value, true)
|
||||
|
||||
// 8. Return responseObject.
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response
|
||||
constructor (body = null, init = {}) {
|
||||
webidl.util.markAsUncloneable(this)
|
||||
if (body === kConstruct) {
|
||||
return
|
||||
}
|
||||
|
||||
if (body !== null) {
|
||||
body = webidl.converters.BodyInit(body)
|
||||
}
|
||||
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
|
||||
// 1. Set this’s response to a new response.
|
||||
this[kState] = 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[kHeaders] = new Headers(kConstruct)
|
||||
setHeadersGuard(this[kHeaders], 'response')
|
||||
setHeadersList(this[kHeaders], this[kState].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[kState].type
|
||||
}
|
||||
|
||||
// Returns response’s URL, if it has one; otherwise the empty string.
|
||||
get url () {
|
||||
webidl.brandCheck(this, Response)
|
||||
|
||||
const urlList = this[kState].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[kState].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[kState].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[kState].status >= 200 && this[kState].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[kState].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[kHeaders]
|
||||
}
|
||||
|
||||
get body () {
|
||||
webidl.brandCheck(this, Response)
|
||||
|
||||
return this[kState].body ? this[kState].body.stream : null
|
||||
}
|
||||
|
||||
get bodyUsed () {
|
||||
webidl.brandCheck(this, Response)
|
||||
|
||||
return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
|
||||
}
|
||||
|
||||
// Returns a clone of response.
|
||||
clone () {
|
||||
webidl.brandCheck(this, Response)
|
||||
|
||||
// 1. If this is unusable, then throw a TypeError.
|
||||
if (bodyUnusable(this)) {
|
||||
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[kState])
|
||||
|
||||
// 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[kHeaders]))
|
||||
}
|
||||
|
||||
[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)}`
|
||||
}
|
||||
}
|
||||
|
||||
mixinBody(Response)
|
||||
|
||||
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(newResponse, 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: Object.freeze([]),
|
||||
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) {
|
||||
response[kState].status = init.status
|
||||
}
|
||||
|
||||
// 4. Set response’s response’s status message to init["statusText"].
|
||||
if ('statusText' in init && init.statusText != null) {
|
||||
response[kState].statusText = init.statusText
|
||||
}
|
||||
|
||||
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
|
||||
if ('headers' in init && init.headers != null) {
|
||||
fill(response[kHeaders], 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.
|
||||
response[kState].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 && !response[kState].headersList.contains('content-type', true)) {
|
||||
response[kState].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)
|
||||
response[kState] = innerResponse
|
||||
response[kHeaders] = new Headers(kConstruct)
|
||||
setHeadersList(response[kHeaders], innerResponse.headersList)
|
||||
setHeadersGuard(response[kHeaders], guard)
|
||||
|
||||
if (hasFinalizationRegistry && 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
|
||||
}
|
||||
|
||||
webidl.converters.ReadableStream = webidl.interfaceConverter(
|
||||
ReadableStream
|
||||
)
|
||||
|
||||
webidl.converters.FormData = webidl.interfaceConverter(
|
||||
FormData
|
||||
)
|
||||
|
||||
webidl.converters.URLSearchParams = webidl.interfaceConverter(
|
||||
URLSearchParams
|
||||
)
|
||||
|
||||
// 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 (isBlobLike(V)) {
|
||||
return webidl.converters.Blob(V, prefix, name, { strict: false })
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
||||
return webidl.converters.BufferSource(V, prefix, name)
|
||||
}
|
||||
|
||||
if (util.isFormDataLike(V)) {
|
||||
return webidl.converters.FormData(V, prefix, name, { strict: false })
|
||||
}
|
||||
|
||||
if (V instanceof URLSearchParams) {
|
||||
return webidl.converters.URLSearchParams(V, prefix, name)
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V, prefix, name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit
|
||||
webidl.converters.BodyInit = function (V, prefix, argument) {
|
||||
if (V instanceof ReadableStream) {
|
||||
return webidl.converters.ReadableStream(V, prefix, argument)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
])
|
||||
|
||||
module.exports = {
|
||||
isNetworkError,
|
||||
makeNetworkError,
|
||||
makeResponse,
|
||||
makeAppropriateNetworkError,
|
||||
filterResponse,
|
||||
Response,
|
||||
cloneResponse,
|
||||
fromInnerResponse
|
||||
}
|
9
book/node_modules/undici/lib/web/fetch/symbols.js
generated
vendored
Normal file
9
book/node_modules/undici/lib/web/fetch/symbols.js
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
kUrl: Symbol('url'),
|
||||
kHeaders: Symbol('headers'),
|
||||
kSignal: Symbol('signal'),
|
||||
kState: Symbol('state'),
|
||||
kDispatcher: Symbol('dispatcher')
|
||||
}
|
1632
book/node_modules/undici/lib/web/fetch/util.js
generated
vendored
Normal file
1632
book/node_modules/undici/lib/web/fetch/util.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
695
book/node_modules/undici/lib/web/fetch/webidl.js
generated
vendored
Normal file
695
book/node_modules/undici/lib/web/fetch/webidl.js
generated
vendored
Normal file
@ -0,0 +1,695 @@
|
||||
'use strict'
|
||||
|
||||
const { types, inspect } = require('node:util')
|
||||
const { markAsUncloneable } = require('node:worker_threads')
|
||||
const { toUSVString } = require('../../core/util')
|
||||
|
||||
/** @type {import('../../../types/webidl').Webidl} */
|
||||
const webidl = {}
|
||||
webidl.converters = {}
|
||||
webidl.util = {}
|
||||
webidl.errors = {}
|
||||
|
||||
webidl.errors.exception = function (message) {
|
||||
return new TypeError(`${message.header}: ${message.message}`)
|
||||
}
|
||||
|
||||
webidl.errors.conversionFailed = function (context) {
|
||||
const plural = context.types.length === 1 ? '' : ' one of'
|
||||
const message =
|
||||
`${context.argument} could not be converted to` +
|
||||
`${plural}: ${context.types.join(', ')}.`
|
||||
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message
|
||||
})
|
||||
}
|
||||
|
||||
webidl.errors.invalidArgument = function (context) {
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message: `"${context.value}" is an invalid ${context.type}.`
|
||||
})
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#implements
|
||||
webidl.brandCheck = function (V, I, opts) {
|
||||
if (opts?.strict !== false) {
|
||||
if (!(V instanceof I)) {
|
||||
const err = new TypeError('Illegal invocation')
|
||||
err.code = 'ERR_INVALID_THIS' // node compat.
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) {
|
||||
const err = new TypeError('Illegal invocation')
|
||||
err.code = 'ERR_INVALID_THIS' // node compat.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webidl.argumentLengthCheck = function ({ length }, min, ctx) {
|
||||
if (length < min) {
|
||||
throw webidl.errors.exception({
|
||||
message: `${min} argument${min !== 1 ? 's' : ''} required, ` +
|
||||
`but${length ? ' only' : ''} ${length} found.`,
|
||||
header: ctx
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
webidl.illegalConstructor = function () {
|
||||
throw webidl.errors.exception({
|
||||
header: 'TypeError',
|
||||
message: 'Illegal constructor'
|
||||
})
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
|
||||
webidl.util.Type = function (V) {
|
||||
switch (typeof V) {
|
||||
case 'undefined': return 'Undefined'
|
||||
case 'boolean': return 'Boolean'
|
||||
case 'string': return 'String'
|
||||
case 'symbol': return 'Symbol'
|
||||
case 'number': return 'Number'
|
||||
case 'bigint': return 'BigInt'
|
||||
case 'function':
|
||||
case 'object': {
|
||||
if (V === null) {
|
||||
return 'Null'
|
||||
}
|
||||
|
||||
return 'Object'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
|
||||
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
||||
webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
||||
let upperBound
|
||||
let lowerBound
|
||||
|
||||
// 1. If bitLength is 64, then:
|
||||
if (bitLength === 64) {
|
||||
// 1. Let upperBound be 2^53 − 1.
|
||||
upperBound = Math.pow(2, 53) - 1
|
||||
|
||||
// 2. If signedness is "unsigned", then let lowerBound be 0.
|
||||
if (signedness === 'unsigned') {
|
||||
lowerBound = 0
|
||||
} else {
|
||||
// 3. Otherwise let lowerBound be −2^53 + 1.
|
||||
lowerBound = Math.pow(-2, 53) + 1
|
||||
}
|
||||
} else if (signedness === 'unsigned') {
|
||||
// 2. Otherwise, if signedness is "unsigned", then:
|
||||
|
||||
// 1. Let lowerBound be 0.
|
||||
lowerBound = 0
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1.
|
||||
upperBound = Math.pow(2, bitLength) - 1
|
||||
} else {
|
||||
// 3. Otherwise:
|
||||
|
||||
// 1. Let lowerBound be -2^bitLength − 1.
|
||||
lowerBound = Math.pow(-2, bitLength) - 1
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1 − 1.
|
||||
upperBound = Math.pow(2, bitLength - 1) - 1
|
||||
}
|
||||
|
||||
// 4. Let x be ? ToNumber(V).
|
||||
let x = Number(V)
|
||||
|
||||
// 5. If x is −0, then set x to +0.
|
||||
if (x === 0) {
|
||||
x = 0
|
||||
}
|
||||
|
||||
// 6. If the conversion is to an IDL type associated
|
||||
// with the [EnforceRange] extended attribute, then:
|
||||
if (opts?.enforceRange === true) {
|
||||
// 1. If x is NaN, +∞, or −∞, then throw a TypeError.
|
||||
if (
|
||||
Number.isNaN(x) ||
|
||||
x === Number.POSITIVE_INFINITY ||
|
||||
x === Number.NEGATIVE_INFINITY
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Integer conversion',
|
||||
message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Set x to IntegerPart(x).
|
||||
x = webidl.util.IntegerPart(x)
|
||||
|
||||
// 3. If x < lowerBound or x > upperBound, then
|
||||
// throw a TypeError.
|
||||
if (x < lowerBound || x > upperBound) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Integer conversion',
|
||||
message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.`
|
||||
})
|
||||
}
|
||||
|
||||
// 4. Return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// 7. If x is not NaN and the conversion is to an IDL
|
||||
// type associated with the [Clamp] extended
|
||||
// attribute, then:
|
||||
if (!Number.isNaN(x) && opts?.clamp === true) {
|
||||
// 1. Set x to min(max(x, lowerBound), upperBound).
|
||||
x = Math.min(Math.max(x, lowerBound), upperBound)
|
||||
|
||||
// 2. Round x to the nearest integer, choosing the
|
||||
// even integer if it lies halfway between two,
|
||||
// and choosing +0 rather than −0.
|
||||
if (Math.floor(x) % 2 === 0) {
|
||||
x = Math.floor(x)
|
||||
} else {
|
||||
x = Math.ceil(x)
|
||||
}
|
||||
|
||||
// 3. Return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// 8. If x is NaN, +0, +∞, or −∞, then return +0.
|
||||
if (
|
||||
Number.isNaN(x) ||
|
||||
(x === 0 && Object.is(0, x)) ||
|
||||
x === Number.POSITIVE_INFINITY ||
|
||||
x === Number.NEGATIVE_INFINITY
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 9. Set x to IntegerPart(x).
|
||||
x = webidl.util.IntegerPart(x)
|
||||
|
||||
// 10. Set x to x modulo 2^bitLength.
|
||||
x = x % Math.pow(2, bitLength)
|
||||
|
||||
// 11. If signedness is "signed" and x ≥ 2^bitLength − 1,
|
||||
// then return x − 2^bitLength.
|
||||
if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) {
|
||||
return x - Math.pow(2, bitLength)
|
||||
}
|
||||
|
||||
// 12. Otherwise, return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
|
||||
webidl.util.IntegerPart = function (n) {
|
||||
// 1. Let r be floor(abs(n)).
|
||||
const r = Math.floor(Math.abs(n))
|
||||
|
||||
// 2. If n < 0, then return -1 × r.
|
||||
if (n < 0) {
|
||||
return -1 * r
|
||||
}
|
||||
|
||||
// 3. Otherwise, return r.
|
||||
return r
|
||||
}
|
||||
|
||||
webidl.util.Stringify = function (V) {
|
||||
const type = webidl.util.Type(V)
|
||||
|
||||
switch (type) {
|
||||
case 'Symbol':
|
||||
return `Symbol(${V.description})`
|
||||
case 'Object':
|
||||
return inspect(V)
|
||||
case 'String':
|
||||
return `"${V}"`
|
||||
default:
|
||||
return `${V}`
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-sequence
|
||||
webidl.sequenceConverter = function (converter) {
|
||||
return (V, prefix, argument, Iterable) => {
|
||||
// 1. If Type(V) is not Object, throw a TypeError.
|
||||
if (webidl.util.Type(V) !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Let method be ? GetMethod(V, @@iterator).
|
||||
/** @type {Generator} */
|
||||
const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.()
|
||||
const seq = []
|
||||
let index = 0
|
||||
|
||||
// 3. If method is undefined, throw a TypeError.
|
||||
if (
|
||||
method === undefined ||
|
||||
typeof method.next !== 'function'
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} is not iterable.`
|
||||
})
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
|
||||
while (true) {
|
||||
const { done, value } = method.next()
|
||||
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
seq.push(converter(value, prefix, `${argument}[${index++}]`))
|
||||
}
|
||||
|
||||
return seq
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-to-record
|
||||
webidl.recordConverter = function (keyConverter, valueConverter) {
|
||||
return (O, prefix, argument) => {
|
||||
// 1. If Type(O) is not Object, throw a TypeError.
|
||||
if (webidl.util.Type(O) !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} ("${webidl.util.Type(O)}") is not an Object.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Let result be a new empty instance of record<K, V>.
|
||||
const result = {}
|
||||
|
||||
if (!types.isProxy(O)) {
|
||||
// 1. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]
|
||||
|
||||
for (const key of keys) {
|
||||
// 1. Let typedKey be key converted to an IDL value of type K.
|
||||
const typedKey = keyConverter(key, prefix, argument)
|
||||
|
||||
// 2. Let value be ? Get(O, key).
|
||||
// 3. Let typedValue be value converted to an IDL value of type V.
|
||||
const typedValue = valueConverter(O[key], prefix, argument)
|
||||
|
||||
// 4. Set result[typedKey] to typedValue.
|
||||
result[typedKey] = typedValue
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. Let keys be ? O.[[OwnPropertyKeys]]().
|
||||
const keys = Reflect.ownKeys(O)
|
||||
|
||||
// 4. For each key of keys.
|
||||
for (const key of keys) {
|
||||
// 1. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
const desc = Reflect.getOwnPropertyDescriptor(O, key)
|
||||
|
||||
// 2. If desc is not undefined and desc.[[Enumerable]] is true:
|
||||
if (desc?.enumerable) {
|
||||
// 1. Let typedKey be key converted to an IDL value of type K.
|
||||
const typedKey = keyConverter(key, prefix, argument)
|
||||
|
||||
// 2. Let value be ? Get(O, key).
|
||||
// 3. Let typedValue be value converted to an IDL value of type V.
|
||||
const typedValue = valueConverter(O[key], prefix, argument)
|
||||
|
||||
// 4. Set result[typedKey] to typedValue.
|
||||
result[typedKey] = typedValue
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
webidl.interfaceConverter = function (i) {
|
||||
return (V, prefix, argument, opts) => {
|
||||
if (opts?.strict !== false && !(V instanceof i)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${i.name}.`
|
||||
})
|
||||
}
|
||||
|
||||
return V
|
||||
}
|
||||
}
|
||||
|
||||
webidl.dictionaryConverter = function (converters) {
|
||||
return (dictionary, prefix, argument) => {
|
||||
const type = webidl.util.Type(dictionary)
|
||||
const dict = {}
|
||||
|
||||
if (type === 'Null' || type === 'Undefined') {
|
||||
return dict
|
||||
} else if (type !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`
|
||||
})
|
||||
}
|
||||
|
||||
for (const options of converters) {
|
||||
const { key, defaultValue, required, converter } = options
|
||||
|
||||
if (required === true) {
|
||||
if (!Object.hasOwn(dictionary, key)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Missing required key "${key}".`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let value = dictionary[key]
|
||||
const hasDefault = Object.hasOwn(options, 'defaultValue')
|
||||
|
||||
// Only use defaultValue if value is undefined and
|
||||
// a defaultValue options was provided.
|
||||
if (hasDefault && value !== null) {
|
||||
value ??= defaultValue()
|
||||
}
|
||||
|
||||
// A key can be optional and have no default value.
|
||||
// When this happens, do not perform a conversion,
|
||||
// and do not assign the key a value.
|
||||
if (required || hasDefault || value !== undefined) {
|
||||
value = converter(value, prefix, `${argument}.${key}`)
|
||||
|
||||
if (
|
||||
options.allowedValues &&
|
||||
!options.allowedValues.includes(value)
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`
|
||||
})
|
||||
}
|
||||
|
||||
dict[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
}
|
||||
|
||||
webidl.nullableConverter = function (converter) {
|
||||
return (V, prefix, argument) => {
|
||||
if (V === null) {
|
||||
return V
|
||||
}
|
||||
|
||||
return converter(V, prefix, argument)
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-DOMString
|
||||
webidl.converters.DOMString = function (V, prefix, argument, opts) {
|
||||
// 1. If V is null and the conversion is to an IDL type
|
||||
// associated with the [LegacyNullToEmptyString]
|
||||
// extended attribute, then return the DOMString value
|
||||
// that represents the empty string.
|
||||
if (V === null && opts?.legacyNullToEmptyString) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 2. Let x be ? ToString(V).
|
||||
if (typeof V === 'symbol') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} is a symbol, which cannot be converted to a DOMString.`
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Return the IDL DOMString value that represents the
|
||||
// same sequence of code units as the one the
|
||||
// ECMAScript String value x represents.
|
||||
return String(V)
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-ByteString
|
||||
webidl.converters.ByteString = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ToString(V).
|
||||
// Note: DOMString converter perform ? ToString(V)
|
||||
const x = webidl.converters.DOMString(V, prefix, argument)
|
||||
|
||||
// 2. If the value of any element of x is greater than
|
||||
// 255, then throw a TypeError.
|
||||
for (let index = 0; index < x.length; index++) {
|
||||
if (x.charCodeAt(index) > 255) {
|
||||
throw new TypeError(
|
||||
'Cannot convert argument to a ByteString because the character at ' +
|
||||
`index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return an IDL ByteString value whose length is the
|
||||
// length of x, and where the value of each element is
|
||||
// the value of the corresponding element of x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-USVString
|
||||
// TODO: rewrite this so we can control the errors thrown
|
||||
webidl.converters.USVString = toUSVString
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-boolean
|
||||
webidl.converters.boolean = function (V) {
|
||||
// 1. Let x be the result of computing ToBoolean(V).
|
||||
const x = Boolean(V)
|
||||
|
||||
// 2. Return the IDL boolean value that is the one that represents
|
||||
// the same truth value as the ECMAScript Boolean value x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-any
|
||||
webidl.converters.any = function (V) {
|
||||
return V
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-long-long
|
||||
webidl.converters['long long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "signed").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'signed', undefined, prefix, argument)
|
||||
|
||||
// 2. Return the IDL long long value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-long-long
|
||||
webidl.converters['unsigned long long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'unsigned', undefined, prefix, argument)
|
||||
|
||||
// 2. Return the IDL unsigned long long value that
|
||||
// represents the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-long
|
||||
webidl.converters['unsigned long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 32, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 32, 'unsigned', undefined, prefix, argument)
|
||||
|
||||
// 2. Return the IDL unsigned long value that
|
||||
// represents the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-short
|
||||
webidl.converters['unsigned short'] = function (V, prefix, argument, opts) {
|
||||
// 1. Let x be ? ConvertToInt(V, 16, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts, prefix, argument)
|
||||
|
||||
// 2. Return the IDL unsigned short value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#idl-ArrayBuffer
|
||||
webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
||||
// 1. If Type(V) is not Object, or V does not have an
|
||||
// [[ArrayBufferData]] internal slot, then throw a
|
||||
// TypeError.
|
||||
// see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
|
||||
// see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances
|
||||
if (
|
||||
webidl.util.Type(V) !== 'Object' ||
|
||||
!types.isAnyArrayBuffer(V)
|
||||
) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix,
|
||||
argument: `${argument} ("${webidl.util.Stringify(V)}")`,
|
||||
types: ['ArrayBuffer']
|
||||
})
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V) is true, then throw a
|
||||
// TypeError.
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V) is true, then throw a
|
||||
// TypeError.
|
||||
if (V.resizable || V.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. Return the IDL ArrayBuffer value that is a
|
||||
// reference to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
||||
// 1. Let T be the IDL type V is being converted to.
|
||||
|
||||
// 2. If Type(V) is not Object, or V does not have a
|
||||
// [[TypedArrayName]] internal slot with a value
|
||||
// equal to T’s name, then throw a TypeError.
|
||||
if (
|
||||
webidl.util.Type(V) !== 'Object' ||
|
||||
!types.isTypedArray(V) ||
|
||||
V.constructor.name !== T.name
|
||||
) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix,
|
||||
argument: `${name} ("${webidl.util.Stringify(V)}")`,
|
||||
types: [T.name]
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
if (V.buffer.resizable || V.buffer.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
}
|
||||
|
||||
// 5. Return the IDL value of type T that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
webidl.converters.DataView = function (V, prefix, name, opts) {
|
||||
// 1. If Type(V) is not Object, or V does not have a
|
||||
// [[DataView]] internal slot, then throw a TypeError.
|
||||
if (webidl.util.Type(V) !== 'Object' || !types.isDataView(V)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${name} is not a DataView.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true,
|
||||
// then throw a TypeError.
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
if (V.buffer.resizable || V.buffer.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. Return the IDL DataView value that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#BufferSource
|
||||
webidl.converters.BufferSource = function (V, prefix, name, opts) {
|
||||
if (types.isAnyArrayBuffer(V)) {
|
||||
return webidl.converters.ArrayBuffer(V, prefix, name, { ...opts, allowShared: false })
|
||||
}
|
||||
|
||||
if (types.isTypedArray(V)) {
|
||||
return webidl.converters.TypedArray(V, V.constructor, prefix, name, { ...opts, allowShared: false })
|
||||
}
|
||||
|
||||
if (types.isDataView(V)) {
|
||||
return webidl.converters.DataView(V, prefix, name, { ...opts, allowShared: false })
|
||||
}
|
||||
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix,
|
||||
argument: `${name} ("${webidl.util.Stringify(V)}")`,
|
||||
types: ['BufferSource']
|
||||
})
|
||||
}
|
||||
|
||||
webidl.converters['sequence<ByteString>'] = webidl.sequenceConverter(
|
||||
webidl.converters.ByteString
|
||||
)
|
||||
|
||||
webidl.converters['sequence<sequence<ByteString>>'] = webidl.sequenceConverter(
|
||||
webidl.converters['sequence<ByteString>']
|
||||
)
|
||||
|
||||
webidl.converters['record<ByteString, ByteString>'] = webidl.recordConverter(
|
||||
webidl.converters.ByteString,
|
||||
webidl.converters.ByteString
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
webidl
|
||||
}
|
Reference in New Issue
Block a user