commit
This commit is contained in:
parent
a15c733e45
commit
2a5130cbda
2838 changed files with 288613 additions and 0 deletions
21
node_modules/undici/lib/fetch/LICENSE
generated
vendored
Normal file
21
node_modules/undici/lib/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.
|
595
node_modules/undici/lib/fetch/body.js
generated
vendored
Normal file
595
node_modules/undici/lib/fetch/body.js
generated
vendored
Normal file
|
@ -0,0 +1,595 @@
|
|||
'use strict'
|
||||
|
||||
const Busboy = require('busboy')
|
||||
const util = require('../core/util')
|
||||
const {
|
||||
ReadableStreamFrom,
|
||||
isBlobLike,
|
||||
isReadableStreamLike,
|
||||
readableStreamClose,
|
||||
createDeferredPromise,
|
||||
fullyReadBody
|
||||
} = require('./util')
|
||||
const { FormData } = require('./formdata')
|
||||
const { kState } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { DOMException, structuredClone } = require('./constants')
|
||||
const { Blob, File: NativeFile } = require('buffer')
|
||||
const { kBodyUsed } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
const { isErrored } = require('../core/util')
|
||||
const { isUint8Array, isArrayBuffer } = require('util/types')
|
||||
const { File: UndiciFile } = require('./file')
|
||||
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
|
||||
|
||||
let ReadableStream = globalThis.ReadableStream
|
||||
|
||||
/** @type {globalThis['File']} */
|
||||
const File = NativeFile ?? UndiciFile
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||||
function extractBody (object, keepalive = false) {
|
||||
if (!ReadableStream) {
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
// 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.
|
||||
stream = new ReadableStream({
|
||||
async pull (controller) {
|
||||
controller.enqueue(
|
||||
typeof source === 'string' ? new TextEncoder().encode(source) : source
|
||||
)
|
||||
queueMicrotask(() => readableStreamClose(controller))
|
||||
},
|
||||
start () {},
|
||||
type: undefined
|
||||
})
|
||||
}
|
||||
|
||||
// 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-${Math.random()}`.replace('.', '').slice(0, 32)
|
||||
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 enc = new TextEncoder()
|
||||
const blobParts = []
|
||||
const rn = new Uint8Array([13, 10]) // '\r\n'
|
||||
length = 0
|
||||
|
||||
for (const [name, value] of object) {
|
||||
if (typeof value === 'string') {
|
||||
const chunk = enc.encode(prefix +
|
||||
`; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
||||
blobParts.push(chunk)
|
||||
length += chunk.byteLength
|
||||
} else {
|
||||
const chunk = enc.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)
|
||||
length += chunk.byteLength + value.size + rn.byteLength
|
||||
}
|
||||
}
|
||||
|
||||
const chunk = enc.encode(`--${boundary}--`)
|
||||
blobParts.push(chunk)
|
||||
length += chunk.byteLength
|
||||
|
||||
// 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()
|
||||
})
|
||||
} 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)) {
|
||||
controller.enqueue(new Uint8Array(value))
|
||||
}
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
},
|
||||
async cancel (reason) {
|
||||
await iterator.return()
|
||||
},
|
||||
type: undefined
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!ReadableStream) {
|
||||
// istanbul ignore next
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
// 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 (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()
|
||||
const out2Clone = structuredClone(out2, { transfer: [out2] })
|
||||
// This, for whatever reasons, unrefs out2Clone which allows
|
||||
// the process to exit by itself.
|
||||
const [, finalClone] = out2Clone.tee()
|
||||
|
||||
// 2. Set body’s stream to out1.
|
||||
body.stream = out1
|
||||
|
||||
// 3. Return a body whose stream is out2 and other members are copied from body.
|
||||
return {
|
||||
stream: finalClone,
|
||||
length: body.length,
|
||||
source: body.source
|
||||
}
|
||||
}
|
||||
|
||||
async function * consumeBody (body) {
|
||||
if (body) {
|
||||
if (isUint8Array(body)) {
|
||||
yield body
|
||||
} else {
|
||||
const stream = body.stream
|
||||
|
||||
if (util.isDisturbed(stream)) {
|
||||
throw new TypeError('The body has already been consumed.')
|
||||
}
|
||||
|
||||
if (stream.locked) {
|
||||
throw new TypeError('The stream is locked.')
|
||||
}
|
||||
|
||||
// Compat.
|
||||
stream[kBodyUsed] = true
|
||||
|
||||
yield * stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 specConsumeBody(this, (bytes) => {
|
||||
let mimeType = bodyMimeType(this)
|
||||
|
||||
if (mimeType === 'failure') {
|
||||
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 specConsumeBody(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 specConsumeBody(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 specConsumeBody(this, parseJSONFromBytes, instance)
|
||||
},
|
||||
|
||||
async formData () {
|
||||
webidl.brandCheck(this, instance)
|
||||
|
||||
throwIfAborted(this[kState])
|
||||
|
||||
const contentType = this.headers.get('Content-Type')
|
||||
|
||||
// If mimeType’s essence is "multipart/form-data", then:
|
||||
if (/multipart\/form-data/.test(contentType)) {
|
||||
const headers = {}
|
||||
for (const [key, value] of this.headers) headers[key.toLowerCase()] = value
|
||||
|
||||
const responseFormData = new FormData()
|
||||
|
||||
let busboy
|
||||
|
||||
try {
|
||||
busboy = Busboy({
|
||||
headers,
|
||||
defParamCharset: 'utf8'
|
||||
})
|
||||
} catch (err) {
|
||||
throw new DOMException(`${err}`, 'AbortError')
|
||||
}
|
||||
|
||||
busboy.on('field', (name, value) => {
|
||||
responseFormData.append(name, value)
|
||||
})
|
||||
busboy.on('file', (name, value, info) => {
|
||||
const { filename, encoding, mimeType } = info
|
||||
const chunks = []
|
||||
|
||||
if (encoding === 'base64' || encoding.toLowerCase() === 'base64') {
|
||||
let base64chunk = ''
|
||||
|
||||
value.on('data', (chunk) => {
|
||||
base64chunk += chunk.toString().replace(/[\r\n]/gm, '')
|
||||
|
||||
const end = base64chunk.length - base64chunk.length % 4
|
||||
chunks.push(Buffer.from(base64chunk.slice(0, end), 'base64'))
|
||||
|
||||
base64chunk = base64chunk.slice(end)
|
||||
})
|
||||
value.on('end', () => {
|
||||
chunks.push(Buffer.from(base64chunk, 'base64'))
|
||||
responseFormData.append(name, new File(chunks, filename, { type: mimeType }))
|
||||
})
|
||||
} else {
|
||||
value.on('data', (chunk) => {
|
||||
chunks.push(chunk)
|
||||
})
|
||||
value.on('end', () => {
|
||||
responseFormData.append(name, new File(chunks, filename, { type: mimeType }))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const busboyResolve = new Promise((resolve, reject) => {
|
||||
busboy.on('finish', resolve)
|
||||
busboy.on('error', (err) => reject(new TypeError(err)))
|
||||
})
|
||||
|
||||
if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk)
|
||||
busboy.end()
|
||||
await busboyResolve
|
||||
|
||||
return responseFormData
|
||||
} else if (/application\/x-www-form-urlencoded/.test(contentType)) {
|
||||
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
|
||||
|
||||
// 1. Let entries be the result of parsing bytes.
|
||||
let entries
|
||||
try {
|
||||
let text = ''
|
||||
// application/x-www-form-urlencoded parser will keep the BOM.
|
||||
// https://url.spec.whatwg.org/#concept-urlencoded-parser
|
||||
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
|
||||
for await (const chunk of consumeBody(this[kState].body)) {
|
||||
if (!isUint8Array(chunk)) {
|
||||
throw new TypeError('Expected Uint8Array chunk')
|
||||
}
|
||||
text += textDecoder.decode(chunk, { stream: true })
|
||||
}
|
||||
text += textDecoder.decode()
|
||||
entries = new URLSearchParams(text)
|
||||
} catch (err) {
|
||||
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
|
||||
// 2. If entries is failure, then throw a TypeError.
|
||||
throw Object.assign(new TypeError(), { cause: err })
|
||||
}
|
||||
|
||||
// 3. Return a new FormData object whose entries are entries.
|
||||
const formData = new FormData()
|
||||
for (const [name, value] of entries) {
|
||||
formData.append(name, value)
|
||||
}
|
||||
return formData
|
||||
} else {
|
||||
// Wait a tick before checking if the request has been aborted.
|
||||
// Otherwise, a TypeError can be thrown when an AbortError should.
|
||||
await Promise.resolve()
|
||||
|
||||
throwIfAborted(this[kState])
|
||||
|
||||
// Otherwise, throw a TypeError.
|
||||
throw webidl.errors.exception({
|
||||
header: `${instance.name}.formData`,
|
||||
message: 'Could not parse content as FormData.'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 specConsumeBody (object, convertBytesToJSValue, instance) {
|
||||
webidl.brandCheck(object, instance)
|
||||
|
||||
throwIfAborted(object[kState])
|
||||
|
||||
// 1. If object is unusable, then return a promise rejected
|
||||
// with a TypeError.
|
||||
if (bodyUnusable(object[kState].body)) {
|
||||
throw new TypeError('Body is unusable')
|
||||
}
|
||||
|
||||
// 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(new Uint8Array())
|
||||
return promise.promise
|
||||
}
|
||||
|
||||
// 6. Otherwise, fully read object’s body given successSteps,
|
||||
// errorSteps, and object’s relevant global object.
|
||||
fullyReadBody(object[kState].body, successSteps, errorSteps)
|
||||
|
||||
// 7. Return promise.
|
||||
return promise.promise
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#body-unusable
|
||||
function bodyUnusable (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://encoding.spec.whatwg.org/#utf-8-decode
|
||||
* @param {Buffer} buffer
|
||||
*/
|
||||
function utf8DecodeBytes (buffer) {
|
||||
if (buffer.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 1. Let buffer be the result of peeking three bytes from
|
||||
// ioQueue, converted to a byte sequence.
|
||||
|
||||
// 2. If buffer is 0xEF 0xBB 0xBF, then read three
|
||||
// bytes from ioQueue. (Do nothing with those bytes.)
|
||||
if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
|
||||
buffer = buffer.subarray(3)
|
||||
}
|
||||
|
||||
// 3. Process a queue with an instance of UTF-8’s
|
||||
// decoder, ioQueue, output, and "replacement".
|
||||
const output = new TextDecoder().decode(buffer)
|
||||
|
||||
// 4. Return output.
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* @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} object
|
||||
*/
|
||||
function bodyMimeType (object) {
|
||||
const { headersList } = object[kState]
|
||||
const contentType = headersList.get('content-type')
|
||||
|
||||
if (contentType === null) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
return parseMIMEType(contentType)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractBody,
|
||||
safelyExtractBody,
|
||||
cloneBody,
|
||||
mixinBody
|
||||
}
|
130
node_modules/undici/lib/fetch/constants.js
generated
vendored
Normal file
130
node_modules/undici/lib/fetch/constants.js
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
'use strict'
|
||||
|
||||
const { MessageChannel, receiveMessageOnPort } = require('worker_threads')
|
||||
|
||||
const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
|
||||
|
||||
const nullBodyStatus = [101, 204, 205, 304]
|
||||
|
||||
const redirectStatus = [301, 302, 303, 307, 308]
|
||||
|
||||
// https://fetch.spec.whatwg.org/#block-bad-port
|
||||
const badPorts = [
|
||||
'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', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697',
|
||||
'10080'
|
||||
]
|
||||
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
||||
const referrerPolicy = [
|
||||
'',
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'same-origin',
|
||||
'origin',
|
||||
'strict-origin',
|
||||
'origin-when-cross-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url'
|
||||
]
|
||||
|
||||
const requestRedirect = ['follow', 'manual', 'error']
|
||||
|
||||
const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
||||
|
||||
const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
|
||||
|
||||
const requestCredentials = ['omit', 'same-origin', 'include']
|
||||
|
||||
const requestCache = [
|
||||
'default',
|
||||
'no-store',
|
||||
'reload',
|
||||
'no-cache',
|
||||
'force-cache',
|
||||
'only-if-cached'
|
||||
]
|
||||
|
||||
const requestBodyHeader = [
|
||||
'content-encoding',
|
||||
'content-language',
|
||||
'content-location',
|
||||
'content-type'
|
||||
]
|
||||
|
||||
// https://fetch.spec.whatwg.org/#enumdef-requestduplex
|
||||
const requestDuplex = [
|
||||
'half'
|
||||
]
|
||||
|
||||
// http://fetch.spec.whatwg.org/#forbidden-method
|
||||
const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
|
||||
|
||||
const subresource = [
|
||||
'audio',
|
||||
'audioworklet',
|
||||
'font',
|
||||
'image',
|
||||
'manifest',
|
||||
'paintworklet',
|
||||
'script',
|
||||
'style',
|
||||
'track',
|
||||
'video',
|
||||
'xslt',
|
||||
''
|
||||
]
|
||||
|
||||
/** @type {globalThis['DOMException']} */
|
||||
const DOMException = globalThis.DOMException ?? (() => {
|
||||
// DOMException was only made a global in Node v17.0.0,
|
||||
// but fetch supports >= v16.8.
|
||||
try {
|
||||
atob('~')
|
||||
} catch (err) {
|
||||
return Object.getPrototypeOf(err).constructor
|
||||
}
|
||||
})()
|
||||
|
||||
let channel
|
||||
|
||||
/** @type {globalThis['structuredClone']} */
|
||||
const structuredClone =
|
||||
globalThis.structuredClone ??
|
||||
// https://github.com/nodejs/node/blob/b27ae24dcc4251bad726d9d84baf678d1f707fed/lib/internal/structured_clone.js
|
||||
// structuredClone was added in v17.0.0, but fetch supports v16.8
|
||||
function structuredClone (value, options = undefined) {
|
||||
if (arguments.length === 0) {
|
||||
throw new TypeError('missing argument')
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
channel = new MessageChannel()
|
||||
}
|
||||
channel.port1.unref()
|
||||
channel.port2.unref()
|
||||
channel.port1.postMessage(value, options?.transfer)
|
||||
return receiveMessageOnPort(channel.port2).message
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DOMException,
|
||||
structuredClone,
|
||||
subresource,
|
||||
forbiddenMethods,
|
||||
requestBodyHeader,
|
||||
referrerPolicy,
|
||||
requestRedirect,
|
||||
requestMode,
|
||||
requestCredentials,
|
||||
requestCache,
|
||||
redirectStatus,
|
||||
corsSafeListedMethods,
|
||||
nullBodyStatus,
|
||||
safeMethods,
|
||||
badPorts,
|
||||
requestDuplex
|
||||
}
|
544
node_modules/undici/lib/fetch/dataURL.js
generated
vendored
Normal file
544
node_modules/undici/lib/fetch/dataURL.js
generated
vendored
Normal file
|
@ -0,0 +1,544 @@
|
|||
const assert = require('assert')
|
||||
const { atob } = require('buffer')
|
||||
const { format } = require('url')
|
||||
const { isValidHTTPToken, isomorphicDecode } = require('./util')
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
// Regex
|
||||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-z0-9]+$/
|
||||
const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line
|
||||
// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
|
||||
const HTTP_QUOTED_STRING_TOKENS = /^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/ // 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 = collectASequenceOfCodePoints(
|
||||
(char) => char !== ',',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 6. Strip leading and trailing ASCII whitespace
|
||||
// from mimeType.
|
||||
// Note: This will only remove U+0020 SPACE code
|
||||
// points, if any.
|
||||
// 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 = mimeType.replace(/^(\u0020)+|(\u0020)+$/g, '')
|
||||
|
||||
// 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) {
|
||||
return format(url, { fragment: !excludeFragment })
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#percent-decode
|
||||
/** @param {Uint8Array} input */
|
||||
function percentDecode (input) {
|
||||
// 1. Let output be an empty byte sequence.
|
||||
/** @type {number[]} */
|
||||
const output = []
|
||||
|
||||
// 2. For each byte byte in input:
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const byte = input[i]
|
||||
|
||||
// 1. If byte is not 0x25 (%), then append byte to output.
|
||||
if (byte !== 0x25) {
|
||||
output.push(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 &&
|
||||
!/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2]))
|
||||
) {
|
||||
output.push(0x25)
|
||||
|
||||
// 3. Otherwise:
|
||||
} else {
|
||||
// 1. Let bytePoint be the two bytes after byte in input,
|
||||
// decoded, and then interpreted as hexadecimal number.
|
||||
const nextTwoBytes = String.fromCharCode(input[i + 1], input[i + 2])
|
||||
const bytePoint = Number.parseInt(nextTwoBytes, 16)
|
||||
|
||||
// 2. Append a byte whose value is bytePoint to output.
|
||||
output.push(bytePoint)
|
||||
|
||||
// 3. Skip the next two bytes in input.
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return output.
|
||||
return Uint8Array.from(output)
|
||||
}
|
||||
|
||||
// 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 = input.trim()
|
||||
|
||||
// 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 = collectASequenceOfCodePoints(
|
||||
(char) => char !== '/',
|
||||
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 = collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 8. Remove any trailing HTTP whitespace from subtype.
|
||||
subtype = subtype.trimEnd()
|
||||
|
||||
// 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'
|
||||
}
|
||||
|
||||
// 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: type.toLowerCase(),
|
||||
subtype: subtype.toLowerCase(),
|
||||
/** @type {Map<string, string>} */
|
||||
parameters: new Map(),
|
||||
// https://mimesniff.spec.whatwg.org/#mime-type-essence
|
||||
essence: `${type}/${subtype}`
|
||||
}
|
||||
|
||||
// 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.
|
||||
collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
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 = collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 2. Remove any trailing HTTP whitespace from parameterValue.
|
||||
// Note: it says "trailing" whitespace; leading is fine.
|
||||
parameterValue = parameterValue.trimEnd()
|
||||
|
||||
// 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) &&
|
||||
!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(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line
|
||||
|
||||
// 2. If data’s code point length divides by 4 leaving
|
||||
// no remainder, then:
|
||||
if (data.length % 4 === 0) {
|
||||
// 1. If data ends with one or two U+003D (=) code points,
|
||||
// then remove them from data.
|
||||
data = data.replace(/=?=$/, '')
|
||||
}
|
||||
|
||||
// 3. If data’s code point length divides by 4 leaving
|
||||
// a remainder of 1, then return failure.
|
||||
if (data.length % 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)) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
const binary = atob(data)
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
|
||||
for (let byte = 0; byte < binary.length; byte++) {
|
||||
bytes[byte] = binary.charCodeAt(byte)
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// 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 { type, subtype, parameters } = mimeType
|
||||
|
||||
// 1. Let serialization be the concatenation of mimeType’s
|
||||
// type, U+002F (/), and mimeType’s subtype.
|
||||
let serialization = `${type}/${subtype}`
|
||||
|
||||
// 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 (!isValidHTTPToken(value)) {
|
||||
// 1. Precede each occurence 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
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dataURLProcessor,
|
||||
URLSerializer,
|
||||
collectASequenceOfCodePoints,
|
||||
stringPercentDecode,
|
||||
parseMIMEType,
|
||||
collectAnHTTPQuotedString,
|
||||
serializeAMimeType
|
||||
}
|
343
node_modules/undici/lib/fetch/file.js
generated
vendored
Normal file
343
node_modules/undici/lib/fetch/file.js
generated
vendored
Normal file
|
@ -0,0 +1,343 @@
|
|||
'use strict'
|
||||
|
||||
const { Blob, File: NativeFile } = require('buffer')
|
||||
const { types } = require('util')
|
||||
const { kState } = require('./symbols')
|
||||
const { isBlobLike } = require('./util')
|
||||
const { webidl } = require('./webidl')
|
||||
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
|
||||
const { kEnumerableProperty } = require('../core/util')
|
||||
|
||||
class File extends Blob {
|
||||
constructor (fileBits, fileName, options = {}) {
|
||||
// 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:
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' })
|
||||
|
||||
fileBits = webidl.converters['sequence<BlobPart>'](fileBits)
|
||||
fileName = webidl.converters.USVString(fileName)
|
||||
options = webidl.converters.FilePropertyBag(options)
|
||||
|
||||
// 1. Let bytes be the result of processing blob parts given fileBits and
|
||||
// options.
|
||||
// Note: Blob handles this for us
|
||||
|
||||
// 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.
|
||||
// 2. Convert every character in t to ASCII lowercase.
|
||||
let t = options.type
|
||||
let d
|
||||
|
||||
// eslint-disable-next-line no-labels
|
||||
substep: {
|
||||
if (t) {
|
||||
t = parseMIMEType(t)
|
||||
|
||||
if (t === 'failure') {
|
||||
t = ''
|
||||
// eslint-disable-next-line no-labels
|
||||
break substep
|
||||
}
|
||||
|
||||
t = serializeAMimeType(t).toLowerCase()
|
||||
}
|
||||
|
||||
// 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]).
|
||||
d = options.lastModified
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
super(processBlobParts(fileBits, options), { type: t })
|
||||
this[kState] = {
|
||||
name: n,
|
||||
lastModified: d,
|
||||
type: t
|
||||
}
|
||||
}
|
||||
|
||||
get name () {
|
||||
webidl.brandCheck(this, File)
|
||||
|
||||
return this[kState].name
|
||||
}
|
||||
|
||||
get lastModified () {
|
||||
webidl.brandCheck(this, File)
|
||||
|
||||
return this[kState].lastModified
|
||||
}
|
||||
|
||||
get type () {
|
||||
webidl.brandCheck(this, File)
|
||||
|
||||
return this[kState].type
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(File.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'File',
|
||||
configurable: true
|
||||
},
|
||||
name: kEnumerableProperty,
|
||||
lastModified: kEnumerableProperty
|
||||
})
|
||||
|
||||
webidl.converters.Blob = webidl.interfaceConverter(Blob)
|
||||
|
||||
webidl.converters.BlobPart = function (V, opts) {
|
||||
if (webidl.util.Type(V) === 'Object') {
|
||||
if (isBlobLike(V)) {
|
||||
return webidl.converters.Blob(V, { strict: false })
|
||||
}
|
||||
|
||||
if (
|
||||
ArrayBuffer.isView(V) ||
|
||||
types.isAnyArrayBuffer(V)
|
||||
) {
|
||||
return webidl.converters.BufferSource(V, opts)
|
||||
}
|
||||
}
|
||||
|
||||
return webidl.converters.USVString(V, opts)
|
||||
}
|
||||
|
||||
webidl.converters['sequence<BlobPart>'] = webidl.sequenceConverter(
|
||||
webidl.converters.BlobPart
|
||||
)
|
||||
|
||||
// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag
|
||||
webidl.converters.FilePropertyBag = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'lastModified',
|
||||
converter: webidl.converters['long long'],
|
||||
get defaultValue () {
|
||||
return Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
converter: webidl.converters.DOMString,
|
||||
defaultValue: ''
|
||||
},
|
||||
{
|
||||
key: 'endings',
|
||||
converter: (value) => {
|
||||
value = webidl.converters.DOMString(value)
|
||||
value = value.toLowerCase()
|
||||
|
||||
if (value !== 'native') {
|
||||
value = 'transparent'
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
defaultValue: 'transparent'
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/FileAPI/#process-blob-parts
|
||||
* @param {(NodeJS.TypedArray|Blob|string)[]} parts
|
||||
* @param {{ type: string, endings: string }} options
|
||||
*/
|
||||
function processBlobParts (parts, options) {
|
||||
// 1. Let bytes be an empty sequence of bytes.
|
||||
/** @type {NodeJS.TypedArray[]} */
|
||||
const bytes = []
|
||||
|
||||
// 2. For each element in parts:
|
||||
for (const element of parts) {
|
||||
// 1. If element is a USVString, run the following substeps:
|
||||
if (typeof element === 'string') {
|
||||
// 1. Let s be element.
|
||||
let s = element
|
||||
|
||||
// 2. If the endings member of options is "native", set s
|
||||
// to the result of converting line endings to native
|
||||
// of element.
|
||||
if (options.endings === 'native') {
|
||||
s = convertLineEndingsNative(s)
|
||||
}
|
||||
|
||||
// 3. Append the result of UTF-8 encoding s to bytes.
|
||||
bytes.push(new TextEncoder().encode(s))
|
||||
} else if (
|
||||
types.isAnyArrayBuffer(element) ||
|
||||
types.isTypedArray(element)
|
||||
) {
|
||||
// 2. If element is a BufferSource, get a copy of the
|
||||
// bytes held by the buffer source, and append those
|
||||
// bytes to bytes.
|
||||
if (!element.buffer) { // ArrayBuffer
|
||||
bytes.push(new Uint8Array(element))
|
||||
} else {
|
||||
bytes.push(
|
||||
new Uint8Array(element.buffer, element.byteOffset, element.byteLength)
|
||||
)
|
||||
}
|
||||
} else if (isBlobLike(element)) {
|
||||
// 3. If element is a Blob, append the bytes it represents
|
||||
// to bytes.
|
||||
bytes.push(element)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return bytes.
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native
|
||||
* @param {string} s
|
||||
*/
|
||||
function convertLineEndingsNative (s) {
|
||||
// 1. Let native line ending be be the code point U+000A LF.
|
||||
let nativeLineEnding = '\n'
|
||||
|
||||
// 2. If the underlying platform’s conventions are to
|
||||
// represent newlines as a carriage return and line feed
|
||||
// sequence, set native line ending to the code point
|
||||
// U+000D CR followed by the code point U+000A LF.
|
||||
if (process.platform === 'win32') {
|
||||
nativeLineEnding = '\r\n'
|
||||
}
|
||||
|
||||
return s.replace(/\r?\n/g, nativeLineEnding)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
(NativeFile && object instanceof NativeFile) ||
|
||||
object instanceof File || (
|
||||
object &&
|
||||
(typeof object.stream === 'function' ||
|
||||
typeof object.arrayBuffer === 'function') &&
|
||||
object[Symbol.toStringTag] === 'File'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { File, FileLike, isFileLike }
|
272
node_modules/undici/lib/fetch/formdata.js
generated
vendored
Normal file
272
node_modules/undici/lib/fetch/formdata.js
generated
vendored
Normal file
|
@ -0,0 +1,272 @@
|
|||
'use strict'
|
||||
|
||||
const { isBlobLike, toUSVString, makeIterator } = require('./util')
|
||||
const { kState } = require('./symbols')
|
||||
const { File: UndiciFile, FileLike, isFileLike } = require('./file')
|
||||
const { webidl } = require('./webidl')
|
||||
const { Blob, File: NativeFile } = require('buffer')
|
||||
|
||||
/** @type {globalThis['File']} */
|
||||
const File = NativeFile ?? UndiciFile
|
||||
|
||||
// https://xhr.spec.whatwg.org/#formdata
|
||||
class FormData {
|
||||
constructor (form) {
|
||||
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)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.append' })
|
||||
|
||||
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)
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, { strict: false })
|
||||
: webidl.converters.USVString(value)
|
||||
filename = arguments.length === 3
|
||||
? webidl.converters.USVString(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)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.delete' })
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// The delete(name) method steps are to remove all entries whose name
|
||||
// is name from this’s entry list.
|
||||
const next = []
|
||||
for (const entry of this[kState]) {
|
||||
if (entry.name !== name) {
|
||||
next.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
this[kState] = next
|
||||
}
|
||||
|
||||
get (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.get' })
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return null.
|
||||
const idx = this[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)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.getAll' })
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return the empty list.
|
||||
// 2. Return the values of all entries whose name is name, in order,
|
||||
// from this’s entry list.
|
||||
return this[kState]
|
||||
.filter((entry) => entry.name === name)
|
||||
.map((entry) => entry.value)
|
||||
}
|
||||
|
||||
has (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.has' })
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// The has(name) method steps are to return true if there is an entry
|
||||
// whose name is name in this’s entry list; otherwise false.
|
||||
return this[kState].findIndex((entry) => entry.name === name) !== -1
|
||||
}
|
||||
|
||||
set (name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.set' })
|
||||
|
||||
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)
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, { strict: false })
|
||||
: webidl.converters.USVString(value)
|
||||
filename = arguments.length === 3
|
||||
? toUSVString(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. 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)
|
||||
}
|
||||
}
|
||||
|
||||
entries () {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
return makeIterator(
|
||||
() => this[kState].map(pair => [pair.name, pair.value]),
|
||||
'FormData',
|
||||
'key+value'
|
||||
)
|
||||
}
|
||||
|
||||
keys () {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
return makeIterator(
|
||||
() => this[kState].map(pair => [pair.name, pair.value]),
|
||||
'FormData',
|
||||
'key'
|
||||
)
|
||||
}
|
||||
|
||||
values () {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
return makeIterator(
|
||||
() => this[kState].map(pair => [pair.name, pair.value]),
|
||||
'FormData',
|
||||
'value'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: string, key: string, self: FormData) => void} callbackFn
|
||||
* @param {unknown} thisArg
|
||||
*/
|
||||
forEach (callbackFn, thisArg = globalThis) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.forEach' })
|
||||
|
||||
if (typeof callbackFn !== 'function') {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'."
|
||||
)
|
||||
}
|
||||
|
||||
for (const [key, value] of this) {
|
||||
callbackFn.apply(thisArg, [value, key, this])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormData.prototype[Symbol.iterator] = FormData.prototype.entries
|
||||
|
||||
Object.defineProperties(FormData.prototype, {
|
||||
[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.
|
||||
// "To convert a string into a scalar value string, replace any surrogates
|
||||
// with U+FFFD."
|
||||
// see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end
|
||||
name = Buffer.from(name).toString('utf8')
|
||||
|
||||
// 2. If value is a string, then set value to the result of converting
|
||||
// value into a scalar value string.
|
||||
if (typeof value === 'string') {
|
||||
value = Buffer.from(value).toString('utf8')
|
||||
} 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 = (NativeFile && value instanceof NativeFile) || value instanceof UndiciFile
|
||||
? 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 }
|
48
node_modules/undici/lib/fetch/global.js
generated
vendored
Normal file
48
node_modules/undici/lib/fetch/global.js
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
'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 &&
|
||||
typeof newOrigin !== 'string' &&
|
||||
!(newOrigin instanceof URL)
|
||||
) {
|
||||
throw new Error('Invalid base url')
|
||||
}
|
||||
|
||||
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
|
||||
}
|
485
node_modules/undici/lib/fetch/headers.js
generated
vendored
Normal file
485
node_modules/undici/lib/fetch/headers.js
generated
vendored
Normal file
|
@ -0,0 +1,485 @@
|
|||
// https://github.com/Ethan-Arrowood/undici-fetch
|
||||
|
||||
'use strict'
|
||||
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const { kGuard, kHeadersCaseInsensitive } = require('./symbols')
|
||||
const { kEnumerableProperty } = require('../core/util')
|
||||
const {
|
||||
makeIterator,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue
|
||||
} = require('./util')
|
||||
const { webidl } = require('./webidl')
|
||||
|
||||
const kHeadersMap = Symbol('headers map')
|
||||
const kHeadersSortedMap = Symbol('headers map sorted')
|
||||
|
||||
/**
|
||||
* @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.
|
||||
return potentialValue.replace(
|
||||
/^[\r\n\t ]+|[\r\n\t ]+$/g,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
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 (const header of object) {
|
||||
// 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.
|
||||
headers.append(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
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
headers.append(key, value)
|
||||
}
|
||||
} else {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class HeadersList {
|
||||
/** @type {[string, string][]|null} */
|
||||
cookies = null
|
||||
|
||||
constructor (init) {
|
||||
if (init instanceof HeadersList) {
|
||||
this[kHeadersMap] = new Map(init[kHeadersMap])
|
||||
this[kHeadersSortedMap] = init[kHeadersSortedMap]
|
||||
} else {
|
||||
this[kHeadersMap] = new Map(init)
|
||||
this[kHeadersSortedMap] = null
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-list-contains
|
||||
contains (name) {
|
||||
// A header list list contains a header name name if list
|
||||
// contains a header whose name is a byte-case-insensitive
|
||||
// match for name.
|
||||
name = name.toLowerCase()
|
||||
|
||||
return this[kHeadersMap].has(name)
|
||||
}
|
||||
|
||||
clear () {
|
||||
this[kHeadersMap].clear()
|
||||
this[kHeadersSortedMap] = null
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-append
|
||||
append (name, value) {
|
||||
this[kHeadersSortedMap] = null
|
||||
|
||||
// 1. If list contains name, then set name to the first such
|
||||
// header’s name.
|
||||
const lowercaseName = name.toLowerCase()
|
||||
const exists = this[kHeadersMap].get(lowercaseName)
|
||||
|
||||
// 2. Append (name, value) to list.
|
||||
if (exists) {
|
||||
this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` })
|
||||
} else {
|
||||
this[kHeadersMap].set(lowercaseName, { name, value })
|
||||
}
|
||||
|
||||
if (lowercaseName === 'set-cookie') {
|
||||
this.cookies ??= []
|
||||
this.cookies.push([name, value])
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-set
|
||||
set (name, value) {
|
||||
this[kHeadersSortedMap] = null
|
||||
const lowercaseName = name.toLowerCase()
|
||||
|
||||
if (lowercaseName === 'set-cookie') {
|
||||
this.cookies = [[name, 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.
|
||||
return this[kHeadersMap].set(lowercaseName, { name, value })
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
||||
delete (name) {
|
||||
this[kHeadersSortedMap] = null
|
||||
|
||||
name = name.toLowerCase()
|
||||
|
||||
if (name === 'set-cookie') {
|
||||
this.cookies = null
|
||||
}
|
||||
|
||||
return this[kHeadersMap].delete(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
||||
get (name) {
|
||||
// 1. If list does not contain name, then return null.
|
||||
if (!this.contains(name)) {
|
||||
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(name.toLowerCase())?.value ?? null
|
||||
}
|
||||
|
||||
* [Symbol.iterator] () {
|
||||
// use the lowercased name
|
||||
for (const [name, { value }] of this[kHeadersMap]) {
|
||||
yield [name, value]
|
||||
}
|
||||
}
|
||||
|
||||
get [kHeadersCaseInsensitive] () {
|
||||
/** @type {string[]} */
|
||||
const flatList = []
|
||||
|
||||
for (const { name, value } of this[kHeadersMap].values()) {
|
||||
flatList.push(name, value)
|
||||
}
|
||||
|
||||
return flatList
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#headers-class
|
||||
class Headers {
|
||||
constructor (init = undefined) {
|
||||
this[kHeadersList] = new HeadersList()
|
||||
|
||||
// The new Headers(init) constructor steps are:
|
||||
|
||||
// 1. Set this’s guard to "none".
|
||||
this[kGuard] = 'none'
|
||||
|
||||
// 2. If init is given, then fill this with init.
|
||||
if (init !== undefined) {
|
||||
init = webidl.converters.HeadersInit(init)
|
||||
fill(this, init)
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-append
|
||||
append (name, value) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.append' })
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
value = webidl.converters.ByteString(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.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// 5. Otherwise, if headers’s guard is "request-no-cors":
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 8. If headers’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from headers
|
||||
return this[kHeadersList].append(name, value)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
||||
delete (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.delete' })
|
||||
|
||||
name = webidl.converters.ByteString(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[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 6. If this’s header list does not contain name, then
|
||||
// return.
|
||||
if (!this[kHeadersList].contains(name)) {
|
||||
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.
|
||||
return this[kHeadersList].delete(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-get
|
||||
get (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.get' })
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.get',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Return the result of getting name from this’s header
|
||||
// list.
|
||||
return this[kHeadersList].get(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-has
|
||||
has (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.has' })
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.has',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Return true if this’s header list contains name;
|
||||
// otherwise false.
|
||||
return this[kHeadersList].contains(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-set
|
||||
set (name, value) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.set' })
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
value = webidl.converters.ByteString(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.set',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
} else if (!isValidHeaderValue(value)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.set',
|
||||
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[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 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
|
||||
return this[kHeadersList].set(name, value)
|
||||
}
|
||||
|
||||
get [kHeadersSortedMap] () {
|
||||
if (!this[kHeadersList][kHeadersSortedMap]) {
|
||||
this[kHeadersList][kHeadersSortedMap] = new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1))
|
||||
}
|
||||
return this[kHeadersList][kHeadersSortedMap]
|
||||
}
|
||||
|
||||
keys () {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
return makeIterator(
|
||||
() => [...this[kHeadersSortedMap].entries()],
|
||||
'Headers',
|
||||
'key'
|
||||
)
|
||||
}
|
||||
|
||||
values () {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
return makeIterator(
|
||||
() => [...this[kHeadersSortedMap].entries()],
|
||||
'Headers',
|
||||
'value'
|
||||
)
|
||||
}
|
||||
|
||||
entries () {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
return makeIterator(
|
||||
() => [...this[kHeadersSortedMap].entries()],
|
||||
'Headers',
|
||||
'key+value'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: string, key: string, self: Headers) => void} callbackFn
|
||||
* @param {unknown} thisArg
|
||||
*/
|
||||
forEach (callbackFn, thisArg = globalThis) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.forEach' })
|
||||
|
||||
if (typeof callbackFn !== 'function') {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'."
|
||||
)
|
||||
}
|
||||
|
||||
for (const [key, value] of this) {
|
||||
callbackFn.apply(thisArg, [value, key, this])
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.for('nodejs.util.inspect.custom')] () {
|
||||
webidl.brandCheck(this, Headers)
|
||||
|
||||
return this[kHeadersList]
|
||||
}
|
||||
}
|
||||
|
||||
Headers.prototype[Symbol.iterator] = Headers.prototype.entries
|
||||
|
||||
Object.defineProperties(Headers.prototype, {
|
||||
append: kEnumerableProperty,
|
||||
delete: kEnumerableProperty,
|
||||
get: kEnumerableProperty,
|
||||
has: kEnumerableProperty,
|
||||
set: kEnumerableProperty,
|
||||
keys: kEnumerableProperty,
|
||||
values: kEnumerableProperty,
|
||||
entries: kEnumerableProperty,
|
||||
forEach: kEnumerableProperty,
|
||||
[Symbol.iterator]: { enumerable: false },
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'Headers',
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
|
||||
webidl.converters.HeadersInit = function (V) {
|
||||
if (webidl.util.Type(V) === 'Object') {
|
||||
if (V[Symbol.iterator]) {
|
||||
return webidl.converters['sequence<sequence<ByteString>>'](V)
|
||||
}
|
||||
|
||||
return webidl.converters['record<ByteString, ByteString>'](V)
|
||||
}
|
||||
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fill,
|
||||
Headers,
|
||||
HeadersList
|
||||
}
|
2108
node_modules/undici/lib/fetch/index.js
generated
vendored
Normal file
2108
node_modules/undici/lib/fetch/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
910
node_modules/undici/lib/fetch/request.js
generated
vendored
Normal file
910
node_modules/undici/lib/fetch/request.js
generated
vendored
Normal file
|
@ -0,0 +1,910 @@
|
|||
/* globals AbortController */
|
||||
|
||||
'use strict'
|
||||
|
||||
const { extractBody, mixinBody, cloneBody } = require('./body')
|
||||
const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
|
||||
const { FinalizationRegistry } = require('../compat/dispatcher-weakref')()
|
||||
const util = require('../core/util')
|
||||
const {
|
||||
isValidHTTPToken,
|
||||
sameOrigin,
|
||||
normalizeMethod
|
||||
} = require('./util')
|
||||
const {
|
||||
forbiddenMethods,
|
||||
corsSafeListedMethods,
|
||||
referrerPolicy,
|
||||
requestRedirect,
|
||||
requestMode,
|
||||
requestCredentials,
|
||||
requestCache,
|
||||
requestDuplex
|
||||
} = require('./constants')
|
||||
const { kEnumerableProperty } = util
|
||||
const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { getGlobalOrigin } = require('./global')
|
||||
const { URLSerializer } = require('./dataURL')
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
|
||||
let TransformStream = globalThis.TransformStream
|
||||
|
||||
const kInit = Symbol('init')
|
||||
|
||||
const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
|
||||
signal.removeEventListener('abort', abort)
|
||||
})
|
||||
|
||||
// https://fetch.spec.whatwg.org/#request-class
|
||||
class Request {
|
||||
// https://fetch.spec.whatwg.org/#dom-request
|
||||
constructor (input, init = {}) {
|
||||
if (input === kInit) {
|
||||
return
|
||||
}
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' })
|
||||
|
||||
input = webidl.converters.RequestInfo(input)
|
||||
init = webidl.converters.RequestInit(init)
|
||||
|
||||
// TODO
|
||||
this[kRealm] = {
|
||||
settingsObject: {
|
||||
baseUrl: getGlobalOrigin()
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Let request be null.
|
||||
let request = null
|
||||
|
||||
// 2. Let fallbackMode be null.
|
||||
let fallbackMode = null
|
||||
|
||||
// 3. Let baseURL be this’s relevant settings object’s API base URL.
|
||||
const baseUrl = this[kRealm].settingsObject.baseUrl
|
||||
|
||||
// 4. Let signal be null.
|
||||
let signal = null
|
||||
|
||||
// 5. If input is a string, then:
|
||||
if (typeof input === 'string') {
|
||||
// 1. Let parsedURL be the result of parsing input with baseURL.
|
||||
// 2. If parsedURL is failure, then throw a TypeError.
|
||||
let parsedURL
|
||||
try {
|
||||
parsedURL = new URL(input, baseUrl)
|
||||
} catch (err) {
|
||||
throw new TypeError('Failed to parse URL from ' + input, { cause: err })
|
||||
}
|
||||
|
||||
// 3. If parsedURL includes credentials, then throw a TypeError.
|
||||
if (parsedURL.username || parsedURL.password) {
|
||||
throw new TypeError(
|
||||
'Request cannot be constructed from a URL that includes credentials: ' +
|
||||
input
|
||||
)
|
||||
}
|
||||
|
||||
// 4. Set request to a new request whose URL is parsedURL.
|
||||
request = makeRequest({ urlList: [parsedURL] })
|
||||
|
||||
// 5. Set fallbackMode to "cors".
|
||||
fallbackMode = 'cors'
|
||||
} else {
|
||||
// 6. Otherwise:
|
||||
|
||||
// 7. Assert: input is a Request object.
|
||||
assert(input instanceof Request)
|
||||
|
||||
// 8. Set request to input’s request.
|
||||
request = input[kState]
|
||||
|
||||
// 9. Set signal to input’s signal.
|
||||
signal = input[kSignal]
|
||||
}
|
||||
|
||||
// 7. Let origin be this’s relevant settings object’s origin.
|
||||
const origin = this[kRealm].settingsObject.origin
|
||||
|
||||
// 8. Let window be "client".
|
||||
let window = 'client'
|
||||
|
||||
// 9. If request’s window is an environment settings object and its origin
|
||||
// is same origin with origin, then set window to request’s window.
|
||||
if (
|
||||
request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
|
||||
sameOrigin(request.window, origin)
|
||||
) {
|
||||
window = request.window
|
||||
}
|
||||
|
||||
// 10. If init["window"] exists and is non-null, then throw a TypeError.
|
||||
if (init.window !== undefined && init.window != null) {
|
||||
throw new TypeError(`'window' option '${window}' must be null`)
|
||||
}
|
||||
|
||||
// 11. If init["window"] exists, then set window to "no-window".
|
||||
if (init.window !== undefined) {
|
||||
window = 'no-window'
|
||||
}
|
||||
|
||||
// 12. Set request to a new request with the following properties:
|
||||
request = makeRequest({
|
||||
// URL request’s URL.
|
||||
// undici implementation note: this is set as the first item in request's urlList in makeRequest
|
||||
// method request’s method.
|
||||
method: request.method,
|
||||
// header list A copy of request’s header list.
|
||||
// undici implementation note: headersList is cloned in makeRequest
|
||||
headersList: request.headersList,
|
||||
// unsafe-request flag Set.
|
||||
unsafeRequest: request.unsafeRequest,
|
||||
// client This’s relevant settings object.
|
||||
client: this[kRealm].settingsObject,
|
||||
// window window.
|
||||
window,
|
||||
// priority request’s priority.
|
||||
priority: request.priority,
|
||||
// origin request’s origin. The propagation of the origin is only significant for navigation requests
|
||||
// being handled by a service worker. In this scenario a request can have an origin that is different
|
||||
// from the current client.
|
||||
origin: request.origin,
|
||||
// referrer request’s referrer.
|
||||
referrer: request.referrer,
|
||||
// referrer policy request’s referrer policy.
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
// mode request’s mode.
|
||||
mode: request.mode,
|
||||
// credentials mode request’s credentials mode.
|
||||
credentials: request.credentials,
|
||||
// cache mode request’s cache mode.
|
||||
cache: request.cache,
|
||||
// redirect mode request’s redirect mode.
|
||||
redirect: request.redirect,
|
||||
// integrity metadata request’s integrity metadata.
|
||||
integrity: request.integrity,
|
||||
// keepalive request’s keepalive.
|
||||
keepalive: request.keepalive,
|
||||
// reload-navigation flag request’s reload-navigation flag.
|
||||
reloadNavigation: request.reloadNavigation,
|
||||
// history-navigation flag request’s history-navigation flag.
|
||||
historyNavigation: request.historyNavigation,
|
||||
// URL list A clone of request’s URL list.
|
||||
urlList: [...request.urlList]
|
||||
})
|
||||
|
||||
// 13. If init is not empty, then:
|
||||
if (Object.keys(init).length > 0) {
|
||||
// 1. If request’s mode is "navigate", then set it to "same-origin".
|
||||
if (request.mode === 'navigate') {
|
||||
request.mode = 'same-origin'
|
||||
}
|
||||
|
||||
// 2. Unset request’s reload-navigation flag.
|
||||
request.reloadNavigation = false
|
||||
|
||||
// 3. Unset request’s history-navigation flag.
|
||||
request.historyNavigation = false
|
||||
|
||||
// 4. Set request’s origin to "client".
|
||||
request.origin = 'client'
|
||||
|
||||
// 5. Set request’s referrer to "client"
|
||||
request.referrer = 'client'
|
||||
|
||||
// 6. Set request’s referrer policy to the empty string.
|
||||
request.referrerPolicy = ''
|
||||
|
||||
// 7. Set request’s URL to request’s current URL.
|
||||
request.url = request.urlList[request.urlList.length - 1]
|
||||
|
||||
// 8. Set request’s URL list to « request’s URL ».
|
||||
request.urlList = [request.url]
|
||||
}
|
||||
|
||||
// 14. If init["referrer"] exists, then:
|
||||
if (init.referrer !== undefined) {
|
||||
// 1. Let referrer be init["referrer"].
|
||||
const referrer = init.referrer
|
||||
|
||||
// 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
|
||||
if (referrer === '') {
|
||||
request.referrer = 'no-referrer'
|
||||
} else {
|
||||
// 1. Let parsedReferrer be the result of parsing referrer with
|
||||
// baseURL.
|
||||
// 2. If parsedReferrer is failure, then throw a TypeError.
|
||||
let parsedReferrer
|
||||
try {
|
||||
parsedReferrer = new URL(referrer, baseUrl)
|
||||
} catch (err) {
|
||||
throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
|
||||
}
|
||||
|
||||
// 3. If one of the following is true
|
||||
// parsedReferrer’s cannot-be-a-base-URL is true, scheme is "about",
|
||||
// and path contains a single string "client"
|
||||
// parsedReferrer’s origin is not same origin with origin
|
||||
// then set request’s referrer to "client".
|
||||
// TODO
|
||||
|
||||
// 4. Otherwise, set request’s referrer to parsedReferrer.
|
||||
request.referrer = parsedReferrer
|
||||
}
|
||||
}
|
||||
|
||||
// 15. If init["referrerPolicy"] exists, then set request’s referrer policy
|
||||
// to it.
|
||||
if (init.referrerPolicy !== undefined) {
|
||||
request.referrerPolicy = init.referrerPolicy
|
||||
}
|
||||
|
||||
// 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
|
||||
let mode
|
||||
if (init.mode !== undefined) {
|
||||
mode = init.mode
|
||||
} else {
|
||||
mode = fallbackMode
|
||||
}
|
||||
|
||||
// 17. If mode is "navigate", then throw a TypeError.
|
||||
if (mode === 'navigate') {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Request constructor',
|
||||
message: 'invalid request mode navigate.'
|
||||
})
|
||||
}
|
||||
|
||||
// 18. If mode is non-null, set request’s mode to mode.
|
||||
if (mode != null) {
|
||||
request.mode = mode
|
||||
}
|
||||
|
||||
// 19. If init["credentials"] exists, then set request’s credentials mode
|
||||
// to it.
|
||||
if (init.credentials !== undefined) {
|
||||
request.credentials = init.credentials
|
||||
}
|
||||
|
||||
// 18. If init["cache"] exists, then set request’s cache mode to it.
|
||||
if (init.cache !== undefined) {
|
||||
request.cache = init.cache
|
||||
}
|
||||
|
||||
// 21. If request’s cache mode is "only-if-cached" and request’s mode is
|
||||
// not "same-origin", then throw a TypeError.
|
||||
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
||||
throw new TypeError(
|
||||
"'only-if-cached' can be set only with 'same-origin' mode"
|
||||
)
|
||||
}
|
||||
|
||||
// 22. If init["redirect"] exists, then set request’s redirect mode to it.
|
||||
if (init.redirect !== undefined) {
|
||||
request.redirect = init.redirect
|
||||
}
|
||||
|
||||
// 23. If init["integrity"] exists, then set request’s integrity metadata to it.
|
||||
if (init.integrity !== undefined && init.integrity != null) {
|
||||
request.integrity = String(init.integrity)
|
||||
}
|
||||
|
||||
// 24. If init["keepalive"] exists, then set request’s keepalive to it.
|
||||
if (init.keepalive !== undefined) {
|
||||
request.keepalive = Boolean(init.keepalive)
|
||||
}
|
||||
|
||||
// 25. If init["method"] exists, then:
|
||||
if (init.method !== undefined) {
|
||||
// 1. Let method be init["method"].
|
||||
let method = init.method
|
||||
|
||||
// 2. If method is not a method or method is a forbidden method, then
|
||||
// throw a TypeError.
|
||||
if (!isValidHTTPToken(init.method)) {
|
||||
throw TypeError(`'${init.method}' is not a valid HTTP method.`)
|
||||
}
|
||||
|
||||
if (forbiddenMethods.indexOf(method.toUpperCase()) !== -1) {
|
||||
throw TypeError(`'${init.method}' HTTP method is unsupported.`)
|
||||
}
|
||||
|
||||
// 3. Normalize method.
|
||||
method = normalizeMethod(init.method)
|
||||
|
||||
// 4. Set request’s method to method.
|
||||
request.method = method
|
||||
}
|
||||
|
||||
// 26. If init["signal"] exists, then set signal to it.
|
||||
if (init.signal !== undefined) {
|
||||
signal = init.signal
|
||||
}
|
||||
|
||||
// 27. Set this’s request to request.
|
||||
this[kState] = request
|
||||
|
||||
// 28. Set this’s signal to a new AbortSignal object with this’s relevant
|
||||
// Realm.
|
||||
const ac = new AbortController()
|
||||
this[kSignal] = ac.signal
|
||||
this[kSignal][kRealm] = this[kRealm]
|
||||
|
||||
// 29. If signal is not null, then make this’s signal follow signal.
|
||||
if (signal != null) {
|
||||
if (
|
||||
!signal ||
|
||||
typeof signal.aborted !== 'boolean' ||
|
||||
typeof signal.addEventListener !== 'function'
|
||||
) {
|
||||
throw new TypeError(
|
||||
"Failed to construct 'Request': member signal is not of type AbortSignal."
|
||||
)
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
ac.abort(signal.reason)
|
||||
} else {
|
||||
const acRef = new WeakRef(ac)
|
||||
const abort = function () {
|
||||
acRef.deref()?.abort(this.reason)
|
||||
}
|
||||
signal.addEventListener('abort', abort, { once: true })
|
||||
requestFinalizer.register(this, { signal, abort })
|
||||
}
|
||||
}
|
||||
|
||||
// 30. Set this’s headers to a new Headers object with this’s relevant
|
||||
// Realm, whose header list is request’s header list and guard is
|
||||
// "request".
|
||||
this[kHeaders] = new Headers()
|
||||
this[kHeaders][kHeadersList] = request.headersList
|
||||
this[kHeaders][kGuard] = 'request'
|
||||
this[kHeaders][kRealm] = this[kRealm]
|
||||
|
||||
// 31. If this’s request’s mode is "no-cors", then:
|
||||
if (mode === 'no-cors') {
|
||||
// 1. If this’s request’s method is not a CORS-safelisted method,
|
||||
// then throw a TypeError.
|
||||
if (!corsSafeListedMethods.includes(request.method)) {
|
||||
throw new TypeError(
|
||||
`'${request.method} is unsupported in no-cors mode.`
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Set this’s headers’s guard to "request-no-cors".
|
||||
this[kHeaders][kGuard] = 'request-no-cors'
|
||||
}
|
||||
|
||||
// 32. If init is not empty, then:
|
||||
if (Object.keys(init).length !== 0) {
|
||||
// 1. Let headers be a copy of this’s headers and its associated header
|
||||
// list.
|
||||
let headers = new Headers(this[kHeaders])
|
||||
|
||||
// 2. If init["headers"] exists, then set headers to init["headers"].
|
||||
if (init.headers !== undefined) {
|
||||
headers = init.headers
|
||||
}
|
||||
|
||||
// 3. Empty this’s headers’s header list.
|
||||
this[kHeaders][kHeadersList].clear()
|
||||
|
||||
// 4. If headers is a Headers object, then for each header in its header
|
||||
// list, append header’s name/header’s value to this’s headers.
|
||||
if (headers.constructor.name === 'Headers') {
|
||||
for (const [key, val] of headers) {
|
||||
this[kHeaders].append(key, val)
|
||||
}
|
||||
} else {
|
||||
// 5. Otherwise, fill this’s headers with headers.
|
||||
fillHeaders(this[kHeaders], headers)
|
||||
}
|
||||
}
|
||||
|
||||
// 33. Let inputBody be input’s request’s body if input is a Request
|
||||
// object; otherwise null.
|
||||
const inputBody = input instanceof Request ? input[kState].body : null
|
||||
|
||||
// 34. If either init["body"] exists and is non-null or inputBody is
|
||||
// non-null, and request’s method is `GET` or `HEAD`, then throw a
|
||||
// TypeError.
|
||||
if (
|
||||
((init.body !== undefined && init.body != null) || inputBody != null) &&
|
||||
(request.method === 'GET' || request.method === 'HEAD')
|
||||
) {
|
||||
throw new TypeError('Request with GET/HEAD method cannot have body.')
|
||||
}
|
||||
|
||||
// 35. Let initBody be null.
|
||||
let initBody = null
|
||||
|
||||
// 36. If init["body"] exists and is non-null, then:
|
||||
if (init.body !== undefined && init.body != null) {
|
||||
// 1. Let Content-Type be null.
|
||||
// 2. Set initBody and Content-Type to the result of extracting
|
||||
// init["body"], with keepalive set to request’s keepalive.
|
||||
const [extractedBody, contentType] = extractBody(
|
||||
init.body,
|
||||
request.keepalive
|
||||
)
|
||||
initBody = extractedBody
|
||||
|
||||
// 3, If Content-Type is non-null and this’s headers’s header list does
|
||||
// not contain `Content-Type`, then append `Content-Type`/Content-Type to
|
||||
// this’s headers.
|
||||
if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) {
|
||||
this[kHeaders].append('content-type', contentType)
|
||||
}
|
||||
}
|
||||
|
||||
// 37. Let inputOrInitBody be initBody if it is non-null; otherwise
|
||||
// inputBody.
|
||||
const inputOrInitBody = initBody ?? inputBody
|
||||
|
||||
// 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
|
||||
// null, then:
|
||||
if (inputOrInitBody != null && inputOrInitBody.source == null) {
|
||||
// 1. If initBody is non-null and init["duplex"] does not exist,
|
||||
// then throw a TypeError.
|
||||
if (initBody != null && init.duplex == null) {
|
||||
throw new TypeError('RequestInit: duplex option is required when sending a body.')
|
||||
}
|
||||
|
||||
// 2. If this’s request’s mode is neither "same-origin" nor "cors",
|
||||
// then throw a TypeError.
|
||||
if (request.mode !== 'same-origin' && request.mode !== 'cors') {
|
||||
throw new TypeError(
|
||||
'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
|
||||
)
|
||||
}
|
||||
|
||||
// 3. Set this’s request’s use-CORS-preflight flag.
|
||||
request.useCORSPreflightFlag = true
|
||||
}
|
||||
|
||||
// 39. Let finalBody be inputOrInitBody.
|
||||
let finalBody = inputOrInitBody
|
||||
|
||||
// 40. If initBody is null and inputBody is non-null, then:
|
||||
if (initBody == null && inputBody != null) {
|
||||
// 1. If input is unusable, then throw a TypeError.
|
||||
if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
|
||||
throw new TypeError(
|
||||
'Cannot construct a Request with a Request object that has already been used.'
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Set finalBody to the result of creating a proxy for inputBody.
|
||||
if (!TransformStream) {
|
||||
TransformStream = require('stream/web').TransformStream
|
||||
}
|
||||
|
||||
// https://streams.spec.whatwg.org/#readablestream-create-a-proxy
|
||||
const identityTransform = new TransformStream()
|
||||
inputBody.stream.pipeThrough(identityTransform)
|
||||
finalBody = {
|
||||
source: inputBody.source,
|
||||
length: inputBody.length,
|
||||
stream: identityTransform.readable
|
||||
}
|
||||
}
|
||||
|
||||
// 41. Set this’s request’s body to finalBody.
|
||||
this[kState].body = finalBody
|
||||
}
|
||||
|
||||
// Returns request’s HTTP method, which is "GET" by default.
|
||||
get method () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The method getter steps are to return this’s request’s method.
|
||||
return this[kState].method
|
||||
}
|
||||
|
||||
// Returns the URL of request as a string.
|
||||
get url () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The url getter steps are to return this’s request’s URL, serialized.
|
||||
return URLSerializer(this[kState].url)
|
||||
}
|
||||
|
||||
// Returns a Headers object consisting of the headers associated with request.
|
||||
// Note that headers added in the network layer by the user agent will not
|
||||
// be accounted for in this object, e.g., the "Host" header.
|
||||
get headers () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The headers getter steps are to return this’s headers.
|
||||
return this[kHeaders]
|
||||
}
|
||||
|
||||
// Returns the kind of resource requested by request, e.g., "document"
|
||||
// or "script".
|
||||
get destination () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The destination getter are to return this’s request’s destination.
|
||||
return this[kState].destination
|
||||
}
|
||||
|
||||
// Returns the referrer of request. Its value can be a same-origin URL if
|
||||
// explicitly set in init, the empty string to indicate no referrer, and
|
||||
// "about:client" when defaulting to the global’s default. This is used
|
||||
// during fetching to determine the value of the `Referer` header of the
|
||||
// request being made.
|
||||
get referrer () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// 1. If this’s request’s referrer is "no-referrer", then return the
|
||||
// empty string.
|
||||
if (this[kState].referrer === 'no-referrer') {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 2. If this’s request’s referrer is "client", then return
|
||||
// "about:client".
|
||||
if (this[kState].referrer === 'client') {
|
||||
return 'about:client'
|
||||
}
|
||||
|
||||
// Return this’s request’s referrer, serialized.
|
||||
return this[kState].referrer.toString()
|
||||
}
|
||||
|
||||
// Returns the referrer policy associated with request.
|
||||
// This is used during fetching to compute the value of the request’s
|
||||
// referrer.
|
||||
get referrerPolicy () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The referrerPolicy getter steps are to return this’s request’s referrer policy.
|
||||
return this[kState].referrerPolicy
|
||||
}
|
||||
|
||||
// Returns the mode associated with request, which is a string indicating
|
||||
// whether the request will use CORS, or will be restricted to same-origin
|
||||
// URLs.
|
||||
get mode () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The mode getter steps are to return this’s request’s mode.
|
||||
return this[kState].mode
|
||||
}
|
||||
|
||||
// Returns the credentials mode associated with request,
|
||||
// which is a string indicating whether credentials will be sent with the
|
||||
// request always, never, or only when sent to a same-origin URL.
|
||||
get credentials () {
|
||||
// The credentials getter steps are to return this’s request’s credentials mode.
|
||||
return this[kState].credentials
|
||||
}
|
||||
|
||||
// Returns the cache mode associated with request,
|
||||
// which is a string indicating how the request will
|
||||
// interact with the browser’s cache when fetching.
|
||||
get cache () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The cache getter steps are to return this’s request’s cache mode.
|
||||
return this[kState].cache
|
||||
}
|
||||
|
||||
// Returns the redirect mode associated with request,
|
||||
// which is a string indicating how redirects for the
|
||||
// request will be handled during fetching. A request
|
||||
// will follow redirects by default.
|
||||
get redirect () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The redirect getter steps are to return this’s request’s redirect mode.
|
||||
return this[kState].redirect
|
||||
}
|
||||
|
||||
// Returns request’s subresource integrity metadata, which is a
|
||||
// cryptographic hash of the resource being fetched. Its value
|
||||
// consists of multiple hashes separated by whitespace. [SRI]
|
||||
get integrity () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The integrity getter steps are to return this’s request’s integrity
|
||||
// metadata.
|
||||
return this[kState].integrity
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request can outlive the
|
||||
// global in which it was created.
|
||||
get keepalive () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The keepalive getter steps are to return this’s request’s keepalive.
|
||||
return this[kState].keepalive
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request is for a reload
|
||||
// navigation.
|
||||
get isReloadNavigation () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The isReloadNavigation getter steps are to return true if this’s
|
||||
// request’s reload-navigation flag is set; otherwise false.
|
||||
return this[kState].reloadNavigation
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request is for a history
|
||||
// navigation (a.k.a. back-foward navigation).
|
||||
get isHistoryNavigation () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The isHistoryNavigation getter steps are to return true if this’s request’s
|
||||
// history-navigation flag is set; otherwise false.
|
||||
return this[kState].historyNavigation
|
||||
}
|
||||
|
||||
// Returns the signal associated with request, which is an AbortSignal
|
||||
// object indicating whether or not request has been aborted, and its
|
||||
// abort event handler.
|
||||
get signal () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// The signal getter steps are to return this’s signal.
|
||||
return this[kSignal]
|
||||
}
|
||||
|
||||
get body () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
return this[kState].body ? this[kState].body.stream : null
|
||||
}
|
||||
|
||||
get bodyUsed () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
|
||||
}
|
||||
|
||||
get duplex () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
return 'half'
|
||||
}
|
||||
|
||||
// Returns a clone of request.
|
||||
clone () {
|
||||
webidl.brandCheck(this, Request)
|
||||
|
||||
// 1. If this is unusable, then throw a TypeError.
|
||||
if (this.bodyUsed || this.body?.locked) {
|
||||
throw new TypeError('unusable')
|
||||
}
|
||||
|
||||
// 2. Let clonedRequest be the result of cloning this’s request.
|
||||
const clonedRequest = cloneRequest(this[kState])
|
||||
|
||||
// 3. Let clonedRequestObject be the result of creating a Request object,
|
||||
// given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
|
||||
const clonedRequestObject = new Request(kInit)
|
||||
clonedRequestObject[kState] = clonedRequest
|
||||
clonedRequestObject[kRealm] = this[kRealm]
|
||||
clonedRequestObject[kHeaders] = new Headers()
|
||||
clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
|
||||
clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
|
||||
clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
|
||||
|
||||
// 4. Make clonedRequestObject’s signal follow this’s signal.
|
||||
const ac = new AbortController()
|
||||
if (this.signal.aborted) {
|
||||
ac.abort(this.signal.reason)
|
||||
} else {
|
||||
this.signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
ac.abort(this.signal.reason)
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
}
|
||||
clonedRequestObject[kSignal] = ac.signal
|
||||
|
||||
// 4. Return clonedRequestObject.
|
||||
return clonedRequestObject
|
||||
}
|
||||
}
|
||||
|
||||
mixinBody(Request)
|
||||
|
||||
function makeRequest (init) {
|
||||
// https://fetch.spec.whatwg.org/#requests
|
||||
const request = {
|
||||
method: 'GET',
|
||||
localURLsOnly: false,
|
||||
unsafeRequest: false,
|
||||
body: null,
|
||||
client: null,
|
||||
reservedClient: null,
|
||||
replacesClientId: '',
|
||||
window: 'client',
|
||||
keepalive: false,
|
||||
serviceWorkers: 'all',
|
||||
initiator: '',
|
||||
destination: '',
|
||||
priority: null,
|
||||
origin: 'client',
|
||||
policyContainer: 'client',
|
||||
referrer: 'client',
|
||||
referrerPolicy: '',
|
||||
mode: 'no-cors',
|
||||
useCORSPreflightFlag: false,
|
||||
credentials: 'same-origin',
|
||||
useCredentials: false,
|
||||
cache: 'default',
|
||||
redirect: 'follow',
|
||||
integrity: '',
|
||||
cryptoGraphicsNonceMetadata: '',
|
||||
parserMetadata: '',
|
||||
reloadNavigation: false,
|
||||
historyNavigation: false,
|
||||
userActivation: false,
|
||||
taintedOrigin: false,
|
||||
redirectCount: 0,
|
||||
responseTainting: 'basic',
|
||||
preventNoCacheCacheControlHeaderModification: false,
|
||||
done: false,
|
||||
timingAllowFailed: false,
|
||||
...init,
|
||||
headersList: init.headersList
|
||||
? new HeadersList(init.headersList)
|
||||
: new HeadersList()
|
||||
}
|
||||
request.url = request.urlList[0]
|
||||
return request
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-request-clone
|
||||
function cloneRequest (request) {
|
||||
// To clone a request request, run these steps:
|
||||
|
||||
// 1. Let newRequest be a copy of request, except for its body.
|
||||
const newRequest = makeRequest({ ...request, body: null })
|
||||
|
||||
// 2. If request’s body is non-null, set newRequest’s body to the
|
||||
// result of cloning request’s body.
|
||||
if (request.body != null) {
|
||||
newRequest.body = cloneBody(request.body)
|
||||
}
|
||||
|
||||
// 3. Return newRequest.
|
||||
return newRequest
|
||||
}
|
||||
|
||||
Object.defineProperties(Request.prototype, {
|
||||
method: kEnumerableProperty,
|
||||
url: kEnumerableProperty,
|
||||
headers: kEnumerableProperty,
|
||||
redirect: kEnumerableProperty,
|
||||
clone: kEnumerableProperty,
|
||||
signal: kEnumerableProperty,
|
||||
duplex: kEnumerableProperty,
|
||||
destination: kEnumerableProperty,
|
||||
body: kEnumerableProperty,
|
||||
bodyUsed: kEnumerableProperty,
|
||||
isHistoryNavigation: kEnumerableProperty,
|
||||
isReloadNavigation: kEnumerableProperty,
|
||||
keepalive: kEnumerableProperty,
|
||||
integrity: kEnumerableProperty,
|
||||
cache: kEnumerableProperty,
|
||||
credentials: kEnumerableProperty,
|
||||
attribute: kEnumerableProperty,
|
||||
referrerPolicy: kEnumerableProperty,
|
||||
referrer: kEnumerableProperty,
|
||||
mode: kEnumerableProperty,
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'Request',
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
|
||||
webidl.converters.Request = webidl.interfaceConverter(
|
||||
Request
|
||||
)
|
||||
|
||||
// https://fetch.spec.whatwg.org/#requestinfo
|
||||
webidl.converters.RequestInfo = function (V) {
|
||||
if (typeof V === 'string') {
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
if (V instanceof Request) {
|
||||
return webidl.converters.Request(V)
|
||||
}
|
||||
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
webidl.converters.AbortSignal = webidl.interfaceConverter(
|
||||
AbortSignal
|
||||
)
|
||||
|
||||
// https://fetch.spec.whatwg.org/#requestinit
|
||||
webidl.converters.RequestInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'method',
|
||||
converter: webidl.converters.ByteString
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.converters.HeadersInit
|
||||
},
|
||||
{
|
||||
key: 'body',
|
||||
converter: webidl.nullableConverter(
|
||||
webidl.converters.BodyInit
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'referrer',
|
||||
converter: webidl.converters.USVString
|
||||
},
|
||||
{
|
||||
key: 'referrerPolicy',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
|
||||
allowedValues: referrerPolicy
|
||||
},
|
||||
{
|
||||
key: 'mode',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#concept-request-mode
|
||||
allowedValues: requestMode
|
||||
},
|
||||
{
|
||||
key: 'credentials',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestcredentials
|
||||
allowedValues: requestCredentials
|
||||
},
|
||||
{
|
||||
key: 'cache',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestcache
|
||||
allowedValues: requestCache
|
||||
},
|
||||
{
|
||||
key: 'redirect',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestredirect
|
||||
allowedValues: requestRedirect
|
||||
},
|
||||
{
|
||||
key: 'integrity',
|
||||
converter: webidl.converters.DOMString
|
||||
},
|
||||
{
|
||||
key: 'keepalive',
|
||||
converter: webidl.converters.boolean
|
||||
},
|
||||
{
|
||||
key: 'signal',
|
||||
converter: webidl.nullableConverter(
|
||||
(signal) => webidl.converters.AbortSignal(
|
||||
signal,
|
||||
{ strict: false }
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'window',
|
||||
converter: webidl.converters.any
|
||||
},
|
||||
{
|
||||
key: 'duplex',
|
||||
converter: webidl.converters.DOMString,
|
||||
allowedValues: requestDuplex
|
||||
}
|
||||
])
|
||||
|
||||
module.exports = { Request, makeRequest }
|
575
node_modules/undici/lib/fetch/response.js
generated
vendored
Normal file
575
node_modules/undici/lib/fetch/response.js
generated
vendored
Normal file
|
@ -0,0 +1,575 @@
|
|||
'use strict'
|
||||
|
||||
const { Headers, HeadersList, fill } = require('./headers')
|
||||
const { extractBody, cloneBody, mixinBody } = require('./body')
|
||||
const util = require('../core/util')
|
||||
const { kEnumerableProperty } = util
|
||||
const {
|
||||
isValidReasonPhrase,
|
||||
isCancelled,
|
||||
isAborted,
|
||||
isBlobLike,
|
||||
serializeJavascriptValueToJSONString,
|
||||
isErrorLike,
|
||||
isomorphicEncode
|
||||
} = require('./util')
|
||||
const {
|
||||
redirectStatus,
|
||||
nullBodyStatus,
|
||||
DOMException
|
||||
} = require('./constants')
|
||||
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { FormData } = require('./formdata')
|
||||
const { getGlobalOrigin } = require('./global')
|
||||
const { URLSerializer } = require('./dataURL')
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
const { types } = require('util')
|
||||
|
||||
const ReadableStream = globalThis.ReadableStream || require('stream/web').ReadableStream
|
||||
|
||||
// https://fetch.spec.whatwg.org/#response-class
|
||||
class Response {
|
||||
// Creates network error Response.
|
||||
static error () {
|
||||
// TODO
|
||||
const relevantRealm = { settingsObject: {} }
|
||||
|
||||
// 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 = new Response()
|
||||
responseObject[kState] = makeNetworkError()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kHeadersList] = responseObject[kState].headersList
|
||||
responseObject[kHeaders][kGuard] = 'immutable'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response-json
|
||||
static json (data = undefined, init = {}) {
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: '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 = new TextEncoder('utf-8').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 relevantRealm = { settingsObject: {} }
|
||||
const responseObject = new Response()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kGuard] = 'response'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
|
||||
// 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) {
|
||||
const relevantRealm = { settingsObject: {} }
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: '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, getGlobalOrigin())
|
||||
} catch (err) {
|
||||
throw Object.assign(new TypeError('Failed to parse URL from ' + url), {
|
||||
cause: err
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If status is not a redirect status, then throw a RangeError.
|
||||
if (!redirectStatus.includes(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 = new Response()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kGuard] = 'immutable'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
|
||||
// 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)
|
||||
|
||||
// 8. Return responseObject.
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response
|
||||
constructor (body = null, init = {}) {
|
||||
if (body !== null) {
|
||||
body = webidl.converters.BodyInit(body)
|
||||
}
|
||||
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
|
||||
// TODO
|
||||
this[kRealm] = { settingsObject: {} }
|
||||
|
||||
// 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()
|
||||
this[kHeaders][kGuard] = 'response'
|
||||
this[kHeaders][kHeadersList] = this[kState].headersList
|
||||
this[kHeaders][kRealm] = this[kRealm]
|
||||
|
||||
// 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 (this.bodyUsed || (this.body && this.body.locked)) {
|
||||
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.
|
||||
const clonedResponseObject = new Response()
|
||||
clonedResponseObject[kState] = clonedResponse
|
||||
clonedResponseObject[kRealm] = this[kRealm]
|
||||
clonedResponseObject[kHeaders][kHeadersList] = clonedResponse.headersList
|
||||
clonedResponseObject[kHeaders][kGuard] = this[kHeaders][kGuard]
|
||||
clonedResponseObject[kHeaders][kRealm] = this[kHeaders][kRealm]
|
||||
|
||||
return clonedResponseObject
|
||||
}
|
||||
}
|
||||
|
||||
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(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, {
|
||||
cause: isError ? reason : undefined
|
||||
}),
|
||||
aborted: reason && reason.name === 'AbortError'
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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(new DOMException('The operation was aborted.', 'AbortError'))
|
||||
: makeNetworkError('Request was cancelled.')
|
||||
}
|
||||
|
||||
// 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[kState].headersList, 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')) {
|
||||
response[kState].headersList.append('content-type', body.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (typeof V === 'string') {
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
if (isBlobLike(V)) {
|
||||
return webidl.converters.Blob(V, { strict: false })
|
||||
}
|
||||
|
||||
if (
|
||||
types.isAnyArrayBuffer(V) ||
|
||||
types.isTypedArray(V) ||
|
||||
types.isDataView(V)
|
||||
) {
|
||||
return webidl.converters.BufferSource(V)
|
||||
}
|
||||
|
||||
if (util.isFormDataLike(V)) {
|
||||
return webidl.converters.FormData(V, { strict: false })
|
||||
}
|
||||
|
||||
if (V instanceof URLSearchParams) {
|
||||
return webidl.converters.URLSearchParams(V)
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit
|
||||
webidl.converters.BodyInit = function (V) {
|
||||
if (V instanceof ReadableStream) {
|
||||
return webidl.converters.ReadableStream(V)
|
||||
}
|
||||
|
||||
// Note: the spec doesn't include async iterables,
|
||||
// this is an undici extension.
|
||||
if (V?.[Symbol.asyncIterator]) {
|
||||
return V
|
||||
}
|
||||
|
||||
return webidl.converters.XMLHttpRequestBodyInit(V)
|
||||
}
|
||||
|
||||
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 = {
|
||||
makeNetworkError,
|
||||
makeResponse,
|
||||
makeAppropriateNetworkError,
|
||||
filterResponse,
|
||||
Response
|
||||
}
|
11
node_modules/undici/lib/fetch/symbols.js
generated
vendored
Normal file
11
node_modules/undici/lib/fetch/symbols.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
kUrl: Symbol('url'),
|
||||
kHeaders: Symbol('headers'),
|
||||
kSignal: Symbol('signal'),
|
||||
kState: Symbol('state'),
|
||||
kGuard: Symbol('guard'),
|
||||
kRealm: Symbol('realm'),
|
||||
kHeadersCaseInsensitive: Symbol('headers case insensitive')
|
||||
}
|
987
node_modules/undici/lib/fetch/util.js
generated
vendored
Normal file
987
node_modules/undici/lib/fetch/util.js
generated
vendored
Normal file
|
@ -0,0 +1,987 @@
|
|||
'use strict'
|
||||
|
||||
const { redirectStatus, badPorts, referrerPolicy: referrerPolicyTokens } = require('./constants')
|
||||
const { performance } = require('perf_hooks')
|
||||
const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
|
||||
const assert = require('assert')
|
||||
const { isUint8Array } = require('util/types')
|
||||
|
||||
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
|
||||
/** @type {import('crypto')|undefined} */
|
||||
let crypto
|
||||
|
||||
try {
|
||||
crypto = require('crypto')
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
function responseURL (response) {
|
||||
// https://fetch.spec.whatwg.org/#responses
|
||||
// A response has an associated URL. It is a pointer to the last URL
|
||||
// in response’s URL list and null if response’s URL list is empty.
|
||||
const urlList = response.urlList
|
||||
const length = urlList.length
|
||||
return length === 0 ? null : urlList[length - 1].toString()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-location-url
|
||||
function responseLocationURL (response, requestFragment) {
|
||||
// 1. If response’s status is not a redirect status, then return null.
|
||||
if (!redirectStatus.includes(response.status)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Let location be the result of extracting header list values given
|
||||
// `Location` and response’s header list.
|
||||
let location = response.headersList.get('location')
|
||||
|
||||
// 3. If location is a value, then set location to the result of parsing
|
||||
// location with response’s URL.
|
||||
location = location ? new URL(location, responseURL(response)) : null
|
||||
|
||||
// 4. If location is a URL whose fragment is null, then set location’s
|
||||
// fragment to requestFragment.
|
||||
if (location && !location.hash) {
|
||||
location.hash = requestFragment
|
||||
}
|
||||
|
||||
// 5. Return location.
|
||||
return location
|
||||
}
|
||||
|
||||
/** @returns {URL} */
|
||||
function requestCurrentURL (request) {
|
||||
return request.urlList[request.urlList.length - 1]
|
||||
}
|
||||
|
||||
function requestBadPort (request) {
|
||||
// 1. Let url be request’s current URL.
|
||||
const url = requestCurrentURL(request)
|
||||
|
||||
// 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
|
||||
// then return blocked.
|
||||
if (/^https?:/.test(url.protocol) && badPorts.includes(url.port)) {
|
||||
return 'blocked'
|
||||
}
|
||||
|
||||
// 3. Return allowed.
|
||||
return 'allowed'
|
||||
}
|
||||
|
||||
function isErrorLike (object) {
|
||||
return object instanceof Error || (
|
||||
object?.constructor?.name === 'Error' ||
|
||||
object?.constructor?.name === 'DOMException'
|
||||
)
|
||||
}
|
||||
|
||||
// Check whether |statusText| is a ByteString and
|
||||
// matches the Reason-Phrase token production.
|
||||
// RFC 2616: https://tools.ietf.org/html/rfc2616
|
||||
// RFC 7230: https://tools.ietf.org/html/rfc7230
|
||||
// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
|
||||
// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
|
||||
function isValidReasonPhrase (statusText) {
|
||||
for (let i = 0; i < statusText.length; ++i) {
|
||||
const c = statusText.charCodeAt(i)
|
||||
if (
|
||||
!(
|
||||
(
|
||||
c === 0x09 || // HTAB
|
||||
(c >= 0x20 && c <= 0x7e) || // SP / VCHAR
|
||||
(c >= 0x80 && c <= 0xff)
|
||||
) // obs-text
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function isTokenChar (c) {
|
||||
return !(
|
||||
c >= 0x7f ||
|
||||
c <= 0x20 ||
|
||||
c === '(' ||
|
||||
c === ')' ||
|
||||
c === '<' ||
|
||||
c === '>' ||
|
||||
c === '@' ||
|
||||
c === ',' ||
|
||||
c === ';' ||
|
||||
c === ':' ||
|
||||
c === '\\' ||
|
||||
c === '"' ||
|
||||
c === '/' ||
|
||||
c === '[' ||
|
||||
c === ']' ||
|
||||
c === '?' ||
|
||||
c === '=' ||
|
||||
c === '{' ||
|
||||
c === '}'
|
||||
)
|
||||
}
|
||||
|
||||
// See RFC 7230, Section 3.2.6.
|
||||
// https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
|
||||
function isValidHTTPToken (characters) {
|
||||
if (!characters || typeof characters !== 'string') {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < characters.length; ++i) {
|
||||
const c = characters.charCodeAt(i)
|
||||
if (c > 0x7f || !isTokenChar(c)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-name
|
||||
// https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
|
||||
function isValidHeaderName (potentialValue) {
|
||||
if (potentialValue.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return isValidHTTPToken(potentialValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#header-value
|
||||
* @param {string} potentialValue
|
||||
*/
|
||||
function isValidHeaderValue (potentialValue) {
|
||||
// - Has no leading or trailing HTTP tab or space bytes.
|
||||
// - Contains no 0x00 (NUL) or HTTP newline bytes.
|
||||
if (
|
||||
potentialValue.startsWith('\t') ||
|
||||
potentialValue.startsWith(' ') ||
|
||||
potentialValue.endsWith('\t') ||
|
||||
potentialValue.endsWith(' ')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
potentialValue.includes('\0') ||
|
||||
potentialValue.includes('\r') ||
|
||||
potentialValue.includes('\n')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
|
||||
function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
|
||||
// Given a request request and a response actualResponse, this algorithm
|
||||
// updates request’s referrer policy according to the Referrer-Policy
|
||||
// header (if any) in actualResponse.
|
||||
|
||||
// 1. Let policy be the result of executing § 8.1 Parse a referrer policy
|
||||
// from a Referrer-Policy header on actualResponse.
|
||||
|
||||
// 8.1 Parse a referrer policy from a Referrer-Policy header
|
||||
// 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
|
||||
const { headersList } = actualResponse
|
||||
// 2. Let policy be the empty string.
|
||||
// 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
|
||||
// 4. Return policy.
|
||||
const policyHeader = (headersList.get('referrer-policy') ?? '').split(',')
|
||||
|
||||
// Note: As the referrer-policy can contain multiple policies
|
||||
// separated by comma, we need to loop through all of them
|
||||
// and pick the first valid one.
|
||||
// Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
|
||||
let policy = ''
|
||||
if (policyHeader.length > 0) {
|
||||
// The right-most policy takes precedence.
|
||||
// The left-most policy is the fallback.
|
||||
for (let i = policyHeader.length; i !== 0; i--) {
|
||||
const token = policyHeader[i - 1].trim()
|
||||
if (referrerPolicyTokens.includes(token)) {
|
||||
policy = token
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If policy is not the empty string, then set request’s referrer policy to policy.
|
||||
if (policy !== '') {
|
||||
request.referrerPolicy = policy
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
|
||||
function crossOriginResourcePolicyCheck () {
|
||||
// TODO
|
||||
return 'allowed'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-cors-check
|
||||
function corsCheck () {
|
||||
// TODO
|
||||
return 'success'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-tao-check
|
||||
function TAOCheck () {
|
||||
// TODO
|
||||
return 'success'
|
||||
}
|
||||
|
||||
function appendFetchMetadata (httpRequest) {
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
|
||||
// TODO
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
|
||||
|
||||
// 1. Assert: r’s url is a potentially trustworthy URL.
|
||||
// TODO
|
||||
|
||||
// 2. Let header be a Structured Header whose value is a token.
|
||||
let header = null
|
||||
|
||||
// 3. Set header’s value to r’s mode.
|
||||
header = httpRequest.mode
|
||||
|
||||
// 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
|
||||
httpRequest.headersList.set('sec-fetch-mode', header)
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
|
||||
// TODO
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
|
||||
// TODO
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#append-a-request-origin-header
|
||||
function appendRequestOriginHeader (request) {
|
||||
// 1. Let serializedOrigin be the result of byte-serializing a request origin with request.
|
||||
let serializedOrigin = request.origin
|
||||
|
||||
// 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
|
||||
if (request.responseTainting === 'cors' || request.mode === 'websocket') {
|
||||
if (serializedOrigin) {
|
||||
request.headersList.append('Origin', serializedOrigin)
|
||||
}
|
||||
|
||||
// 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
|
||||
} else if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
// 1. Switch on request’s referrer policy:
|
||||
switch (request.referrerPolicy) {
|
||||
case 'no-referrer':
|
||||
// Set serializedOrigin to `null`.
|
||||
serializedOrigin = null
|
||||
break
|
||||
case 'no-referrer-when-downgrade':
|
||||
case 'strict-origin':
|
||||
case 'strict-origin-when-cross-origin':
|
||||
// If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
|
||||
if (/^https:/.test(request.origin) && !/^https:/.test(requestCurrentURL(request))) {
|
||||
serializedOrigin = null
|
||||
}
|
||||
break
|
||||
case 'same-origin':
|
||||
// If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`.
|
||||
if (!sameOrigin(request, requestCurrentURL(request))) {
|
||||
serializedOrigin = null
|
||||
}
|
||||
break
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
if (serializedOrigin) {
|
||||
// 2. Append (`Origin`, serializedOrigin) to request’s header list.
|
||||
request.headersList.append('Origin', serializedOrigin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
|
||||
// TODO
|
||||
return performance.now()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
|
||||
function createOpaqueTimingInfo (timingInfo) {
|
||||
return {
|
||||
startTime: timingInfo.startTime ?? 0,
|
||||
redirectStartTime: 0,
|
||||
redirectEndTime: 0,
|
||||
postRedirectStartTime: timingInfo.startTime ?? 0,
|
||||
finalServiceWorkerStartTime: 0,
|
||||
finalNetworkResponseStartTime: 0,
|
||||
finalNetworkRequestStartTime: 0,
|
||||
endTime: 0,
|
||||
encodedBodySize: 0,
|
||||
decodedBodySize: 0,
|
||||
finalConnectionTimingInfo: null
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#policy-container
|
||||
function makePolicyContainer () {
|
||||
// TODO
|
||||
return {}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
|
||||
function clonePolicyContainer () {
|
||||
// TODO
|
||||
return {}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
|
||||
function determineRequestsReferrer (request) {
|
||||
// 1. Let policy be request's referrer policy.
|
||||
const policy = request.referrerPolicy
|
||||
|
||||
// Return no-referrer when empty or policy says so
|
||||
if (policy == null || policy === '' || policy === 'no-referrer') {
|
||||
return 'no-referrer'
|
||||
}
|
||||
|
||||
// 2. Let environment be the request client
|
||||
const environment = request.client
|
||||
let referrerSource = null
|
||||
|
||||
/**
|
||||
* 3, Switch on request’s referrer:
|
||||
"client"
|
||||
If environment’s global object is a Window object, then
|
||||
Let document be the associated Document of environment’s global object.
|
||||
If document’s origin is an opaque origin, return no referrer.
|
||||
While document is an iframe srcdoc document,
|
||||
let document be document’s browsing context’s browsing context container’s node document.
|
||||
Let referrerSource be document’s URL.
|
||||
|
||||
Otherwise, let referrerSource be environment’s creation URL.
|
||||
|
||||
a URL
|
||||
Let referrerSource be request’s referrer.
|
||||
*/
|
||||
if (request.referrer === 'client') {
|
||||
// Not defined in Node but part of the spec
|
||||
if (request.client?.globalObject?.constructor?.name === 'Window' ) { // eslint-disable-line
|
||||
const origin = environment.globalObject.self?.origin ?? environment.globalObject.location?.origin
|
||||
|
||||
// If document’s origin is an opaque origin, return no referrer.
|
||||
if (origin == null || origin === 'null') return 'no-referrer'
|
||||
|
||||
// Let referrerSource be document’s URL.
|
||||
referrerSource = new URL(environment.globalObject.location.href)
|
||||
} else {
|
||||
// 3(a)(II) If environment's global object is not Window,
|
||||
// Let referrerSource be environments creationURL
|
||||
if (environment?.globalObject?.location == null) {
|
||||
return 'no-referrer'
|
||||
}
|
||||
|
||||
referrerSource = new URL(environment.globalObject.location.href)
|
||||
}
|
||||
} else if (request.referrer instanceof URL) {
|
||||
// 3(b) If requests's referrer is a URL instance, then make
|
||||
// referrerSource be requests's referrer.
|
||||
referrerSource = request.referrer
|
||||
} else {
|
||||
// If referrerSource neither client nor instance of URL
|
||||
// then return "no-referrer".
|
||||
return 'no-referrer'
|
||||
}
|
||||
|
||||
const urlProtocol = referrerSource.protocol
|
||||
|
||||
// If url's scheme is a local scheme (i.e. one of "about", "data", "javascript", "file")
|
||||
// then return "no-referrer".
|
||||
if (
|
||||
urlProtocol === 'about:' || urlProtocol === 'data:' ||
|
||||
urlProtocol === 'blob:'
|
||||
) {
|
||||
return 'no-referrer'
|
||||
}
|
||||
|
||||
let temp
|
||||
let referrerOrigin
|
||||
// 4. Let requests's referrerURL be the result of stripping referrer
|
||||
// source for use as referrer (using util function, without origin only)
|
||||
const referrerUrl = (temp = stripURLForReferrer(referrerSource)).length > 4096
|
||||
// 5. Let referrerOrigin be the result of stripping referrer
|
||||
// source for use as referrer (using util function, with originOnly true)
|
||||
? (referrerOrigin = stripURLForReferrer(referrerSource, true))
|
||||
// 6. If result of seralizing referrerUrl is a string whose length is greater than
|
||||
// 4096, then set referrerURL to referrerOrigin
|
||||
: temp
|
||||
const areSameOrigin = sameOrigin(request, referrerUrl)
|
||||
const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerUrl) &&
|
||||
!isURLPotentiallyTrustworthy(request.url)
|
||||
|
||||
// NOTE: How to treat step 7?
|
||||
// 8. Execute the switch statements corresponding to the value of policy:
|
||||
switch (policy) {
|
||||
case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
|
||||
case 'unsafe-url': return referrerUrl
|
||||
case 'same-origin':
|
||||
return areSameOrigin ? referrerOrigin : 'no-referrer'
|
||||
case 'origin-when-cross-origin':
|
||||
return areSameOrigin ? referrerUrl : referrerOrigin
|
||||
case 'strict-origin-when-cross-origin':
|
||||
/**
|
||||
* 1. If the origin of referrerURL and the origin of request’s current URL are the same,
|
||||
* then return referrerURL.
|
||||
* 2. If referrerURL is a potentially trustworthy URL and request’s current URL is not a
|
||||
* potentially trustworthy URL, then return no referrer.
|
||||
* 3. Return referrerOrigin
|
||||
*/
|
||||
if (areSameOrigin) return referrerOrigin
|
||||
// else return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
|
||||
case 'strict-origin': // eslint-disable-line
|
||||
/**
|
||||
* 1. If referrerURL is a potentially trustworthy URL and
|
||||
* request’s current URL is not a potentially trustworthy URL,
|
||||
* then return no referrer.
|
||||
* 2. Return referrerOrigin
|
||||
*/
|
||||
case 'no-referrer-when-downgrade': // eslint-disable-line
|
||||
/**
|
||||
* 1. If referrerURL is a potentially trustworthy URL and
|
||||
* request’s current URL is not a potentially trustworthy URL,
|
||||
* then return no referrer.
|
||||
* 2. Return referrerOrigin
|
||||
*/
|
||||
|
||||
default: // eslint-disable-line
|
||||
return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
|
||||
}
|
||||
|
||||
function stripURLForReferrer (url, originOnly = false) {
|
||||
const urlObject = new URL(url.href)
|
||||
urlObject.username = ''
|
||||
urlObject.password = ''
|
||||
urlObject.hash = ''
|
||||
|
||||
return originOnly ? urlObject.origin : urlObject.href
|
||||
}
|
||||
}
|
||||
|
||||
function isURLPotentiallyTrustworthy (url) {
|
||||
if (!(url instanceof URL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If child of about, return true
|
||||
if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
|
||||
return true
|
||||
}
|
||||
|
||||
// If scheme is data, return true
|
||||
if (url.protocol === 'data:') return true
|
||||
|
||||
// If file, return true
|
||||
if (url.protocol === 'file:') return true
|
||||
|
||||
return isOriginPotentiallyTrustworthy(url.origin)
|
||||
|
||||
function isOriginPotentiallyTrustworthy (origin) {
|
||||
// If origin is explicitly null, return false
|
||||
if (origin == null || origin === 'null') return false
|
||||
|
||||
const originAsURL = new URL(origin)
|
||||
|
||||
// If secure, return true
|
||||
if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') {
|
||||
return true
|
||||
}
|
||||
|
||||
// If localhost or variants, return true
|
||||
if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) ||
|
||||
(originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) ||
|
||||
(originAsURL.hostname.endsWith('.localhost'))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If any other, return false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {string} metadataList
|
||||
*/
|
||||
function bytesMatch (bytes, metadataList) {
|
||||
// If node is not built with OpenSSL support, we cannot check
|
||||
// a request's integrity, so allow it by default (the spec will
|
||||
// allow requests if an invalid hash is given, as precedence).
|
||||
/* istanbul ignore if: only if node is built with --without-ssl */
|
||||
if (crypto === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 1. Let parsedMetadata be the result of parsing metadataList.
|
||||
const parsedMetadata = parseMetadata(metadataList)
|
||||
|
||||
// 2. If parsedMetadata is no metadata, return true.
|
||||
if (parsedMetadata === 'no metadata') {
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. If parsedMetadata is the empty set, return true.
|
||||
if (parsedMetadata.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 4. Let metadata be the result of getting the strongest
|
||||
// metadata from parsedMetadata.
|
||||
const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
|
||||
// get the strongest algorithm
|
||||
const strongest = list[0].algo
|
||||
// get all entries that use the strongest algorithm; ignore weaker
|
||||
const metadata = list.filter((item) => item.algo === strongest)
|
||||
|
||||
// 5. For each item in metadata:
|
||||
for (const item of metadata) {
|
||||
// 1. Let algorithm be the alg component of item.
|
||||
const algorithm = item.algo
|
||||
|
||||
// 2. Let expectedValue be the val component of item.
|
||||
const expectedValue = item.hash
|
||||
|
||||
// 3. Let actualValue be the result of applying algorithm to bytes.
|
||||
const actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
|
||||
|
||||
// 4. If actualValue is a case-sensitive match for expectedValue,
|
||||
// return true.
|
||||
if (actualValue === expectedValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Return false.
|
||||
return false
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
|
||||
// https://www.w3.org/TR/CSP2/#source-list-syntax
|
||||
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
|
||||
const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
|
||||
* @param {string} metadata
|
||||
*/
|
||||
function parseMetadata (metadata) {
|
||||
// 1. Let result be the empty set.
|
||||
/** @type {{ algo: string, hash: string }[]} */
|
||||
const result = []
|
||||
|
||||
// 2. Let empty be equal to true.
|
||||
let empty = true
|
||||
|
||||
const supportedHashes = crypto.getHashes()
|
||||
|
||||
// 3. For each token returned by splitting metadata on spaces:
|
||||
for (const token of metadata.split(' ')) {
|
||||
// 1. Set empty to false.
|
||||
empty = false
|
||||
|
||||
// 2. Parse token as a hash-with-options.
|
||||
const parsedToken = parseHashWithOptions.exec(token)
|
||||
|
||||
// 3. If token does not parse, continue to the next token.
|
||||
if (parsedToken === null || parsedToken.groups === undefined) {
|
||||
// Note: Chromium blocks the request at this point, but Firefox
|
||||
// gives a warning that an invalid integrity was given. The
|
||||
// correct behavior is to ignore these, and subsequently not
|
||||
// check the integrity of the resource.
|
||||
continue
|
||||
}
|
||||
|
||||
// 4. Let algorithm be the hash-algo component of token.
|
||||
const algorithm = parsedToken.groups.algo
|
||||
|
||||
// 5. If algorithm is a hash function recognized by the user
|
||||
// agent, add the parsed token to result.
|
||||
if (supportedHashes.includes(algorithm.toLowerCase())) {
|
||||
result.push(parsedToken.groups)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return no metadata if empty is true, otherwise return result.
|
||||
if (empty === true) {
|
||||
return 'no metadata'
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
|
||||
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
|
||||
* @param {URL} A
|
||||
* @param {URL} B
|
||||
*/
|
||||
function sameOrigin (A, B) {
|
||||
// 1. If A and B are the same opaque origin, then return true.
|
||||
// "opaque origin" is an internal value we cannot access, ignore.
|
||||
|
||||
// 2. If A and B are both tuple origins and their schemes,
|
||||
// hosts, and port are identical, then return true.
|
||||
if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. Return false.
|
||||
return false
|
||||
}
|
||||
|
||||
function createDeferredPromise () {
|
||||
let res
|
||||
let rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
|
||||
return { promise, resolve: res, reject: rej }
|
||||
}
|
||||
|
||||
function isAborted (fetchParams) {
|
||||
return fetchParams.controller.state === 'aborted'
|
||||
}
|
||||
|
||||
function isCancelled (fetchParams) {
|
||||
return fetchParams.controller.state === 'aborted' ||
|
||||
fetchParams.controller.state === 'terminated'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-method-normalize
|
||||
function normalizeMethod (method) {
|
||||
return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method)
|
||||
? method.toUpperCase()
|
||||
: method
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
|
||||
function serializeJavascriptValueToJSONString (value) {
|
||||
// 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
|
||||
const result = JSON.stringify(value)
|
||||
|
||||
// 2. If result is undefined, then throw a TypeError.
|
||||
if (result === undefined) {
|
||||
throw new TypeError('Value is not JSON serializable')
|
||||
}
|
||||
|
||||
// 3. Assert: result is a string.
|
||||
assert(typeof result === 'string')
|
||||
|
||||
// 4. Return result.
|
||||
return result
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
||||
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
||||
|
||||
/**
|
||||
* @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
||||
* @param {() => unknown[]} iterator
|
||||
* @param {string} name name of the instance
|
||||
* @param {'key'|'value'|'key+value'} kind
|
||||
*/
|
||||
function makeIterator (iterator, name, kind) {
|
||||
const object = {
|
||||
index: 0,
|
||||
kind,
|
||||
target: iterator
|
||||
}
|
||||
|
||||
const i = {
|
||||
next () {
|
||||
// 1. Let interface be the interface for which the iterator prototype object exists.
|
||||
|
||||
// 2. Let thisValue be the this value.
|
||||
|
||||
// 3. Let object be ? ToObject(thisValue).
|
||||
|
||||
// 4. If object is a platform object, then perform a security
|
||||
// check, passing:
|
||||
|
||||
// 5. If object is not a default iterator object for interface,
|
||||
// then throw a TypeError.
|
||||
if (Object.getPrototypeOf(this) !== i) {
|
||||
throw new TypeError(
|
||||
`'next' called on an object that does not implement interface ${name} Iterator.`
|
||||
)
|
||||
}
|
||||
|
||||
// 6. Let index be object’s index.
|
||||
// 7. Let kind be object’s kind.
|
||||
// 8. Let values be object’s target's value pairs to iterate over.
|
||||
const { index, kind, target } = object
|
||||
const values = target()
|
||||
|
||||
// 9. Let len be the length of values.
|
||||
const len = values.length
|
||||
|
||||
// 10. If index is greater than or equal to len, then return
|
||||
// CreateIterResultObject(undefined, true).
|
||||
if (index >= len) {
|
||||
return { value: undefined, done: true }
|
||||
}
|
||||
|
||||
// 11. Let pair be the entry in values at index index.
|
||||
const pair = values[index]
|
||||
|
||||
// 12. Set object’s index to index + 1.
|
||||
object.index = index + 1
|
||||
|
||||
// 13. Return the iterator result for pair and kind.
|
||||
return iteratorResult(pair, kind)
|
||||
},
|
||||
// The class string of an iterator prototype object for a given interface is the
|
||||
// result of concatenating the identifier of the interface and the string " Iterator".
|
||||
[Symbol.toStringTag]: `${name} Iterator`
|
||||
}
|
||||
|
||||
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
||||
Object.setPrototypeOf(i, esIteratorPrototype)
|
||||
// esIteratorPrototype needs to be the prototype of i
|
||||
// which is the prototype of an empty object. Yes, it's confusing.
|
||||
return Object.setPrototypeOf({}, i)
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#iterator-result
|
||||
function iteratorResult (pair, kind) {
|
||||
let result
|
||||
|
||||
// 1. Let result be a value determined by the value of kind:
|
||||
switch (kind) {
|
||||
case 'key': {
|
||||
// 1. Let idlKey be pair’s key.
|
||||
// 2. Let key be the result of converting idlKey to an
|
||||
// ECMAScript value.
|
||||
// 3. result is key.
|
||||
result = pair[0]
|
||||
break
|
||||
}
|
||||
case 'value': {
|
||||
// 1. Let idlValue be pair’s value.
|
||||
// 2. Let value be the result of converting idlValue to
|
||||
// an ECMAScript value.
|
||||
// 3. result is value.
|
||||
result = pair[1]
|
||||
break
|
||||
}
|
||||
case 'key+value': {
|
||||
// 1. Let idlKey be pair’s key.
|
||||
// 2. Let idlValue be pair’s value.
|
||||
// 3. Let key be the result of converting idlKey to an
|
||||
// ECMAScript value.
|
||||
// 4. Let value be the result of converting idlValue to
|
||||
// an ECMAScript value.
|
||||
// 5. Let array be ! ArrayCreate(2).
|
||||
// 6. Call ! CreateDataProperty(array, "0", key).
|
||||
// 7. Call ! CreateDataProperty(array, "1", value).
|
||||
// 8. result is array.
|
||||
result = pair
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Return CreateIterResultObject(result, false).
|
||||
return { value: result, done: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#body-fully-read
|
||||
*/
|
||||
function fullyReadBody (body, processBody, processBodyError) {
|
||||
// 1. If taskDestination is null, then set taskDestination to
|
||||
// the result of starting a new parallel queue.
|
||||
|
||||
// 2. Let successSteps given a byte sequence bytes be to queue a
|
||||
// fetch task to run processBody given bytes, with taskDestination.
|
||||
const successSteps = (bytes) => queueMicrotask(() => processBody(bytes))
|
||||
|
||||
// 3. Let errorSteps be to queue a fetch task to run processBodyError,
|
||||
// with taskDestination.
|
||||
const errorSteps = (error) => queueMicrotask(() => processBodyError(error))
|
||||
|
||||
// 4. Let reader be the result of getting a reader for body’s stream.
|
||||
// If that threw an exception, then run errorSteps with that
|
||||
// exception and return.
|
||||
let reader
|
||||
|
||||
try {
|
||||
reader = body.stream.getReader()
|
||||
} catch (e) {
|
||||
errorSteps(e)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. Read all bytes from reader, given successSteps and errorSteps.
|
||||
readAllBytes(reader, successSteps, errorSteps)
|
||||
}
|
||||
|
||||
/** @type {ReadableStream} */
|
||||
let ReadableStream = globalThis.ReadableStream
|
||||
|
||||
function isReadableStreamLike (stream) {
|
||||
if (!ReadableStream) {
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
return stream instanceof ReadableStream || (
|
||||
stream[Symbol.toStringTag] === 'ReadableStream' &&
|
||||
typeof stream.tee === 'function'
|
||||
)
|
||||
}
|
||||
|
||||
const MAXIMUM_ARGUMENT_LENGTH = 65535
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#isomorphic-decode
|
||||
* @param {number[]|Uint8Array} input
|
||||
*/
|
||||
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.
|
||||
|
||||
if (input.length < MAXIMUM_ARGUMENT_LENGTH) {
|
||||
return String.fromCharCode(...input)
|
||||
}
|
||||
|
||||
return input.reduce((previous, current) => previous + String.fromCharCode(current), '')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ReadableStreamController<Uint8Array>} controller
|
||||
*/
|
||||
function readableStreamClose (controller) {
|
||||
try {
|
||||
controller.close()
|
||||
} catch (err) {
|
||||
// TODO: add comment explaining why this error occurs.
|
||||
if (!err.message.includes('Controller is already closed')) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#isomorphic-encode
|
||||
* @param {string} input
|
||||
*/
|
||||
function isomorphicEncode (input) {
|
||||
// 1. Assert: input contains no code points greater than U+00FF.
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
assert(input.charCodeAt(i) <= 0xFF)
|
||||
}
|
||||
|
||||
// 2. Return a byte sequence whose length is equal to input’s code
|
||||
// point length and whose bytes have the same values as the
|
||||
// values of input’s code points, in the same order
|
||||
return input
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
|
||||
* @see https://streams.spec.whatwg.org/#read-loop
|
||||
* @param {ReadableStreamDefaultReader} reader
|
||||
* @param {(bytes: Uint8Array) => void} successSteps
|
||||
* @param {(error: Error) => void} failureSteps
|
||||
*/
|
||||
async function readAllBytes (reader, successSteps, failureSteps) {
|
||||
const bytes = []
|
||||
let byteLength = 0
|
||||
|
||||
while (true) {
|
||||
let done
|
||||
let chunk
|
||||
|
||||
try {
|
||||
({ done, value: chunk } = await reader.read())
|
||||
} catch (e) {
|
||||
// 1. Call failureSteps with e.
|
||||
failureSteps(e)
|
||||
return
|
||||
}
|
||||
|
||||
if (done) {
|
||||
// 1. Call successSteps with bytes.
|
||||
successSteps(Buffer.concat(bytes, byteLength))
|
||||
return
|
||||
}
|
||||
|
||||
// 1. If chunk is not a Uint8Array object, call failureSteps
|
||||
// with a TypeError and abort these steps.
|
||||
if (!isUint8Array(chunk)) {
|
||||
failureSteps(new TypeError('Received non-Uint8Array chunk'))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Append the bytes represented by chunk to bytes.
|
||||
bytes.push(chunk)
|
||||
byteLength += chunk.length
|
||||
|
||||
// 3. Read-loop given reader, bytes, successSteps, and failureSteps.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0.
|
||||
*/
|
||||
const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key))
|
||||
|
||||
module.exports = {
|
||||
isAborted,
|
||||
isCancelled,
|
||||
createDeferredPromise,
|
||||
ReadableStreamFrom,
|
||||
toUSVString,
|
||||
tryUpgradeRequestToAPotentiallyTrustworthyURL,
|
||||
coarsenedSharedCurrentTime,
|
||||
determineRequestsReferrer,
|
||||
makePolicyContainer,
|
||||
clonePolicyContainer,
|
||||
appendFetchMetadata,
|
||||
appendRequestOriginHeader,
|
||||
TAOCheck,
|
||||
corsCheck,
|
||||
crossOriginResourcePolicyCheck,
|
||||
createOpaqueTimingInfo,
|
||||
setRequestReferrerPolicyOnRedirect,
|
||||
isValidHTTPToken,
|
||||
requestBadPort,
|
||||
requestCurrentURL,
|
||||
responseURL,
|
||||
responseLocationURL,
|
||||
isBlobLike,
|
||||
isURLPotentiallyTrustworthy,
|
||||
isValidReasonPhrase,
|
||||
sameOrigin,
|
||||
normalizeMethod,
|
||||
serializeJavascriptValueToJSONString,
|
||||
makeIterator,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue,
|
||||
hasOwn,
|
||||
isErrorLike,
|
||||
fullyReadBody,
|
||||
bytesMatch,
|
||||
isReadableStreamLike,
|
||||
readableStreamClose,
|
||||
isomorphicEncode,
|
||||
isomorphicDecode
|
||||
}
|
641
node_modules/undici/lib/fetch/webidl.js
generated
vendored
Normal file
641
node_modules/undici/lib/fetch/webidl.js
generated
vendored
Normal file
|
@ -0,0 +1,641 @@
|
|||
'use strict'
|
||||
|
||||
const { types } = require('util')
|
||||
const { hasOwn, toUSVString } = require('./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 = undefined) {
|
||||
if (opts?.strict !== false && !(V instanceof I)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
} else {
|
||||
return V?.[Symbol.toStringTag] === I.prototype[Symbol.toStringTag]
|
||||
}
|
||||
}
|
||||
|
||||
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.`,
|
||||
...ctx
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ${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
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-sequence
|
||||
webidl.sequenceConverter = function (converter) {
|
||||
return (V) => {
|
||||
// 1. If Type(V) is not Object, throw a TypeError.
|
||||
if (webidl.util.Type(V) !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Sequence',
|
||||
message: `Value of type ${webidl.util.Type(V)} is not an Object.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Let method be ? GetMethod(V, @@iterator).
|
||||
/** @type {Generator} */
|
||||
const method = V?.[Symbol.iterator]?.()
|
||||
const seq = []
|
||||
|
||||
// 3. If method is undefined, throw a TypeError.
|
||||
if (
|
||||
method === undefined ||
|
||||
typeof method.next !== 'function'
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Sequence',
|
||||
message: 'Object is not an iterator.'
|
||||
})
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
|
||||
while (true) {
|
||||
const { done, value } = method.next()
|
||||
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
seq.push(converter(value))
|
||||
}
|
||||
|
||||
return seq
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-to-record
|
||||
webidl.recordConverter = function (keyConverter, valueConverter) {
|
||||
return (O) => {
|
||||
// 1. If Type(O) is not Object, throw a TypeError.
|
||||
if (webidl.util.Type(O) !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Record',
|
||||
message: `Value of type ${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)) {
|
||||
// Object.keys only returns enumerable properties
|
||||
const keys = Object.keys(O)
|
||||
|
||||
for (const key of keys) {
|
||||
// 1. Let typedKey be key converted to an IDL value of type K.
|
||||
const typedKey = keyConverter(key)
|
||||
|
||||
// 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])
|
||||
|
||||
// 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)
|
||||
|
||||
// 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])
|
||||
|
||||
// 4. Set result[typedKey] to typedValue.
|
||||
result[typedKey] = typedValue
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
webidl.interfaceConverter = function (i) {
|
||||
return (V, opts = {}) => {
|
||||
if (opts.strict !== false && !(V instanceof i)) {
|
||||
throw webidl.errors.exception({
|
||||
header: i.name,
|
||||
message: `Expected ${V} to be an instance of ${i.name}.`
|
||||
})
|
||||
}
|
||||
|
||||
return V
|
||||
}
|
||||
}
|
||||
|
||||
webidl.dictionaryConverter = function (converters) {
|
||||
return (dictionary) => {
|
||||
const type = webidl.util.Type(dictionary)
|
||||
const dict = {}
|
||||
|
||||
if (type === 'Null' || type === 'Undefined') {
|
||||
return dict
|
||||
} else if (type !== 'Object') {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
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 (!hasOwn(dictionary, key)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
message: `Missing required key "${key}".`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let value = dictionary[key]
|
||||
const hasDefault = hasOwn(options, 'defaultValue')
|
||||
|
||||
// Only use defaultValue if value is undefined and
|
||||
// a defaultValue options was provided.
|
||||
if (hasDefault && value !== null) {
|
||||
value = 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)
|
||||
|
||||
if (
|
||||
options.allowedValues &&
|
||||
!options.allowedValues.includes(value)
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`
|
||||
})
|
||||
}
|
||||
|
||||
dict[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
}
|
||||
|
||||
webidl.nullableConverter = function (converter) {
|
||||
return (V) => {
|
||||
if (V === null) {
|
||||
return V
|
||||
}
|
||||
|
||||
return converter(V)
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-DOMString
|
||||
webidl.converters.DOMString = function (V, 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 new TypeError('Could not convert argument of type symbol to string.')
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 1. Let x be ? ToString(V).
|
||||
// Note: DOMString converter perform ? ToString(V)
|
||||
const x = webidl.converters.DOMString(V)
|
||||
|
||||
// 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++) {
|
||||
const charCode = x.charCodeAt(index)
|
||||
|
||||
if (charCode > 255) {
|
||||
throw new TypeError(
|
||||
'Cannot convert argument to a ByteString because the character at ' +
|
||||
`index ${index} has a value of ${charCode} 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
|
||||
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) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "signed").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'signed')
|
||||
|
||||
// 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) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'unsigned')
|
||||
|
||||
// 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) {
|
||||
// 1. Let x be ? ConvertToInt(V, 32, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 32, 'unsigned')
|
||||
|
||||
// 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, opts) {
|
||||
// 1. Let x be ? ConvertToInt(V, 16, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts)
|
||||
|
||||
// 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, 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: `${V}`,
|
||||
argument: `${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.
|
||||
// Note: resizable ArrayBuffers are currently a proposal.
|
||||
|
||||
// 4. Return the IDL ArrayBuffer value that is a
|
||||
// reference to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
webidl.converters.TypedArray = function (V, T, 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: `${T.name}`,
|
||||
argument: `${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.
|
||||
// Note: resizable array buffers are currently a proposal
|
||||
|
||||
// 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, 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: 'DataView',
|
||||
message: 'Object 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.
|
||||
// Note: resizable ArrayBuffers are currently a proposal
|
||||
|
||||
// 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, opts = {}) {
|
||||
if (types.isAnyArrayBuffer(V)) {
|
||||
return webidl.converters.ArrayBuffer(V, opts)
|
||||
}
|
||||
|
||||
if (types.isTypedArray(V)) {
|
||||
return webidl.converters.TypedArray(V, V.constructor)
|
||||
}
|
||||
|
||||
if (types.isDataView(V)) {
|
||||
return webidl.converters.DataView(V, opts)
|
||||
}
|
||||
|
||||
throw new TypeError(`Could not convert ${V} to a 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue