format: prettify entire project
This commit is contained in:
640
node_modules/undici/lib/web/cache/cache.js
generated
vendored
640
node_modules/undici/lib/web/cache/cache.js
generated
vendored
File diff suppressed because it is too large
Load Diff
105
node_modules/undici/lib/web/cache/cachestorage.js
generated
vendored
105
node_modules/undici/lib/web/cache/cachestorage.js
generated
vendored
@ -1,52 +1,53 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { Cache } = require('./cache')
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { kConstruct } = require('../../core/symbols')
|
||||
const { Cache } = require('./cache');
|
||||
const { webidl } = require('../fetch/webidl');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const { kConstruct } = require('../../core/symbols');
|
||||
|
||||
class CacheStorage {
|
||||
/**
|
||||
* @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map
|
||||
* @type {Map<string, import('./cache').requestResponseList}
|
||||
*/
|
||||
#caches = new Map()
|
||||
#caches = new Map();
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
if (arguments[0] !== kConstruct) {
|
||||
webidl.illegalConstructor()
|
||||
webidl.illegalConstructor();
|
||||
}
|
||||
|
||||
webidl.util.markAsUncloneable(this)
|
||||
webidl.util.markAsUncloneable(this);
|
||||
}
|
||||
|
||||
async match (request, options = {}) {
|
||||
webidl.brandCheck(this, CacheStorage)
|
||||
webidl.argumentLengthCheck(arguments, 1, 'CacheStorage.match')
|
||||
async match(request, options = {}) {
|
||||
webidl.brandCheck(this, CacheStorage);
|
||||
webidl.argumentLengthCheck(arguments, 1, 'CacheStorage.match');
|
||||
|
||||
request = webidl.converters.RequestInfo(request)
|
||||
options = webidl.converters.MultiCacheQueryOptions(options)
|
||||
request = webidl.converters.RequestInfo(request);
|
||||
options = webidl.converters.MultiCacheQueryOptions(options);
|
||||
|
||||
// 1.
|
||||
if (options.cacheName != null) {
|
||||
// 1.1.1.1
|
||||
if (this.#caches.has(options.cacheName)) {
|
||||
// 1.1.1.1.1
|
||||
const cacheList = this.#caches.get(options.cacheName)
|
||||
const cache = new Cache(kConstruct, cacheList)
|
||||
const cacheList = this.#caches.get(options.cacheName);
|
||||
const cache = new Cache(kConstruct, cacheList);
|
||||
|
||||
return await cache.match(request, options)
|
||||
return await cache.match(request, options);
|
||||
}
|
||||
} else { // 2.
|
||||
} else {
|
||||
// 2.
|
||||
// 2.2
|
||||
for (const cacheList of this.#caches.values()) {
|
||||
const cache = new Cache(kConstruct, cacheList)
|
||||
const cache = new Cache(kConstruct, cacheList);
|
||||
|
||||
// 2.2.1.2
|
||||
const response = await cache.match(request, options)
|
||||
const response = await cache.match(request, options);
|
||||
|
||||
if (response !== undefined) {
|
||||
return response
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,17 +58,17 @@ class CacheStorage {
|
||||
* @param {string} cacheName
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async has (cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage)
|
||||
async has(cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage);
|
||||
|
||||
const prefix = 'CacheStorage.has'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'CacheStorage.has';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName')
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName');
|
||||
|
||||
// 2.1.1
|
||||
// 2.2
|
||||
return this.#caches.has(cacheName)
|
||||
return this.#caches.has(cacheName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,33 +76,33 @@ class CacheStorage {
|
||||
* @param {string} cacheName
|
||||
* @returns {Promise<Cache>}
|
||||
*/
|
||||
async open (cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage)
|
||||
async open(cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage);
|
||||
|
||||
const prefix = 'CacheStorage.open'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'CacheStorage.open';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName')
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName');
|
||||
|
||||
// 2.1
|
||||
if (this.#caches.has(cacheName)) {
|
||||
// await caches.open('v1') !== await caches.open('v1')
|
||||
|
||||
// 2.1.1
|
||||
const cache = this.#caches.get(cacheName)
|
||||
const cache = this.#caches.get(cacheName);
|
||||
|
||||
// 2.1.1.1
|
||||
return new Cache(kConstruct, cache)
|
||||
return new Cache(kConstruct, cache);
|
||||
}
|
||||
|
||||
// 2.2
|
||||
const cache = []
|
||||
const cache = [];
|
||||
|
||||
// 2.3
|
||||
this.#caches.set(cacheName, cache)
|
||||
this.#caches.set(cacheName, cache);
|
||||
|
||||
// 2.4
|
||||
return new Cache(kConstruct, cache)
|
||||
return new Cache(kConstruct, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,44 +110,44 @@ class CacheStorage {
|
||||
* @param {string} cacheName
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async delete (cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage)
|
||||
async delete(cacheName) {
|
||||
webidl.brandCheck(this, CacheStorage);
|
||||
|
||||
const prefix = 'CacheStorage.delete'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'CacheStorage.delete';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName')
|
||||
cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName');
|
||||
|
||||
return this.#caches.delete(cacheName)
|
||||
return this.#caches.delete(cacheName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/ServiceWorker/#cache-storage-keys
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async keys () {
|
||||
webidl.brandCheck(this, CacheStorage)
|
||||
async keys() {
|
||||
webidl.brandCheck(this, CacheStorage);
|
||||
|
||||
// 2.1
|
||||
const keys = this.#caches.keys()
|
||||
const keys = this.#caches.keys();
|
||||
|
||||
// 2.2
|
||||
return [...keys]
|
||||
return [...keys];
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(CacheStorage.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'CacheStorage',
|
||||
configurable: true
|
||||
configurable: true,
|
||||
},
|
||||
match: kEnumerableProperty,
|
||||
has: kEnumerableProperty,
|
||||
open: kEnumerableProperty,
|
||||
delete: kEnumerableProperty,
|
||||
keys: kEnumerableProperty
|
||||
})
|
||||
keys: kEnumerableProperty,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
CacheStorage
|
||||
}
|
||||
CacheStorage,
|
||||
};
|
||||
|
32
node_modules/undici/lib/web/cache/util.js
generated
vendored
32
node_modules/undici/lib/web/cache/util.js
generated
vendored
@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert')
|
||||
const { URLSerializer } = require('../fetch/data-url')
|
||||
const { isValidHeaderName } = require('../fetch/util')
|
||||
const assert = require('node:assert');
|
||||
const { URLSerializer } = require('../fetch/data-url');
|
||||
const { isValidHeaderName } = require('../fetch/util');
|
||||
|
||||
/**
|
||||
* @see https://url.spec.whatwg.org/#concept-url-equals
|
||||
@ -11,35 +11,35 @@ const { isValidHeaderName } = require('../fetch/util')
|
||||
* @param {boolean | undefined} excludeFragment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function urlEquals (A, B, excludeFragment = false) {
|
||||
const serializedA = URLSerializer(A, excludeFragment)
|
||||
function urlEquals(A, B, excludeFragment = false) {
|
||||
const serializedA = URLSerializer(A, excludeFragment);
|
||||
|
||||
const serializedB = URLSerializer(B, excludeFragment)
|
||||
const serializedB = URLSerializer(B, excludeFragment);
|
||||
|
||||
return serializedA === serializedB
|
||||
return serializedA === serializedB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262
|
||||
* @param {string} header
|
||||
*/
|
||||
function getFieldValues (header) {
|
||||
assert(header !== null)
|
||||
function getFieldValues(header) {
|
||||
assert(header !== null);
|
||||
|
||||
const values = []
|
||||
const values = [];
|
||||
|
||||
for (let value of header.split(',')) {
|
||||
value = value.trim()
|
||||
value = value.trim();
|
||||
|
||||
if (isValidHeaderName(value)) {
|
||||
values.push(value)
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
return values;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
urlEquals,
|
||||
getFieldValues
|
||||
}
|
||||
getFieldValues,
|
||||
};
|
||||
|
10
node_modules/undici/lib/web/cookies/constants.js
generated
vendored
10
node_modules/undici/lib/web/cookies/constants.js
generated
vendored
@ -1,12 +1,12 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size
|
||||
const maxAttributeValueSize = 1024
|
||||
const maxAttributeValueSize = 1024;
|
||||
|
||||
// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size
|
||||
const maxNameValuePairSize = 4096
|
||||
const maxNameValuePairSize = 4096;
|
||||
|
||||
module.exports = {
|
||||
maxAttributeValueSize,
|
||||
maxNameValuePairSize
|
||||
}
|
||||
maxNameValuePairSize,
|
||||
};
|
||||
|
118
node_modules/undici/lib/web/cookies/index.js
generated
vendored
118
node_modules/undici/lib/web/cookies/index.js
generated
vendored
@ -1,11 +1,13 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { parseSetCookie } = require('./parse')
|
||||
const { stringify } = require('./util')
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { Headers } = require('../fetch/headers')
|
||||
const { parseSetCookie } = require('./parse');
|
||||
const { stringify } = require('./util');
|
||||
const { webidl } = require('../fetch/webidl');
|
||||
const { Headers } = require('../fetch/headers');
|
||||
|
||||
const brandChecks = webidl.brandCheckMultiple([Headers, globalThis.Headers].filter(Boolean))
|
||||
const brandChecks = webidl.brandCheckMultiple(
|
||||
[Headers, globalThis.Headers].filter(Boolean)
|
||||
);
|
||||
|
||||
/**
|
||||
* @typedef {Object} Cookie
|
||||
@ -25,27 +27,27 @@ const brandChecks = webidl.brandCheckMultiple([Headers, globalThis.Headers].filt
|
||||
* @param {Headers} headers
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function getCookies (headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'getCookies')
|
||||
function getCookies(headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'getCookies');
|
||||
|
||||
brandChecks(headers)
|
||||
brandChecks(headers);
|
||||
|
||||
const cookie = headers.get('cookie')
|
||||
const cookie = headers.get('cookie');
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const out = {}
|
||||
const out = {};
|
||||
|
||||
if (!cookie) {
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
for (const piece of cookie.split(';')) {
|
||||
const [name, ...value] = piece.split('=')
|
||||
const [name, ...value] = piece.split('=');
|
||||
|
||||
out[name.trim()] = value.join('=')
|
||||
out[name.trim()] = value.join('=');
|
||||
}
|
||||
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,14 +56,14 @@ function getCookies (headers) {
|
||||
* @param {{ path?: string, domain?: string }|undefined} attributes
|
||||
* @returns {void}
|
||||
*/
|
||||
function deleteCookie (headers, name, attributes) {
|
||||
brandChecks(headers)
|
||||
function deleteCookie(headers, name, attributes) {
|
||||
brandChecks(headers);
|
||||
|
||||
const prefix = 'deleteCookie'
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix)
|
||||
const prefix = 'deleteCookie';
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix);
|
||||
|
||||
name = webidl.converters.DOMString(name, prefix, 'name')
|
||||
attributes = webidl.converters.DeleteCookieAttributes(attributes)
|
||||
name = webidl.converters.DOMString(name, prefix, 'name');
|
||||
attributes = webidl.converters.DeleteCookieAttributes(attributes);
|
||||
|
||||
// Matches behavior of
|
||||
// https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278
|
||||
@ -69,36 +71,36 @@ function deleteCookie (headers, name, attributes) {
|
||||
name,
|
||||
value: '',
|
||||
expires: new Date(0),
|
||||
...attributes
|
||||
})
|
||||
...attributes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @returns {Cookie[]}
|
||||
*/
|
||||
function getSetCookies (headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'getSetCookies')
|
||||
function getSetCookies(headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'getSetCookies');
|
||||
|
||||
brandChecks(headers)
|
||||
brandChecks(headers);
|
||||
|
||||
const cookies = headers.getSetCookie()
|
||||
const cookies = headers.getSetCookie();
|
||||
|
||||
if (!cookies) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
return cookies.map((pair) => parseSetCookie(pair))
|
||||
return cookies.map((pair) => parseSetCookie(pair));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a cookie string
|
||||
* @param {string} cookie
|
||||
*/
|
||||
function parseCookie (cookie) {
|
||||
cookie = webidl.converters.DOMString(cookie)
|
||||
function parseCookie(cookie) {
|
||||
cookie = webidl.converters.DOMString(cookie);
|
||||
|
||||
return parseSetCookie(cookie)
|
||||
return parseSetCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,17 +108,17 @@ function parseCookie (cookie) {
|
||||
* @param {Cookie} cookie
|
||||
* @returns {void}
|
||||
*/
|
||||
function setCookie (headers, cookie) {
|
||||
webidl.argumentLengthCheck(arguments, 2, 'setCookie')
|
||||
function setCookie(headers, cookie) {
|
||||
webidl.argumentLengthCheck(arguments, 2, 'setCookie');
|
||||
|
||||
brandChecks(headers)
|
||||
brandChecks(headers);
|
||||
|
||||
cookie = webidl.converters.Cookie(cookie)
|
||||
cookie = webidl.converters.Cookie(cookie);
|
||||
|
||||
const str = stringify(cookie)
|
||||
const str = stringify(cookie);
|
||||
|
||||
if (str) {
|
||||
headers.append('set-cookie', str, true)
|
||||
headers.append('set-cookie', str, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,76 +126,76 @@ webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'path',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'domain',
|
||||
defaultValue: () => null
|
||||
}
|
||||
])
|
||||
defaultValue: () => null,
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.converters.Cookie = webidl.dictionaryConverter([
|
||||
{
|
||||
converter: webidl.converters.DOMString,
|
||||
key: 'name'
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
converter: webidl.converters.DOMString,
|
||||
key: 'value'
|
||||
key: 'value',
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter((value) => {
|
||||
if (typeof value === 'number') {
|
||||
return webidl.converters['unsigned long long'](value)
|
||||
return webidl.converters['unsigned long long'](value);
|
||||
}
|
||||
|
||||
return new Date(value)
|
||||
return new Date(value);
|
||||
}),
|
||||
key: 'expires',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters['long long']),
|
||||
key: 'maxAge',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'domain',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'path',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.boolean),
|
||||
key: 'secure',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.boolean),
|
||||
key: 'httpOnly',
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
converter: webidl.converters.USVString,
|
||||
key: 'sameSite',
|
||||
allowedValues: ['Strict', 'Lax', 'None']
|
||||
allowedValues: ['Strict', 'Lax', 'None'],
|
||||
},
|
||||
{
|
||||
converter: webidl.sequenceConverter(webidl.converters.DOMString),
|
||||
key: 'unparsed',
|
||||
defaultValue: () => new Array(0)
|
||||
}
|
||||
])
|
||||
defaultValue: () => new Array(0),
|
||||
},
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
getCookies,
|
||||
deleteCookie,
|
||||
getSetCookies,
|
||||
setCookie,
|
||||
parseCookie
|
||||
}
|
||||
parseCookie,
|
||||
};
|
||||
|
160
node_modules/undici/lib/web/cookies/parse.js
generated
vendored
160
node_modules/undici/lib/web/cookies/parse.js
generated
vendored
@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
|
||||
const { isCTLExcludingHtab } = require('./util')
|
||||
const { collectASequenceOfCodePointsFast } = require('../fetch/data-url')
|
||||
const assert = require('node:assert')
|
||||
const { unescape } = require('node:querystring')
|
||||
const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants');
|
||||
const { isCTLExcludingHtab } = require('./util');
|
||||
const { collectASequenceOfCodePointsFast } = require('../fetch/data-url');
|
||||
const assert = require('node:assert');
|
||||
const { unescape } = require('node:querystring');
|
||||
|
||||
/**
|
||||
* @description Parses the field-value attributes of a set-cookie header string.
|
||||
@ -12,18 +12,18 @@ const { unescape } = require('node:querystring')
|
||||
* @param {string} header
|
||||
* @returns {import('./index').Cookie|null} if the header is invalid, null will be returned
|
||||
*/
|
||||
function parseSetCookie (header) {
|
||||
function parseSetCookie(header) {
|
||||
// 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F
|
||||
// character (CTL characters excluding HTAB): Abort these steps and
|
||||
// ignore the set-cookie-string entirely.
|
||||
if (isCTLExcludingHtab(header)) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
let nameValuePair = ''
|
||||
let unparsedAttributes = ''
|
||||
let name = ''
|
||||
let value = ''
|
||||
let nameValuePair = '';
|
||||
let unparsedAttributes = '';
|
||||
let name = '';
|
||||
let value = '';
|
||||
|
||||
// 2. If the set-cookie-string contains a %x3B (";") character:
|
||||
if (header.includes(';')) {
|
||||
@ -31,48 +31,44 @@ function parseSetCookie (header) {
|
||||
// but not including, the first %x3B (";"), and the unparsed-
|
||||
// attributes consist of the remainder of the set-cookie-string
|
||||
// (including the %x3B (";") in question).
|
||||
const position = { position: 0 }
|
||||
const position = { position: 0 };
|
||||
|
||||
nameValuePair = collectASequenceOfCodePointsFast(';', header, position)
|
||||
unparsedAttributes = header.slice(position.position)
|
||||
nameValuePair = collectASequenceOfCodePointsFast(';', header, position);
|
||||
unparsedAttributes = header.slice(position.position);
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. The name-value-pair string consists of all the characters
|
||||
// contained in the set-cookie-string, and the unparsed-
|
||||
// attributes is the empty string.
|
||||
nameValuePair = header
|
||||
nameValuePair = header;
|
||||
}
|
||||
|
||||
// 3. If the name-value-pair string lacks a %x3D ("=") character, then
|
||||
// the name string is empty, and the value string is the value of
|
||||
// name-value-pair.
|
||||
if (!nameValuePair.includes('=')) {
|
||||
value = nameValuePair
|
||||
value = nameValuePair;
|
||||
} else {
|
||||
// Otherwise, the name string consists of the characters up to, but
|
||||
// not including, the first %x3D ("=") character, and the (possibly
|
||||
// empty) value string consists of the characters after the first
|
||||
// %x3D ("=") character.
|
||||
const position = { position: 0 }
|
||||
name = collectASequenceOfCodePointsFast(
|
||||
'=',
|
||||
nameValuePair,
|
||||
position
|
||||
)
|
||||
value = nameValuePair.slice(position.position + 1)
|
||||
const position = { position: 0 };
|
||||
name = collectASequenceOfCodePointsFast('=', nameValuePair, position);
|
||||
value = nameValuePair.slice(position.position + 1);
|
||||
}
|
||||
|
||||
// 4. Remove any leading or trailing WSP characters from the name
|
||||
// string and the value string.
|
||||
name = name.trim()
|
||||
value = value.trim()
|
||||
name = name.trim();
|
||||
value = value.trim();
|
||||
|
||||
// 5. If the sum of the lengths of the name string and the value string
|
||||
// is more than 4096 octets, abort these steps and ignore the set-
|
||||
// cookie-string entirely.
|
||||
if (name.length + value.length > maxNameValuePairSize) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// 6. The cookie-name is the name string, and the cookie-value is the
|
||||
@ -82,8 +78,10 @@ function parseSetCookie (header) {
|
||||
// store arbitrary data in a cookie-value SHOULD encode that data, for
|
||||
// example, using Base64 [RFC4648].
|
||||
return {
|
||||
name, value: unescape(value), ...parseUnparsedAttributes(unparsedAttributes)
|
||||
}
|
||||
name,
|
||||
value: unescape(value),
|
||||
...parseUnparsedAttributes(unparsedAttributes),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,43 +90,41 @@ function parseSetCookie (header) {
|
||||
* @param {string} unparsedAttributes
|
||||
* @param {Object.<string, unknown>} [cookieAttributeList={}]
|
||||
*/
|
||||
function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) {
|
||||
function parseUnparsedAttributes(unparsedAttributes, cookieAttributeList = {}) {
|
||||
// 1. If the unparsed-attributes string is empty, skip the rest of
|
||||
// these steps.
|
||||
if (unparsedAttributes.length === 0) {
|
||||
return cookieAttributeList
|
||||
return cookieAttributeList;
|
||||
}
|
||||
|
||||
// 2. Discard the first character of the unparsed-attributes (which
|
||||
// will be a %x3B (";") character).
|
||||
assert(unparsedAttributes[0] === ';')
|
||||
unparsedAttributes = unparsedAttributes.slice(1)
|
||||
assert(unparsedAttributes[0] === ';');
|
||||
unparsedAttributes = unparsedAttributes.slice(1);
|
||||
|
||||
let cookieAv = ''
|
||||
let cookieAv = '';
|
||||
|
||||
// 3. If the remaining unparsed-attributes contains a %x3B (";")
|
||||
// character:
|
||||
if (unparsedAttributes.includes(';')) {
|
||||
// 1. Consume the characters of the unparsed-attributes up to, but
|
||||
// not including, the first %x3B (";") character.
|
||||
cookieAv = collectASequenceOfCodePointsFast(
|
||||
';',
|
||||
unparsedAttributes,
|
||||
{ position: 0 }
|
||||
)
|
||||
unparsedAttributes = unparsedAttributes.slice(cookieAv.length)
|
||||
cookieAv = collectASequenceOfCodePointsFast(';', unparsedAttributes, {
|
||||
position: 0,
|
||||
});
|
||||
unparsedAttributes = unparsedAttributes.slice(cookieAv.length);
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. Consume the remainder of the unparsed-attributes.
|
||||
cookieAv = unparsedAttributes
|
||||
unparsedAttributes = ''
|
||||
cookieAv = unparsedAttributes;
|
||||
unparsedAttributes = '';
|
||||
}
|
||||
|
||||
// Let the cookie-av string be the characters consumed in this step.
|
||||
|
||||
let attributeName = ''
|
||||
let attributeValue = ''
|
||||
let attributeName = '';
|
||||
let attributeValue = '';
|
||||
|
||||
// 4. If the cookie-av string contains a %x3D ("=") character:
|
||||
if (cookieAv.includes('=')) {
|
||||
@ -137,37 +133,33 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
// character, and the (possibly empty) attribute-value string
|
||||
// consists of the characters after the first %x3D ("=")
|
||||
// character.
|
||||
const position = { position: 0 }
|
||||
const position = { position: 0 };
|
||||
|
||||
attributeName = collectASequenceOfCodePointsFast(
|
||||
'=',
|
||||
cookieAv,
|
||||
position
|
||||
)
|
||||
attributeValue = cookieAv.slice(position.position + 1)
|
||||
attributeName = collectASequenceOfCodePointsFast('=', cookieAv, position);
|
||||
attributeValue = cookieAv.slice(position.position + 1);
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. The attribute-name string consists of the entire cookie-av
|
||||
// string, and the attribute-value string is empty.
|
||||
attributeName = cookieAv
|
||||
attributeName = cookieAv;
|
||||
}
|
||||
|
||||
// 5. Remove any leading or trailing WSP characters from the attribute-
|
||||
// name string and the attribute-value string.
|
||||
attributeName = attributeName.trim()
|
||||
attributeValue = attributeValue.trim()
|
||||
attributeName = attributeName.trim();
|
||||
attributeValue = attributeValue.trim();
|
||||
|
||||
// 6. If the attribute-value is longer than 1024 octets, ignore the
|
||||
// cookie-av string and return to Step 1 of this algorithm.
|
||||
if (attributeValue.length > maxAttributeValueSize) {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
|
||||
}
|
||||
|
||||
// 7. Process the attribute-name and attribute-value according to the
|
||||
// requirements in the following subsections. (Notice that
|
||||
// attributes with unrecognized attribute-names are ignored.)
|
||||
const attributeNameLowercase = attributeName.toLowerCase()
|
||||
const attributeNameLowercase = attributeName.toLowerCase();
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
@ -175,12 +167,12 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
if (attributeNameLowercase === 'expires') {
|
||||
// 1. Let the expiry-time be the result of parsing the attribute-value
|
||||
// as cookie-date (see Section 5.1.1).
|
||||
const expiryTime = new Date(attributeValue)
|
||||
const expiryTime = new Date(attributeValue);
|
||||
|
||||
// 2. If the attribute-value failed to parse as a cookie date, ignore
|
||||
// the cookie-av.
|
||||
|
||||
cookieAttributeList.expires = expiryTime
|
||||
cookieAttributeList.expires = expiryTime;
|
||||
} else if (attributeNameLowercase === 'max-age') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2
|
||||
// If the attribute-name case-insensitively matches the string "Max-
|
||||
@ -188,20 +180,20 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
|
||||
// 1. If the first character of the attribute-value is not a DIGIT or a
|
||||
// "-" character, ignore the cookie-av.
|
||||
const charCode = attributeValue.charCodeAt(0)
|
||||
const charCode = attributeValue.charCodeAt(0);
|
||||
|
||||
if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
|
||||
}
|
||||
|
||||
// 2. If the remainder of attribute-value contains a non-DIGIT
|
||||
// character, ignore the cookie-av.
|
||||
if (!/^\d+$/.test(attributeValue)) {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
|
||||
}
|
||||
|
||||
// 3. Let delta-seconds be the attribute-value converted to an integer.
|
||||
const deltaSeconds = Number(attributeValue)
|
||||
const deltaSeconds = Number(attributeValue);
|
||||
|
||||
// 4. Let cookie-age-limit be the maximum age of the cookie (which
|
||||
// SHOULD be 400 days or less, see Section 4.1.2.2).
|
||||
@ -218,27 +210,27 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
|
||||
// 7. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Max-Age and an attribute-value of expiry-time.
|
||||
cookieAttributeList.maxAge = deltaSeconds
|
||||
cookieAttributeList.maxAge = deltaSeconds;
|
||||
} else if (attributeNameLowercase === 'domain') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3
|
||||
// If the attribute-name case-insensitively matches the string "Domain",
|
||||
// the user agent MUST process the cookie-av as follows.
|
||||
|
||||
// 1. Let cookie-domain be the attribute-value.
|
||||
let cookieDomain = attributeValue
|
||||
let cookieDomain = attributeValue;
|
||||
|
||||
// 2. If cookie-domain starts with %x2E ("."), let cookie-domain be
|
||||
// cookie-domain without its leading %x2E (".").
|
||||
if (cookieDomain[0] === '.') {
|
||||
cookieDomain = cookieDomain.slice(1)
|
||||
cookieDomain = cookieDomain.slice(1);
|
||||
}
|
||||
|
||||
// 3. Convert the cookie-domain to lower case.
|
||||
cookieDomain = cookieDomain.toLowerCase()
|
||||
cookieDomain = cookieDomain.toLowerCase();
|
||||
|
||||
// 4. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Domain and an attribute-value of cookie-domain.
|
||||
cookieAttributeList.domain = cookieDomain
|
||||
cookieAttributeList.domain = cookieDomain;
|
||||
} else if (attributeNameLowercase === 'path') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4
|
||||
// If the attribute-name case-insensitively matches the string "Path",
|
||||
@ -246,27 +238,27 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
|
||||
// 1. If the attribute-value is empty or if the first character of the
|
||||
// attribute-value is not %x2F ("/"):
|
||||
let cookiePath = ''
|
||||
let cookiePath = '';
|
||||
if (attributeValue.length === 0 || attributeValue[0] !== '/') {
|
||||
// 1. Let cookie-path be the default-path.
|
||||
cookiePath = '/'
|
||||
cookiePath = '/';
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. Let cookie-path be the attribute-value.
|
||||
cookiePath = attributeValue
|
||||
cookiePath = attributeValue;
|
||||
}
|
||||
|
||||
// 2. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Path and an attribute-value of cookie-path.
|
||||
cookieAttributeList.path = cookiePath
|
||||
cookieAttributeList.path = cookiePath;
|
||||
} else if (attributeNameLowercase === 'secure') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5
|
||||
// If the attribute-name case-insensitively matches the string "Secure",
|
||||
// the user agent MUST append an attribute to the cookie-attribute-list
|
||||
// with an attribute-name of Secure and an empty attribute-value.
|
||||
|
||||
cookieAttributeList.secure = true
|
||||
cookieAttributeList.secure = true;
|
||||
} else if (attributeNameLowercase === 'httponly') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
@ -274,49 +266,49 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
||||
// attribute-list with an attribute-name of HttpOnly and an empty
|
||||
// attribute-value.
|
||||
|
||||
cookieAttributeList.httpOnly = true
|
||||
cookieAttributeList.httpOnly = true;
|
||||
} else if (attributeNameLowercase === 'samesite') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
// "SameSite", the user agent MUST process the cookie-av as follows:
|
||||
|
||||
// 1. Let enforcement be "Default".
|
||||
let enforcement = 'Default'
|
||||
let enforcement = 'Default';
|
||||
|
||||
const attributeValueLowercase = attributeValue.toLowerCase()
|
||||
const attributeValueLowercase = attributeValue.toLowerCase();
|
||||
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "None", set enforcement to "None".
|
||||
if (attributeValueLowercase.includes('none')) {
|
||||
enforcement = 'None'
|
||||
enforcement = 'None';
|
||||
}
|
||||
|
||||
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "Strict", set enforcement to "Strict".
|
||||
if (attributeValueLowercase.includes('strict')) {
|
||||
enforcement = 'Strict'
|
||||
enforcement = 'Strict';
|
||||
}
|
||||
|
||||
// 4. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "Lax", set enforcement to "Lax".
|
||||
if (attributeValueLowercase.includes('lax')) {
|
||||
enforcement = 'Lax'
|
||||
enforcement = 'Lax';
|
||||
}
|
||||
|
||||
// 5. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of "SameSite" and an attribute-value of
|
||||
// enforcement.
|
||||
cookieAttributeList.sameSite = enforcement
|
||||
cookieAttributeList.sameSite = enforcement;
|
||||
} else {
|
||||
cookieAttributeList.unparsed ??= []
|
||||
cookieAttributeList.unparsed ??= [];
|
||||
|
||||
cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`)
|
||||
cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`);
|
||||
}
|
||||
|
||||
// 8. Return to Step 1 of this algorithm.
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseSetCookie,
|
||||
parseUnparsedAttributes
|
||||
}
|
||||
parseUnparsedAttributes,
|
||||
};
|
||||
|
177
node_modules/undici/lib/web/cookies/util.js
generated
vendored
177
node_modules/undici/lib/web/cookies/util.js
generated
vendored
@ -1,22 +1,22 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCTLExcludingHtab (value) {
|
||||
function isCTLExcludingHtab(value) {
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
const code = value.charCodeAt(i)
|
||||
const code = value.charCodeAt(i);
|
||||
|
||||
if (
|
||||
(code >= 0x00 && code <= 0x08) ||
|
||||
(code >= 0x0A && code <= 0x1F) ||
|
||||
code === 0x7F
|
||||
(code >= 0x0a && code <= 0x1f) ||
|
||||
code === 0x7f
|
||||
) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,32 +28,32 @@ function isCTLExcludingHtab (value) {
|
||||
| "{" | "}" | SP | HT
|
||||
* @param {string} name
|
||||
*/
|
||||
function validateCookieName (name) {
|
||||
function validateCookieName(name) {
|
||||
for (let i = 0; i < name.length; ++i) {
|
||||
const code = name.charCodeAt(i)
|
||||
const code = name.charCodeAt(i);
|
||||
|
||||
if (
|
||||
code < 0x21 || // exclude CTLs (0-31), SP and HT
|
||||
code > 0x7E || // exclude non-ascii and DEL
|
||||
code > 0x7e || // exclude non-ascii and DEL
|
||||
code === 0x22 || // "
|
||||
code === 0x28 || // (
|
||||
code === 0x29 || // )
|
||||
code === 0x3C || // <
|
||||
code === 0x3E || // >
|
||||
code === 0x3c || // <
|
||||
code === 0x3e || // >
|
||||
code === 0x40 || // @
|
||||
code === 0x2C || // ,
|
||||
code === 0x3B || // ;
|
||||
code === 0x3A || // :
|
||||
code === 0x5C || // \
|
||||
code === 0x2F || // /
|
||||
code === 0x5B || // [
|
||||
code === 0x5D || // ]
|
||||
code === 0x3F || // ?
|
||||
code === 0x3D || // =
|
||||
code === 0x7B || // {
|
||||
code === 0x7D // }
|
||||
code === 0x2c || // ,
|
||||
code === 0x3b || // ;
|
||||
code === 0x3a || // :
|
||||
code === 0x5c || // \
|
||||
code === 0x2f || // /
|
||||
code === 0x5b || // [
|
||||
code === 0x5d || // ]
|
||||
code === 0x3f || // ?
|
||||
code === 0x3d || // =
|
||||
code === 0x7b || // {
|
||||
code === 0x7d // }
|
||||
) {
|
||||
throw new Error('Invalid cookie name')
|
||||
throw new Error('Invalid cookie name');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,31 +66,31 @@ function validateCookieName (name) {
|
||||
; and backslash
|
||||
* @param {string} value
|
||||
*/
|
||||
function validateCookieValue (value) {
|
||||
let len = value.length
|
||||
let i = 0
|
||||
function validateCookieValue(value) {
|
||||
let len = value.length;
|
||||
let i = 0;
|
||||
|
||||
// if the value is wrapped in DQUOTE
|
||||
if (value[0] === '"') {
|
||||
if (len === 1 || value[len - 1] !== '"') {
|
||||
throw new Error('Invalid cookie value')
|
||||
throw new Error('Invalid cookie value');
|
||||
}
|
||||
--len
|
||||
++i
|
||||
--len;
|
||||
++i;
|
||||
}
|
||||
|
||||
while (i < len) {
|
||||
const code = value.charCodeAt(i++)
|
||||
const code = value.charCodeAt(i++);
|
||||
|
||||
if (
|
||||
code < 0x21 || // exclude CTLs (0-31)
|
||||
code > 0x7E || // non-ascii and DEL (127)
|
||||
code > 0x7e || // non-ascii and DEL (127)
|
||||
code === 0x22 || // "
|
||||
code === 0x2C || // ,
|
||||
code === 0x3B || // ;
|
||||
code === 0x5C // \
|
||||
code === 0x2c || // ,
|
||||
code === 0x3b || // ;
|
||||
code === 0x5c // \
|
||||
) {
|
||||
throw new Error('Invalid cookie value')
|
||||
throw new Error('Invalid cookie value');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,16 +99,16 @@ function validateCookieValue (value) {
|
||||
* path-value = <any CHAR except CTLs or ";">
|
||||
* @param {string} path
|
||||
*/
|
||||
function validateCookiePath (path) {
|
||||
function validateCookiePath(path) {
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const code = path.charCodeAt(i)
|
||||
const code = path.charCodeAt(i);
|
||||
|
||||
if (
|
||||
code < 0x20 || // exclude CTLs (0-31)
|
||||
code === 0x7F || // DEL
|
||||
code === 0x3B // ;
|
||||
code === 0x7f || // DEL
|
||||
code === 0x3b // ;
|
||||
) {
|
||||
throw new Error('Invalid cookie path')
|
||||
throw new Error('Invalid cookie path');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,27 +118,32 @@ function validateCookiePath (path) {
|
||||
* but Deno tests these. - Khafra
|
||||
* @param {string} domain
|
||||
*/
|
||||
function validateCookieDomain (domain) {
|
||||
if (
|
||||
domain.startsWith('-') ||
|
||||
domain.endsWith('.') ||
|
||||
domain.endsWith('-')
|
||||
) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
function validateCookieDomain(domain) {
|
||||
if (domain.startsWith('-') || domain.endsWith('.') || domain.endsWith('-')) {
|
||||
throw new Error('Invalid cookie domain');
|
||||
}
|
||||
}
|
||||
|
||||
const IMFDays = [
|
||||
'Sun', 'Mon', 'Tue', 'Wed',
|
||||
'Thu', 'Fri', 'Sat'
|
||||
]
|
||||
const IMFDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
const IMFMonths = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
]
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
const IMFPaddedNumbers = Array(61).fill(0).map((_, i) => i.toString().padStart(2, '0'))
|
||||
const IMFPaddedNumbers = Array(61)
|
||||
.fill(0)
|
||||
.map((_, i) => i.toString().padStart(2, '0'));
|
||||
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
|
||||
@ -181,12 +186,12 @@ const IMFPaddedNumbers = Array(61).fill(0).map((_, i) => i.toString().padStart(2
|
||||
minute = 2DIGIT
|
||||
second = 2DIGIT
|
||||
*/
|
||||
function toIMFDate (date) {
|
||||
function toIMFDate(date) {
|
||||
if (typeof date === 'number') {
|
||||
date = new Date(date)
|
||||
date = new Date(date);
|
||||
}
|
||||
|
||||
return `${IMFDays[date.getUTCDay()]}, ${IMFPaddedNumbers[date.getUTCDate()]} ${IMFMonths[date.getUTCMonth()]} ${date.getUTCFullYear()} ${IMFPaddedNumbers[date.getUTCHours()]}:${IMFPaddedNumbers[date.getUTCMinutes()]}:${IMFPaddedNumbers[date.getUTCSeconds()]} GMT`
|
||||
return `${IMFDays[date.getUTCDay()]}, ${IMFPaddedNumbers[date.getUTCDate()]} ${IMFMonths[date.getUTCMonth()]} ${date.getUTCFullYear()} ${IMFPaddedNumbers[date.getUTCHours()]}:${IMFPaddedNumbers[date.getUTCMinutes()]}:${IMFPaddedNumbers[date.getUTCSeconds()]} GMT`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,9 +201,9 @@ function toIMFDate (date) {
|
||||
; user agent.
|
||||
* @param {number} maxAge
|
||||
*/
|
||||
function validateCookieMaxAge (maxAge) {
|
||||
function validateCookieMaxAge(maxAge) {
|
||||
if (maxAge < 0) {
|
||||
throw new Error('Invalid cookie max-age')
|
||||
throw new Error('Invalid cookie max-age');
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,70 +211,70 @@ function validateCookieMaxAge (maxAge) {
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
|
||||
* @param {import('./index').Cookie} cookie
|
||||
*/
|
||||
function stringify (cookie) {
|
||||
function stringify(cookie) {
|
||||
if (cookie.name.length === 0) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
validateCookieName(cookie.name)
|
||||
validateCookieValue(cookie.value)
|
||||
validateCookieName(cookie.name);
|
||||
validateCookieValue(cookie.value);
|
||||
|
||||
const out = [`${cookie.name}=${cookie.value}`]
|
||||
const out = [`${cookie.name}=${cookie.value}`];
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2
|
||||
if (cookie.name.startsWith('__Secure-')) {
|
||||
cookie.secure = true
|
||||
cookie.secure = true;
|
||||
}
|
||||
|
||||
if (cookie.name.startsWith('__Host-')) {
|
||||
cookie.secure = true
|
||||
cookie.domain = null
|
||||
cookie.path = '/'
|
||||
cookie.secure = true;
|
||||
cookie.domain = null;
|
||||
cookie.path = '/';
|
||||
}
|
||||
|
||||
if (cookie.secure) {
|
||||
out.push('Secure')
|
||||
out.push('Secure');
|
||||
}
|
||||
|
||||
if (cookie.httpOnly) {
|
||||
out.push('HttpOnly')
|
||||
out.push('HttpOnly');
|
||||
}
|
||||
|
||||
if (typeof cookie.maxAge === 'number') {
|
||||
validateCookieMaxAge(cookie.maxAge)
|
||||
out.push(`Max-Age=${cookie.maxAge}`)
|
||||
validateCookieMaxAge(cookie.maxAge);
|
||||
out.push(`Max-Age=${cookie.maxAge}`);
|
||||
}
|
||||
|
||||
if (cookie.domain) {
|
||||
validateCookieDomain(cookie.domain)
|
||||
out.push(`Domain=${cookie.domain}`)
|
||||
validateCookieDomain(cookie.domain);
|
||||
out.push(`Domain=${cookie.domain}`);
|
||||
}
|
||||
|
||||
if (cookie.path) {
|
||||
validateCookiePath(cookie.path)
|
||||
out.push(`Path=${cookie.path}`)
|
||||
validateCookiePath(cookie.path);
|
||||
out.push(`Path=${cookie.path}`);
|
||||
}
|
||||
|
||||
if (cookie.expires && cookie.expires.toString() !== 'Invalid Date') {
|
||||
out.push(`Expires=${toIMFDate(cookie.expires)}`)
|
||||
out.push(`Expires=${toIMFDate(cookie.expires)}`);
|
||||
}
|
||||
|
||||
if (cookie.sameSite) {
|
||||
out.push(`SameSite=${cookie.sameSite}`)
|
||||
out.push(`SameSite=${cookie.sameSite}`);
|
||||
}
|
||||
|
||||
for (const part of cookie.unparsed) {
|
||||
if (!part.includes('=')) {
|
||||
throw new Error('Invalid unparsed')
|
||||
throw new Error('Invalid unparsed');
|
||||
}
|
||||
|
||||
const [key, ...value] = part.split('=')
|
||||
const [key, ...value] = part.split('=');
|
||||
|
||||
out.push(`${key.trim()}=${value.join('=')}`)
|
||||
out.push(`${key.trim()}=${value.join('=')}`);
|
||||
}
|
||||
|
||||
return out.join('; ')
|
||||
return out.join('; ');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -278,5 +283,5 @@ module.exports = {
|
||||
validateCookiePath,
|
||||
validateCookieValue,
|
||||
toIMFDate,
|
||||
stringify
|
||||
}
|
||||
stringify,
|
||||
};
|
||||
|
205
node_modules/undici/lib/web/eventsource/eventsource-stream.js
generated
vendored
205
node_modules/undici/lib/web/eventsource/eventsource-stream.js
generated
vendored
@ -1,27 +1,27 @@
|
||||
'use strict'
|
||||
const { Transform } = require('node:stream')
|
||||
const { isASCIINumber, isValidLastEventId } = require('./util')
|
||||
'use strict';
|
||||
const { Transform } = require('node:stream');
|
||||
const { isASCIINumber, isValidLastEventId } = require('./util');
|
||||
|
||||
/**
|
||||
* @type {number[]} BOM
|
||||
*/
|
||||
const BOM = [0xEF, 0xBB, 0xBF]
|
||||
const BOM = [0xef, 0xbb, 0xbf];
|
||||
/**
|
||||
* @type {10} LF
|
||||
*/
|
||||
const LF = 0x0A
|
||||
const LF = 0x0a;
|
||||
/**
|
||||
* @type {13} CR
|
||||
*/
|
||||
const CR = 0x0D
|
||||
const CR = 0x0d;
|
||||
/**
|
||||
* @type {58} COLON
|
||||
*/
|
||||
const COLON = 0x3A
|
||||
const COLON = 0x3a;
|
||||
/**
|
||||
* @type {32} SPACE
|
||||
*/
|
||||
const SPACE = 0x20
|
||||
const SPACE = 0x20;
|
||||
|
||||
/**
|
||||
* @typedef {object} EventSourceStreamEvent
|
||||
@ -44,37 +44,37 @@ class EventSourceStream extends Transform {
|
||||
/**
|
||||
* @type {eventSourceSettings}
|
||||
*/
|
||||
state
|
||||
state;
|
||||
|
||||
/**
|
||||
* Leading byte-order-mark check.
|
||||
* @type {boolean}
|
||||
*/
|
||||
checkBOM = true
|
||||
checkBOM = true;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
crlfCheck = false
|
||||
crlfCheck = false;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
eventEndCheck = false
|
||||
eventEndCheck = false;
|
||||
|
||||
/**
|
||||
* @type {Buffer|null}
|
||||
*/
|
||||
buffer = null
|
||||
buffer = null;
|
||||
|
||||
pos = 0
|
||||
pos = 0;
|
||||
|
||||
event = {
|
||||
data: undefined,
|
||||
event: undefined,
|
||||
id: undefined,
|
||||
retry: undefined
|
||||
}
|
||||
retry: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
@ -82,16 +82,16 @@ class EventSourceStream extends Transform {
|
||||
* @param {eventSourceSettings} [options.eventSourceSettings]
|
||||
* @param {(chunk: any, encoding?: BufferEncoding | undefined) => boolean} [options.push]
|
||||
*/
|
||||
constructor (options = {}) {
|
||||
constructor(options = {}) {
|
||||
// Enable object mode as EventSourceStream emits objects of shape
|
||||
// EventSourceStreamEvent
|
||||
options.readableObjectMode = true
|
||||
options.readableObjectMode = true;
|
||||
|
||||
super(options)
|
||||
super(options);
|
||||
|
||||
this.state = options.eventSourceSettings || {}
|
||||
this.state = options.eventSourceSettings || {};
|
||||
if (options.push) {
|
||||
this.push = options.push
|
||||
this.push = options.push;
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,10 +101,10 @@ class EventSourceStream extends Transform {
|
||||
* @param {Function} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
_transform (chunk, _encoding, callback) {
|
||||
_transform(chunk, _encoding, callback) {
|
||||
if (chunk.length === 0) {
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the chunk in the buffer, as the data might not be complete while
|
||||
@ -113,9 +113,9 @@ class EventSourceStream extends Transform {
|
||||
// incoming chunks
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
if (this.buffer) {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk])
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
} else {
|
||||
this.buffer = chunk
|
||||
this.buffer = chunk;
|
||||
}
|
||||
|
||||
// Strip leading byte-order-mark if we opened the stream and started
|
||||
@ -126,33 +126,30 @@ class EventSourceStream extends Transform {
|
||||
// Check if the first byte is the same as the first byte of the BOM
|
||||
if (this.buffer[0] === BOM[0]) {
|
||||
// If it is, we need to wait for more data
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
this.checkBOM = false;
|
||||
|
||||
// The buffer only contains one byte so we need to wait for more data
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
case 2:
|
||||
// Check if the first two bytes are the same as the first two bytes
|
||||
// of the BOM
|
||||
if (
|
||||
this.buffer[0] === BOM[0] &&
|
||||
this.buffer[1] === BOM[1]
|
||||
) {
|
||||
if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1]) {
|
||||
// If it is, we need to wait for more data, because the third byte
|
||||
// is needed to determine if it is the BOM or not
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
break
|
||||
this.checkBOM = false;
|
||||
break;
|
||||
case 3:
|
||||
// Check if the first three bytes are the same as the first three
|
||||
// bytes of the BOM
|
||||
@ -162,18 +159,18 @@ class EventSourceStream extends Transform {
|
||||
this.buffer[2] === BOM[2]
|
||||
) {
|
||||
// If it is, we can drop the buffered data, as it is only the BOM
|
||||
this.buffer = Buffer.alloc(0)
|
||||
this.buffer = Buffer.alloc(0);
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
this.checkBOM = false;
|
||||
|
||||
// Await more data
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// If it is not the BOM, we can start processing the data
|
||||
this.checkBOM = false
|
||||
break
|
||||
this.checkBOM = false;
|
||||
break;
|
||||
default:
|
||||
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
|
||||
// present
|
||||
@ -183,12 +180,12 @@ class EventSourceStream extends Transform {
|
||||
this.buffer[2] === BOM[2]
|
||||
) {
|
||||
// Remove the BOM from the buffer
|
||||
this.buffer = this.buffer.subarray(3)
|
||||
this.buffer = this.buffer.subarray(3);
|
||||
}
|
||||
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
this.checkBOM = false
|
||||
break
|
||||
this.checkBOM = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,9 +203,9 @@ class EventSourceStream extends Transform {
|
||||
// If the current character is a line feed, we can remove it
|
||||
// from the buffer and reset the crlfCheck flag
|
||||
if (this.buffer[this.pos] === LF) {
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
this.pos = 0
|
||||
this.crlfCheck = false
|
||||
this.buffer = this.buffer.subarray(this.pos + 1);
|
||||
this.pos = 0;
|
||||
this.crlfCheck = false;
|
||||
|
||||
// It is possible that the line feed is not the end of the
|
||||
// event. We need to check if the next character is an
|
||||
@ -219,9 +216,9 @@ class EventSourceStream extends Transform {
|
||||
// As we removed the line feed from the buffer and set the
|
||||
// crlfCheck flag to false, we basically don't make any
|
||||
// distinction between a line feed and a carriage return.
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
this.crlfCheck = false
|
||||
this.crlfCheck = false;
|
||||
}
|
||||
|
||||
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
|
||||
@ -230,22 +227,26 @@ class EventSourceStream extends Transform {
|
||||
// next character is a line feed so we can remove it from the
|
||||
// buffer
|
||||
if (this.buffer[this.pos] === CR) {
|
||||
this.crlfCheck = true
|
||||
this.crlfCheck = true;
|
||||
}
|
||||
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
this.pos = 0
|
||||
this.buffer = this.buffer.subarray(this.pos + 1);
|
||||
this.pos = 0;
|
||||
if (
|
||||
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
||||
this.processEvent(this.event)
|
||||
this.event.data !== undefined ||
|
||||
this.event.event ||
|
||||
this.event.id ||
|
||||
this.event.retry
|
||||
) {
|
||||
this.processEvent(this.event);
|
||||
}
|
||||
this.clearEvent()
|
||||
continue
|
||||
this.clearEvent();
|
||||
continue;
|
||||
}
|
||||
// If the current character is not an end-of-line, then the event
|
||||
// is not finished and we have to reset the eventEndCheck flag
|
||||
this.eventEndCheck = false
|
||||
continue
|
||||
this.eventEndCheck = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the current character is an end-of-line, we can process the
|
||||
@ -255,51 +256,51 @@ class EventSourceStream extends Transform {
|
||||
// set the crlfCheck flag to true, as we need to check if the
|
||||
// next character is a line feed
|
||||
if (this.buffer[this.pos] === CR) {
|
||||
this.crlfCheck = true
|
||||
this.crlfCheck = true;
|
||||
}
|
||||
|
||||
// In any case, we can process the line as we reached an
|
||||
// end-of-line character
|
||||
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
|
||||
this.parseLine(this.buffer.subarray(0, this.pos), this.event);
|
||||
|
||||
// Remove the processed line from the buffer
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
this.buffer = this.buffer.subarray(this.pos + 1);
|
||||
// Reset the position as we removed the processed line from the buffer
|
||||
this.pos = 0
|
||||
this.pos = 0;
|
||||
// A line was processed and this could be the end of the event. We need
|
||||
// to check if the next line is empty to determine if the event is
|
||||
// finished.
|
||||
this.eventEndCheck = true
|
||||
continue
|
||||
this.eventEndCheck = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.pos++
|
||||
this.pos++;
|
||||
}
|
||||
|
||||
callback()
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} line
|
||||
* @param {EventSourceStreamEvent} event
|
||||
*/
|
||||
parseLine (line, event) {
|
||||
parseLine(line, event) {
|
||||
// If the line is empty (a blank line)
|
||||
// Dispatch the event, as defined below.
|
||||
// This will be handled in the _transform method
|
||||
if (line.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// If the line starts with a U+003A COLON character (:)
|
||||
// Ignore the line.
|
||||
const colonPosition = line.indexOf(COLON)
|
||||
const colonPosition = line.indexOf(COLON);
|
||||
if (colonPosition === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let field = ''
|
||||
let value = ''
|
||||
let field = '';
|
||||
let value = '';
|
||||
|
||||
// If the line contains a U+003A COLON character (:)
|
||||
if (colonPosition !== -1) {
|
||||
@ -308,27 +309,27 @@ class EventSourceStream extends Transform {
|
||||
// TODO: Investigate if there is a more performant way to extract the
|
||||
// field
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
field = line.subarray(0, colonPosition).toString('utf8')
|
||||
field = line.subarray(0, colonPosition).toString('utf8');
|
||||
|
||||
// Collect the characters on the line after the first U+003A COLON
|
||||
// character (:), and let value be that string.
|
||||
// If value starts with a U+0020 SPACE character, remove it from value.
|
||||
let valueStart = colonPosition + 1
|
||||
let valueStart = colonPosition + 1;
|
||||
if (line[valueStart] === SPACE) {
|
||||
++valueStart
|
||||
++valueStart;
|
||||
}
|
||||
// TODO: Investigate if there is a more performant way to extract the
|
||||
// value
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
value = line.subarray(valueStart).toString('utf8')
|
||||
value = line.subarray(valueStart).toString('utf8');
|
||||
|
||||
// Otherwise, the string is not empty but does not contain a U+003A COLON
|
||||
// character (:)
|
||||
} else {
|
||||
// Process the field using the steps described below, using the whole
|
||||
// line as the field name, and the empty string as the field value.
|
||||
field = line.toString('utf8')
|
||||
value = ''
|
||||
field = line.toString('utf8');
|
||||
value = '';
|
||||
}
|
||||
|
||||
// Modify the event with the field name and value. The value is also
|
||||
@ -336,39 +337,39 @@ class EventSourceStream extends Transform {
|
||||
switch (field) {
|
||||
case 'data':
|
||||
if (event[field] === undefined) {
|
||||
event[field] = value
|
||||
event[field] = value;
|
||||
} else {
|
||||
event[field] += `\n${value}`
|
||||
event[field] += `\n${value}`;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'retry':
|
||||
if (isASCIINumber(value)) {
|
||||
event[field] = value
|
||||
event[field] = value;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'id':
|
||||
if (isValidLastEventId(value)) {
|
||||
event[field] = value
|
||||
event[field] = value;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'event':
|
||||
if (value.length > 0) {
|
||||
event[field] = value
|
||||
event[field] = value;
|
||||
}
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EventSourceStreamEvent} event
|
||||
*/
|
||||
processEvent (event) {
|
||||
processEvent(event) {
|
||||
if (event.retry && isASCIINumber(event.retry)) {
|
||||
this.state.reconnectionTime = parseInt(event.retry, 10)
|
||||
this.state.reconnectionTime = parseInt(event.retry, 10);
|
||||
}
|
||||
|
||||
if (event.id && isValidLastEventId(event.id)) {
|
||||
this.state.lastEventId = event.id
|
||||
this.state.lastEventId = event.id;
|
||||
}
|
||||
|
||||
// only dispatch event, when data is provided
|
||||
@ -378,22 +379,22 @@ class EventSourceStream extends Transform {
|
||||
options: {
|
||||
data: event.data,
|
||||
lastEventId: this.state.lastEventId,
|
||||
origin: this.state.origin
|
||||
}
|
||||
})
|
||||
origin: this.state.origin,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clearEvent () {
|
||||
clearEvent() {
|
||||
this.event = {
|
||||
data: undefined,
|
||||
event: undefined,
|
||||
id: undefined,
|
||||
retry: undefined
|
||||
}
|
||||
retry: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
EventSourceStream
|
||||
}
|
||||
EventSourceStream,
|
||||
};
|
||||
|
333
node_modules/undici/lib/web/eventsource/eventsource.js
generated
vendored
333
node_modules/undici/lib/web/eventsource/eventsource.js
generated
vendored
@ -1,18 +1,18 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { pipeline } = require('node:stream')
|
||||
const { fetching } = require('../fetch')
|
||||
const { makeRequest } = require('../fetch/request')
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { EventSourceStream } = require('./eventsource-stream')
|
||||
const { parseMIMEType } = require('../fetch/data-url')
|
||||
const { createFastMessageEvent } = require('../websocket/events')
|
||||
const { isNetworkError } = require('../fetch/response')
|
||||
const { delay } = require('./util')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { environmentSettingsObject } = require('../fetch/util')
|
||||
const { pipeline } = require('node:stream');
|
||||
const { fetching } = require('../fetch');
|
||||
const { makeRequest } = require('../fetch/request');
|
||||
const { webidl } = require('../fetch/webidl');
|
||||
const { EventSourceStream } = require('./eventsource-stream');
|
||||
const { parseMIMEType } = require('../fetch/data-url');
|
||||
const { createFastMessageEvent } = require('../websocket/events');
|
||||
const { isNetworkError } = require('../fetch/response');
|
||||
const { delay } = require('./util');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const { environmentSettingsObject } = require('../fetch/util');
|
||||
|
||||
let experimentalWarned = false
|
||||
let experimentalWarned = false;
|
||||
|
||||
/**
|
||||
* A reconnection time, in milliseconds. This must initially be an implementation-defined value,
|
||||
@ -24,7 +24,7 @@ let experimentalWarned = false
|
||||
*
|
||||
* @type {3000}
|
||||
*/
|
||||
const defaultReconnectionTime = 3000
|
||||
const defaultReconnectionTime = 3000;
|
||||
|
||||
/**
|
||||
* The readyState attribute represents the state of the connection.
|
||||
@ -39,32 +39,32 @@ const defaultReconnectionTime = 3000
|
||||
* agent is reconnecting.
|
||||
* @type {0}
|
||||
*/
|
||||
const CONNECTING = 0
|
||||
const CONNECTING = 0;
|
||||
|
||||
/**
|
||||
* The user agent has an open connection and is dispatching events as it
|
||||
* receives them.
|
||||
* @type {1}
|
||||
*/
|
||||
const OPEN = 1
|
||||
const OPEN = 1;
|
||||
|
||||
/**
|
||||
* The connection is not open, and the user agent is not trying to reconnect.
|
||||
* @type {2}
|
||||
*/
|
||||
const CLOSED = 2
|
||||
const CLOSED = 2;
|
||||
|
||||
/**
|
||||
* Requests for the element will have their mode set to "cors" and their credentials mode set to "same-origin".
|
||||
* @type {'anonymous'}
|
||||
*/
|
||||
const ANONYMOUS = 'anonymous'
|
||||
const ANONYMOUS = 'anonymous';
|
||||
|
||||
/**
|
||||
* Requests for the element will have their mode set to "cors" and their credentials mode set to "include".
|
||||
* @type {'use-credentials'}
|
||||
*/
|
||||
const USE_CREDENTIALS = 'use-credentials'
|
||||
const USE_CREDENTIALS = 'use-credentials';
|
||||
|
||||
/**
|
||||
* The EventSource interface is used to receive server-sent events. It
|
||||
@ -78,26 +78,26 @@ class EventSource extends EventTarget {
|
||||
#events = {
|
||||
open: null,
|
||||
error: null,
|
||||
message: null
|
||||
}
|
||||
message: null,
|
||||
};
|
||||
|
||||
#url
|
||||
#withCredentials = false
|
||||
#url;
|
||||
#withCredentials = false;
|
||||
|
||||
/**
|
||||
* @type {ReadyState}
|
||||
*/
|
||||
#readyState = CONNECTING
|
||||
#readyState = CONNECTING;
|
||||
|
||||
#request = null
|
||||
#controller = null
|
||||
#request = null;
|
||||
#controller = null;
|
||||
|
||||
#dispatcher
|
||||
#dispatcher;
|
||||
|
||||
/**
|
||||
* @type {import('./eventsource-stream').eventSourceSettings}
|
||||
*/
|
||||
#state
|
||||
#state;
|
||||
|
||||
/**
|
||||
* Creates a new EventSource object.
|
||||
@ -105,58 +105,65 @@ class EventSource extends EventTarget {
|
||||
* @param {EventSourceInit} [eventSourceInitDict={}]
|
||||
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
|
||||
*/
|
||||
constructor (url, eventSourceInitDict = {}) {
|
||||
constructor(url, eventSourceInitDict = {}) {
|
||||
// 1. Let ev be a new EventSource object.
|
||||
super()
|
||||
super();
|
||||
|
||||
webidl.util.markAsUncloneable(this)
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
const prefix = 'EventSource constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'EventSource constructor';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
if (!experimentalWarned) {
|
||||
experimentalWarned = true
|
||||
process.emitWarning('EventSource is experimental, expect them to change at any time.', {
|
||||
code: 'UNDICI-ES'
|
||||
})
|
||||
experimentalWarned = true;
|
||||
process.emitWarning(
|
||||
'EventSource is experimental, expect them to change at any time.',
|
||||
{
|
||||
code: 'UNDICI-ES',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict, prefix, 'eventSourceInitDict')
|
||||
url = webidl.converters.USVString(url);
|
||||
eventSourceInitDict = webidl.converters.EventSourceInitDict(
|
||||
eventSourceInitDict,
|
||||
prefix,
|
||||
'eventSourceInitDict'
|
||||
);
|
||||
|
||||
this.#dispatcher = eventSourceInitDict.dispatcher
|
||||
this.#dispatcher = eventSourceInitDict.dispatcher;
|
||||
this.#state = {
|
||||
lastEventId: '',
|
||||
reconnectionTime: defaultReconnectionTime
|
||||
}
|
||||
reconnectionTime: defaultReconnectionTime,
|
||||
};
|
||||
|
||||
// 2. Let settings be ev's relevant settings object.
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
||||
const settings = environmentSettingsObject
|
||||
const settings = environmentSettingsObject;
|
||||
|
||||
let urlRecord
|
||||
let urlRecord;
|
||||
|
||||
try {
|
||||
// 3. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings.
|
||||
urlRecord = new URL(url, settings.settingsObject.baseUrl)
|
||||
this.#state.origin = urlRecord.origin
|
||||
urlRecord = new URL(url, settings.settingsObject.baseUrl);
|
||||
this.#state.origin = urlRecord.origin;
|
||||
} catch (e) {
|
||||
// 4. If urlRecord is failure, then throw a "SyntaxError" DOMException.
|
||||
throw new DOMException(e, 'SyntaxError')
|
||||
throw new DOMException(e, 'SyntaxError');
|
||||
}
|
||||
|
||||
// 5. Set ev's url to urlRecord.
|
||||
this.#url = urlRecord.href
|
||||
this.#url = urlRecord.href;
|
||||
|
||||
// 6. Let corsAttributeState be Anonymous.
|
||||
let corsAttributeState = ANONYMOUS
|
||||
let corsAttributeState = ANONYMOUS;
|
||||
|
||||
// 7. If the value of eventSourceInitDict's withCredentials member is true,
|
||||
// then set corsAttributeState to Use Credentials and set ev's
|
||||
// withCredentials attribute to true.
|
||||
if (eventSourceInitDict.withCredentials === true) {
|
||||
corsAttributeState = USE_CREDENTIALS
|
||||
this.#withCredentials = true
|
||||
corsAttributeState = USE_CREDENTIALS;
|
||||
this.#withCredentials = true;
|
||||
}
|
||||
|
||||
// 8. Let request be the result of creating a potential-CORS request given
|
||||
@ -166,30 +173,30 @@ class EventSource extends EventTarget {
|
||||
keepalive: true,
|
||||
// @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
|
||||
mode: 'cors',
|
||||
credentials: corsAttributeState === 'anonymous'
|
||||
? 'same-origin'
|
||||
: 'omit',
|
||||
referrer: 'no-referrer'
|
||||
}
|
||||
credentials: corsAttributeState === 'anonymous' ? 'same-origin' : 'omit',
|
||||
referrer: 'no-referrer',
|
||||
};
|
||||
|
||||
// 9. Set request's client to settings.
|
||||
initRequest.client = environmentSettingsObject.settingsObject
|
||||
initRequest.client = environmentSettingsObject.settingsObject;
|
||||
|
||||
// 10. User agents may set (`Accept`, `text/event-stream`) in request's header list.
|
||||
initRequest.headersList = [['accept', { name: 'accept', value: 'text/event-stream' }]]
|
||||
initRequest.headersList = [
|
||||
['accept', { name: 'accept', value: 'text/event-stream' }],
|
||||
];
|
||||
|
||||
// 11. Set request's cache mode to "no-store".
|
||||
initRequest.cache = 'no-store'
|
||||
initRequest.cache = 'no-store';
|
||||
|
||||
// 12. Set request's initiator type to "other".
|
||||
initRequest.initiator = 'other'
|
||||
initRequest.initiator = 'other';
|
||||
|
||||
initRequest.urlList = [new URL(this.#url)]
|
||||
initRequest.urlList = [new URL(this.#url)];
|
||||
|
||||
// 13. Set ev's request to request.
|
||||
this.#request = makeRequest(initRequest)
|
||||
this.#request = makeRequest(initRequest);
|
||||
|
||||
this.#connect()
|
||||
this.#connect();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,8 +205,8 @@ class EventSource extends EventTarget {
|
||||
* @returns {ReadyState}
|
||||
* @readonly
|
||||
*/
|
||||
get readyState () {
|
||||
return this.#readyState
|
||||
get readyState() {
|
||||
return this.#readyState;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,40 +214,40 @@ class EventSource extends EventTarget {
|
||||
* @readonly
|
||||
* @returns {string}
|
||||
*/
|
||||
get url () {
|
||||
return this.#url
|
||||
get url() {
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the EventSource object was
|
||||
* instantiated with CORS credentials set (true), or not (false, the default).
|
||||
*/
|
||||
get withCredentials () {
|
||||
return this.#withCredentials
|
||||
get withCredentials() {
|
||||
return this.#withCredentials;
|
||||
}
|
||||
|
||||
#connect () {
|
||||
if (this.#readyState === CLOSED) return
|
||||
#connect() {
|
||||
if (this.#readyState === CLOSED) return;
|
||||
|
||||
this.#readyState = CONNECTING
|
||||
this.#readyState = CONNECTING;
|
||||
|
||||
const fetchParams = {
|
||||
request: this.#request,
|
||||
dispatcher: this.#dispatcher
|
||||
}
|
||||
dispatcher: this.#dispatcher,
|
||||
};
|
||||
|
||||
// 14. Let processEventSourceEndOfBody given response res be the following step: if res is not a network error, then reestablish the connection.
|
||||
const processEventSourceEndOfBody = (response) => {
|
||||
if (isNetworkError(response)) {
|
||||
this.dispatchEvent(new Event('error'))
|
||||
this.close()
|
||||
this.dispatchEvent(new Event('error'));
|
||||
this.close();
|
||||
}
|
||||
|
||||
this.#reconnect()
|
||||
}
|
||||
this.#reconnect();
|
||||
};
|
||||
|
||||
// 15. Fetch request, with processResponseEndOfBody set to processEventSourceEndOfBody...
|
||||
fetchParams.processResponseEndOfBody = processEventSourceEndOfBody
|
||||
fetchParams.processResponseEndOfBody = processEventSourceEndOfBody;
|
||||
|
||||
// and processResponse set to the following steps given response res:
|
||||
fetchParams.processResponse = (response) => {
|
||||
@ -254,30 +261,29 @@ class EventSource extends EventTarget {
|
||||
// user agent has failed the connection, it does not attempt to
|
||||
// reconnect.
|
||||
if (response.aborted) {
|
||||
this.close()
|
||||
this.dispatchEvent(new Event('error'))
|
||||
return
|
||||
this.close();
|
||||
this.dispatchEvent(new Event('error'));
|
||||
return;
|
||||
// 2. Otherwise, if res is a network error, then reestablish the
|
||||
// connection, unless the user agent knows that to be futile, in
|
||||
// which case the user agent may fail the connection.
|
||||
} else {
|
||||
this.#reconnect()
|
||||
return
|
||||
this.#reconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Otherwise, if res's status is not 200, or if res's `Content-Type`
|
||||
// is not `text/event-stream`, then fail the connection.
|
||||
const contentType = response.headersList.get('content-type', true)
|
||||
const mimeType = contentType !== null ? parseMIMEType(contentType) : 'failure'
|
||||
const contentTypeValid = mimeType !== 'failure' && mimeType.essence === 'text/event-stream'
|
||||
if (
|
||||
response.status !== 200 ||
|
||||
contentTypeValid === false
|
||||
) {
|
||||
this.close()
|
||||
this.dispatchEvent(new Event('error'))
|
||||
return
|
||||
const contentType = response.headersList.get('content-type', true);
|
||||
const mimeType =
|
||||
contentType !== null ? parseMIMEType(contentType) : 'failure';
|
||||
const contentTypeValid =
|
||||
mimeType !== 'failure' && mimeType.essence === 'text/event-stream';
|
||||
if (response.status !== 200 || contentTypeValid === false) {
|
||||
this.close();
|
||||
this.dispatchEvent(new Event('error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Otherwise, announce the connection and interpret res's body
|
||||
@ -288,42 +294,35 @@ class EventSource extends EventTarget {
|
||||
// value other than CLOSED, sets the readyState attribute to OPEN
|
||||
// and fires an event named open at the EventSource object.
|
||||
// @see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model
|
||||
this.#readyState = OPEN
|
||||
this.dispatchEvent(new Event('open'))
|
||||
this.#readyState = OPEN;
|
||||
this.dispatchEvent(new Event('open'));
|
||||
|
||||
// If redirected to a different origin, set the origin to the new origin.
|
||||
this.#state.origin = response.urlList[response.urlList.length - 1].origin
|
||||
this.#state.origin = response.urlList[response.urlList.length - 1].origin;
|
||||
|
||||
const eventSourceStream = new EventSourceStream({
|
||||
eventSourceSettings: this.#state,
|
||||
push: (event) => {
|
||||
this.dispatchEvent(createFastMessageEvent(
|
||||
event.type,
|
||||
event.options
|
||||
))
|
||||
this.dispatchEvent(createFastMessageEvent(event.type, event.options));
|
||||
},
|
||||
});
|
||||
|
||||
pipeline(response.body.stream, eventSourceStream, (error) => {
|
||||
if (error?.aborted === false) {
|
||||
this.close();
|
||||
this.dispatchEvent(new Event('error'));
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
pipeline(response.body.stream,
|
||||
eventSourceStream,
|
||||
(error) => {
|
||||
if (
|
||||
error?.aborted === false
|
||||
) {
|
||||
this.close()
|
||||
this.dispatchEvent(new Event('error'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.#controller = fetching(fetchParams)
|
||||
this.#controller = fetching(fetchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async #reconnect () {
|
||||
async #reconnect() {
|
||||
// When a user agent is to reestablish the connection, the user agent must
|
||||
// run the following steps. These steps are run in parallel, not as part of
|
||||
// a task. (The tasks that it queues, of course, are run like normal tasks
|
||||
@ -332,22 +331,22 @@ class EventSource extends EventTarget {
|
||||
// 1. Queue a task to run the following steps:
|
||||
|
||||
// 1. If the readyState attribute is set to CLOSED, abort the task.
|
||||
if (this.#readyState === CLOSED) return
|
||||
if (this.#readyState === CLOSED) return;
|
||||
|
||||
// 2. Set the readyState attribute to CONNECTING.
|
||||
this.#readyState = CONNECTING
|
||||
this.#readyState = CONNECTING;
|
||||
|
||||
// 3. Fire an event named error at the EventSource object.
|
||||
this.dispatchEvent(new Event('error'))
|
||||
this.dispatchEvent(new Event('error'));
|
||||
|
||||
// 2. Wait a delay equal to the reconnection time of the event source.
|
||||
await delay(this.#state.reconnectionTime)
|
||||
await delay(this.#state.reconnectionTime);
|
||||
|
||||
// 5. Queue a task to run the following steps:
|
||||
|
||||
// 1. If the EventSource object's readyState attribute is not set to
|
||||
// CONNECTING, then return.
|
||||
if (this.#readyState !== CONNECTING) return
|
||||
if (this.#readyState !== CONNECTING) return;
|
||||
|
||||
// 2. Let request be the EventSource object's request.
|
||||
// 3. If the EventSource object's last event ID string is not the empty
|
||||
@ -357,74 +356,78 @@ class EventSource extends EventTarget {
|
||||
// 2. Set (`Last-Event-ID`, lastEventIDValue) in request's header
|
||||
// list.
|
||||
if (this.#state.lastEventId.length) {
|
||||
this.#request.headersList.set('last-event-id', this.#state.lastEventId, true)
|
||||
this.#request.headersList.set(
|
||||
'last-event-id',
|
||||
this.#state.lastEventId,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Fetch request and process the response obtained in this fashion, if any, as described earlier in this section.
|
||||
this.#connect()
|
||||
this.#connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection, if any, and sets the readyState attribute to
|
||||
* CLOSED.
|
||||
*/
|
||||
close () {
|
||||
webidl.brandCheck(this, EventSource)
|
||||
close() {
|
||||
webidl.brandCheck(this, EventSource);
|
||||
|
||||
if (this.#readyState === CLOSED) return
|
||||
this.#readyState = CLOSED
|
||||
this.#controller.abort()
|
||||
this.#request = null
|
||||
if (this.#readyState === CLOSED) return;
|
||||
this.#readyState = CLOSED;
|
||||
this.#controller.abort();
|
||||
this.#request = null;
|
||||
}
|
||||
|
||||
get onopen () {
|
||||
return this.#events.open
|
||||
get onopen() {
|
||||
return this.#events.open;
|
||||
}
|
||||
|
||||
set onopen (fn) {
|
||||
set onopen(fn) {
|
||||
if (this.#events.open) {
|
||||
this.removeEventListener('open', this.#events.open)
|
||||
this.removeEventListener('open', this.#events.open);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.open = fn
|
||||
this.addEventListener('open', fn)
|
||||
this.#events.open = fn;
|
||||
this.addEventListener('open', fn);
|
||||
} else {
|
||||
this.#events.open = null
|
||||
this.#events.open = null;
|
||||
}
|
||||
}
|
||||
|
||||
get onmessage () {
|
||||
return this.#events.message
|
||||
get onmessage() {
|
||||
return this.#events.message;
|
||||
}
|
||||
|
||||
set onmessage (fn) {
|
||||
set onmessage(fn) {
|
||||
if (this.#events.message) {
|
||||
this.removeEventListener('message', this.#events.message)
|
||||
this.removeEventListener('message', this.#events.message);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.message = fn
|
||||
this.addEventListener('message', fn)
|
||||
this.#events.message = fn;
|
||||
this.addEventListener('message', fn);
|
||||
} else {
|
||||
this.#events.message = null
|
||||
this.#events.message = null;
|
||||
}
|
||||
}
|
||||
|
||||
get onerror () {
|
||||
return this.#events.error
|
||||
get onerror() {
|
||||
return this.#events.error;
|
||||
}
|
||||
|
||||
set onerror (fn) {
|
||||
set onerror(fn) {
|
||||
if (this.#events.error) {
|
||||
this.removeEventListener('error', this.#events.error)
|
||||
this.removeEventListener('error', this.#events.error);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.error = fn
|
||||
this.addEventListener('error', fn)
|
||||
this.#events.error = fn;
|
||||
this.addEventListener('error', fn);
|
||||
} else {
|
||||
this.#events.error = null
|
||||
this.#events.error = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -435,26 +438,26 @@ const constantsPropertyDescriptors = {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: CONNECTING,
|
||||
writable: false
|
||||
writable: false,
|
||||
},
|
||||
OPEN: {
|
||||
__proto__: null,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: OPEN,
|
||||
writable: false
|
||||
writable: false,
|
||||
},
|
||||
CLOSED: {
|
||||
__proto__: null,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: CLOSED,
|
||||
writable: false
|
||||
}
|
||||
}
|
||||
writable: false,
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperties(EventSource, constantsPropertyDescriptors)
|
||||
Object.defineProperties(EventSource.prototype, constantsPropertyDescriptors)
|
||||
Object.defineProperties(EventSource, constantsPropertyDescriptors);
|
||||
Object.defineProperties(EventSource.prototype, constantsPropertyDescriptors);
|
||||
|
||||
Object.defineProperties(EventSource.prototype, {
|
||||
close: kEnumerableProperty,
|
||||
@ -463,22 +466,22 @@ Object.defineProperties(EventSource.prototype, {
|
||||
onopen: kEnumerableProperty,
|
||||
readyState: kEnumerableProperty,
|
||||
url: kEnumerableProperty,
|
||||
withCredentials: kEnumerableProperty
|
||||
})
|
||||
withCredentials: kEnumerableProperty,
|
||||
});
|
||||
|
||||
webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'withCredentials',
|
||||
converter: webidl.converters.boolean,
|
||||
defaultValue: () => false
|
||||
defaultValue: () => false,
|
||||
},
|
||||
{
|
||||
key: 'dispatcher', // undici only
|
||||
converter: webidl.converters.any
|
||||
}
|
||||
])
|
||||
converter: webidl.converters.any,
|
||||
},
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
EventSource,
|
||||
defaultReconnectionTime
|
||||
}
|
||||
defaultReconnectionTime,
|
||||
};
|
||||
|
24
node_modules/undici/lib/web/eventsource/util.js
generated
vendored
24
node_modules/undici/lib/web/eventsource/util.js
generated
vendored
@ -1,13 +1,13 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Checks if the given value is a valid LastEventId.
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidLastEventId (value) {
|
||||
function isValidLastEventId(value) {
|
||||
// LastEventId should not contain U+0000 NULL
|
||||
return value.indexOf('\u0000') === -1
|
||||
return value.indexOf('\u0000') === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -15,23 +15,23 @@ function isValidLastEventId (value) {
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isASCIINumber (value) {
|
||||
if (value.length === 0) return false
|
||||
function isASCIINumber(value) {
|
||||
if (value.length === 0) return false;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (value.charCodeAt(i) < 0x30 || value.charCodeAt(i) > 0x39) return false
|
||||
if (value.charCodeAt(i) < 0x30 || value.charCodeAt(i) > 0x39) return false;
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/undici/issues/2664
|
||||
function delay (ms) {
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms).unref()
|
||||
})
|
||||
setTimeout(resolve, ms).unref();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isValidLastEventId,
|
||||
isASCIINumber,
|
||||
delay
|
||||
}
|
||||
delay,
|
||||
};
|
||||
|
453
node_modules/undici/lib/web/fetch/body.js
generated
vendored
453
node_modules/undici/lib/web/fetch/body.js
generated
vendored
@ -1,99 +1,106 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const util = require('../../core/util')
|
||||
const util = require('../../core/util');
|
||||
const {
|
||||
ReadableStreamFrom,
|
||||
readableStreamClose,
|
||||
createDeferredPromise,
|
||||
fullyReadBody,
|
||||
extractMimeType,
|
||||
utf8DecodeBytes
|
||||
} = require('./util')
|
||||
const { FormData, setFormDataState } = require('./formdata')
|
||||
const { webidl } = require('./webidl')
|
||||
const { Blob } = require('node:buffer')
|
||||
const assert = require('node:assert')
|
||||
const { isErrored, isDisturbed } = require('node:stream')
|
||||
const { isArrayBuffer } = require('node:util/types')
|
||||
const { serializeAMimeType } = require('./data-url')
|
||||
const { multipartFormDataParser } = require('./formdata-parser')
|
||||
let random
|
||||
utf8DecodeBytes,
|
||||
} = require('./util');
|
||||
const { FormData, setFormDataState } = require('./formdata');
|
||||
const { webidl } = require('./webidl');
|
||||
const { Blob } = require('node:buffer');
|
||||
const assert = require('node:assert');
|
||||
const { isErrored, isDisturbed } = require('node:stream');
|
||||
const { isArrayBuffer } = require('node:util/types');
|
||||
const { serializeAMimeType } = require('./data-url');
|
||||
const { multipartFormDataParser } = require('./formdata-parser');
|
||||
let random;
|
||||
|
||||
try {
|
||||
const crypto = require('node:crypto')
|
||||
random = (max) => crypto.randomInt(0, max)
|
||||
const crypto = require('node:crypto');
|
||||
random = (max) => crypto.randomInt(0, max);
|
||||
} catch {
|
||||
random = (max) => Math.floor(Math.random() * max)
|
||||
random = (max) => Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
const textEncoder = new TextEncoder()
|
||||
function noop () {}
|
||||
const textEncoder = new TextEncoder();
|
||||
function noop() {}
|
||||
|
||||
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
|
||||
let streamRegistry
|
||||
const hasFinalizationRegistry =
|
||||
globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0;
|
||||
let streamRegistry;
|
||||
|
||||
if (hasFinalizationRegistry) {
|
||||
streamRegistry = new FinalizationRegistry((weakRef) => {
|
||||
const stream = weakRef.deref()
|
||||
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
|
||||
stream.cancel('Response object has been garbage collected').catch(noop)
|
||||
const stream = weakRef.deref();
|
||||
if (
|
||||
stream &&
|
||||
!stream.locked &&
|
||||
!isDisturbed(stream) &&
|
||||
!isErrored(stream)
|
||||
) {
|
||||
stream.cancel('Response object has been garbage collected').catch(noop);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||||
function extractBody (object, keepalive = false) {
|
||||
function extractBody(object, keepalive = false) {
|
||||
// 1. Let stream be null.
|
||||
let stream = null
|
||||
let stream = null;
|
||||
|
||||
// 2. If object is a ReadableStream object, then set stream to object.
|
||||
if (webidl.is.ReadableStream(object)) {
|
||||
stream = object
|
||||
stream = object;
|
||||
} else if (webidl.is.Blob(object)) {
|
||||
// 3. Otherwise, if object is a Blob object, set stream to the
|
||||
// result of running object’s get stream.
|
||||
stream = object.stream()
|
||||
stream = object.stream();
|
||||
} else {
|
||||
// 4. Otherwise, set stream to a new ReadableStream object, and set
|
||||
// up stream with byte reading support.
|
||||
stream = new ReadableStream({
|
||||
async pull (controller) {
|
||||
const buffer = typeof source === 'string' ? textEncoder.encode(source) : source
|
||||
async pull(controller) {
|
||||
const buffer =
|
||||
typeof source === 'string' ? textEncoder.encode(source) : source;
|
||||
|
||||
if (buffer.byteLength) {
|
||||
controller.enqueue(buffer)
|
||||
controller.enqueue(buffer);
|
||||
}
|
||||
|
||||
queueMicrotask(() => readableStreamClose(controller))
|
||||
queueMicrotask(() => readableStreamClose(controller));
|
||||
},
|
||||
start () {},
|
||||
type: 'bytes'
|
||||
})
|
||||
start() {},
|
||||
type: 'bytes',
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Assert: stream is a ReadableStream object.
|
||||
assert(webidl.is.ReadableStream(stream))
|
||||
assert(webidl.is.ReadableStream(stream));
|
||||
|
||||
// 6. Let action be null.
|
||||
let action = null
|
||||
let action = null;
|
||||
|
||||
// 7. Let source be null.
|
||||
let source = null
|
||||
let source = null;
|
||||
|
||||
// 8. Let length be null.
|
||||
let length = null
|
||||
let length = null;
|
||||
|
||||
// 9. Let type be null.
|
||||
let type = 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
|
||||
source = object;
|
||||
|
||||
// Set type to `text/plain;charset=UTF-8`.
|
||||
type = 'text/plain;charset=UTF-8'
|
||||
type = 'text/plain;charset=UTF-8';
|
||||
} else if (webidl.is.URLSearchParams(object)) {
|
||||
// URLSearchParams
|
||||
|
||||
@ -103,28 +110,33 @@ function extractBody (object, keepalive = false) {
|
||||
// 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()
|
||||
source = object.toString();
|
||||
|
||||
// Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
|
||||
type = '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())
|
||||
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))
|
||||
source = new Uint8Array(
|
||||
object.buffer.slice(
|
||||
object.byteOffset,
|
||||
object.byteOffset + object.byteLength
|
||||
)
|
||||
);
|
||||
} else if (webidl.is.FormData(object)) {
|
||||
const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
|
||||
const prefix = `--${boundary}\r\nContent-Disposition: form-data`
|
||||
const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`;
|
||||
const prefix = `--${boundary}\r\nContent-Disposition: form-data`;
|
||||
|
||||
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||
const escape = (str) =>
|
||||
str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
|
||||
const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, '\r\n')
|
||||
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.
|
||||
@ -132,29 +144,32 @@ function extractBody (object, keepalive = false) {
|
||||
// - That the content-length is calculated in advance.
|
||||
// - And that all parts are pre-encoded and ready to be sent.
|
||||
|
||||
const blobParts = []
|
||||
const rn = new Uint8Array([13, 10]) // '\r\n'
|
||||
length = 0
|
||||
let hasUnknownSizeValue = false
|
||||
const blobParts = [];
|
||||
const rn = new Uint8Array([13, 10]); // '\r\n'
|
||||
length = 0;
|
||||
let hasUnknownSizeValue = false;
|
||||
|
||||
for (const [name, value] of object) {
|
||||
if (typeof value === 'string') {
|
||||
const chunk = textEncoder.encode(prefix +
|
||||
`; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
||||
blobParts.push(chunk)
|
||||
length += chunk.byteLength
|
||||
const chunk = textEncoder.encode(
|
||||
prefix +
|
||||
`; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`
|
||||
);
|
||||
blobParts.push(chunk);
|
||||
length += chunk.byteLength;
|
||||
} else {
|
||||
const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
(value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
|
||||
`Content-Type: ${
|
||||
value.type || 'application/octet-stream'
|
||||
}\r\n\r\n`)
|
||||
blobParts.push(chunk, value, rn)
|
||||
const chunk = textEncoder.encode(
|
||||
`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
(value.name ? `; filename="${escape(value.name)}"` : '') +
|
||||
'\r\n' +
|
||||
`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`
|
||||
);
|
||||
blobParts.push(chunk, value, rn);
|
||||
if (typeof value.size === 'number') {
|
||||
length += chunk.byteLength + value.size + rn.byteLength
|
||||
length += chunk.byteLength + value.size + rn.byteLength;
|
||||
} else {
|
||||
hasUnknownSizeValue = true
|
||||
hasUnknownSizeValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,113 +177,113 @@ function extractBody (object, keepalive = false) {
|
||||
// CRLF is appended to the body to function with legacy servers and match other implementations.
|
||||
// https://github.com/curl/curl/blob/3434c6b46e682452973972e8313613dfa58cd690/lib/mime.c#L1029-L1030
|
||||
// https://github.com/form-data/form-data/issues/63
|
||||
const chunk = textEncoder.encode(`--${boundary}--\r\n`)
|
||||
blobParts.push(chunk)
|
||||
length += chunk.byteLength
|
||||
const chunk = textEncoder.encode(`--${boundary}--\r\n`);
|
||||
blobParts.push(chunk);
|
||||
length += chunk.byteLength;
|
||||
if (hasUnknownSizeValue) {
|
||||
length = null
|
||||
length = null;
|
||||
}
|
||||
|
||||
// Set source to object.
|
||||
source = object
|
||||
source = object;
|
||||
|
||||
action = async function * () {
|
||||
action = async function* () {
|
||||
for (const part of blobParts) {
|
||||
if (part.stream) {
|
||||
yield * part.stream()
|
||||
yield* part.stream();
|
||||
} else {
|
||||
yield part
|
||||
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}`
|
||||
type = `multipart/form-data; boundary=${boundary}`;
|
||||
} else if (webidl.is.Blob(object)) {
|
||||
// Blob
|
||||
|
||||
// Set source to object.
|
||||
source = object
|
||||
source = object;
|
||||
|
||||
// Set length to object’s size.
|
||||
length = object.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
|
||||
type = object.type;
|
||||
}
|
||||
} else if (typeof object[Symbol.asyncIterator] === 'function') {
|
||||
// If keepalive is true, then throw a TypeError.
|
||||
if (keepalive) {
|
||||
throw new TypeError('keepalive')
|
||||
throw new TypeError('keepalive');
|
||||
}
|
||||
|
||||
// If object is disturbed or locked, then throw a TypeError.
|
||||
if (util.isDisturbed(object) || object.locked) {
|
||||
throw new TypeError(
|
||||
'Response body object should not be disturbed or locked'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
stream =
|
||||
webidl.is.ReadableStream(object) ? object : ReadableStreamFrom(object)
|
||||
webidl.is.ReadableStream(object) ? object : ReadableStreamFrom(object);
|
||||
}
|
||||
|
||||
// 11. If source is a byte sequence, then set action to a
|
||||
// step that returns source and length to source’s length.
|
||||
if (typeof source === 'string' || util.isBuffer(source)) {
|
||||
length = Buffer.byteLength(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
|
||||
let iterator;
|
||||
stream = new ReadableStream({
|
||||
async start () {
|
||||
iterator = action(object)[Symbol.asyncIterator]()
|
||||
async start() {
|
||||
iterator = action(object)[Symbol.asyncIterator]();
|
||||
},
|
||||
async pull (controller) {
|
||||
const { value, done } = await iterator.next()
|
||||
async pull(controller) {
|
||||
const { value, done } = await iterator.next();
|
||||
if (done) {
|
||||
// When running action is done, close stream.
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
controller.byobRequest?.respond(0)
|
||||
})
|
||||
controller.close();
|
||||
controller.byobRequest?.respond(0);
|
||||
});
|
||||
} else {
|
||||
// Whenever one or more bytes are available and stream is not errored,
|
||||
// enqueue a Uint8Array wrapping an ArrayBuffer containing the available
|
||||
// bytes into stream.
|
||||
if (!isErrored(stream)) {
|
||||
const buffer = new Uint8Array(value)
|
||||
const buffer = new Uint8Array(value);
|
||||
if (buffer.byteLength) {
|
||||
controller.enqueue(buffer)
|
||||
controller.enqueue(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
return controller.desiredSize > 0;
|
||||
},
|
||||
async cancel (reason) {
|
||||
await iterator.return()
|
||||
async cancel(reason) {
|
||||
await iterator.return();
|
||||
},
|
||||
type: 'bytes'
|
||||
})
|
||||
type: 'bytes',
|
||||
});
|
||||
}
|
||||
|
||||
// 13. Let body be a body whose stream is stream, source is source,
|
||||
// and length is length.
|
||||
const body = { stream, source, length }
|
||||
const body = { stream, source, length };
|
||||
|
||||
// 14. Return (body, type).
|
||||
return [body, type]
|
||||
return [body, type];
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit-safely-extract
|
||||
function safelyExtractBody (object, keepalive = false) {
|
||||
function safelyExtractBody(object, keepalive = false) {
|
||||
// To safely extract a body and a `Content-Type` value from
|
||||
// a byte sequence or BodyInit object object, run these steps:
|
||||
|
||||
@ -276,152 +291,175 @@ function safelyExtractBody (object, keepalive = false) {
|
||||
if (webidl.is.ReadableStream(object)) {
|
||||
// Assert: object is neither disturbed nor locked.
|
||||
// istanbul ignore next
|
||||
assert(!util.isDisturbed(object), 'The body has already been consumed.')
|
||||
assert(!util.isDisturbed(object), 'The body has already been consumed.');
|
||||
// istanbul ignore next
|
||||
assert(!object.locked, 'The stream is locked.')
|
||||
assert(!object.locked, 'The stream is locked.');
|
||||
}
|
||||
|
||||
// 2. Return the results of extracting object.
|
||||
return extractBody(object, keepalive)
|
||||
return extractBody(object, keepalive);
|
||||
}
|
||||
|
||||
function cloneBody (instance, body) {
|
||||
function cloneBody(instance, body) {
|
||||
// To clone a body body, run these steps:
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-clone
|
||||
|
||||
// 1. Let « out1, out2 » be the result of teeing body’s stream.
|
||||
const [out1, out2] = body.stream.tee()
|
||||
const [out1, out2] = body.stream.tee();
|
||||
|
||||
if (hasFinalizationRegistry) {
|
||||
streamRegistry.register(instance, new WeakRef(out1))
|
||||
streamRegistry.register(instance, new WeakRef(out1));
|
||||
}
|
||||
|
||||
// 2. Set body’s stream to out1.
|
||||
body.stream = out1
|
||||
body.stream = out1;
|
||||
|
||||
// 3. Return a body whose stream is out2 and other members are copied from body.
|
||||
return {
|
||||
stream: out2,
|
||||
length: body.length,
|
||||
source: body.source
|
||||
}
|
||||
source: body.source,
|
||||
};
|
||||
}
|
||||
|
||||
function throwIfAborted (state) {
|
||||
function throwIfAborted(state) {
|
||||
if (state.aborted) {
|
||||
throw new DOMException('The operation was aborted.', 'AbortError')
|
||||
throw new DOMException('The operation was aborted.', 'AbortError');
|
||||
}
|
||||
}
|
||||
|
||||
function bodyMixinMethods (instance, getInternalState) {
|
||||
function bodyMixinMethods(instance, getInternalState) {
|
||||
const methods = {
|
||||
blob () {
|
||||
blob() {
|
||||
// The blob() method steps are to return the result of
|
||||
// running consume body with this and the following step
|
||||
// given a byte sequence bytes: return a Blob whose
|
||||
// contents are bytes and whose type attribute is this’s
|
||||
// MIME type.
|
||||
return consumeBody(this, (bytes) => {
|
||||
let mimeType = bodyMimeType(getInternalState(this))
|
||||
return consumeBody(
|
||||
this,
|
||||
(bytes) => {
|
||||
let mimeType = bodyMimeType(getInternalState(this));
|
||||
|
||||
if (mimeType === null) {
|
||||
mimeType = ''
|
||||
} else if (mimeType) {
|
||||
mimeType = serializeAMimeType(mimeType)
|
||||
}
|
||||
if (mimeType === null) {
|
||||
mimeType = '';
|
||||
} else if (mimeType) {
|
||||
mimeType = serializeAMimeType(mimeType);
|
||||
}
|
||||
|
||||
// Return a Blob whose contents are bytes and type attribute
|
||||
// is mimeType.
|
||||
return new Blob([bytes], { type: mimeType })
|
||||
}, instance, getInternalState)
|
||||
// Return a Blob whose contents are bytes and type attribute
|
||||
// is mimeType.
|
||||
return new Blob([bytes], { type: mimeType });
|
||||
},
|
||||
instance,
|
||||
getInternalState
|
||||
);
|
||||
},
|
||||
|
||||
arrayBuffer () {
|
||||
arrayBuffer() {
|
||||
// The arrayBuffer() method steps are to return the result
|
||||
// of running consume body with this and the following step
|
||||
// given a byte sequence bytes: return a new ArrayBuffer
|
||||
// whose contents are bytes.
|
||||
return consumeBody(this, (bytes) => {
|
||||
return new Uint8Array(bytes).buffer
|
||||
}, instance, getInternalState)
|
||||
return consumeBody(
|
||||
this,
|
||||
(bytes) => {
|
||||
return new Uint8Array(bytes).buffer;
|
||||
},
|
||||
instance,
|
||||
getInternalState
|
||||
);
|
||||
},
|
||||
|
||||
text () {
|
||||
text() {
|
||||
// The text() method steps are to return the result of running
|
||||
// consume body with this and UTF-8 decode.
|
||||
return consumeBody(this, utf8DecodeBytes, instance, getInternalState)
|
||||
return consumeBody(this, utf8DecodeBytes, instance, getInternalState);
|
||||
},
|
||||
|
||||
json () {
|
||||
json() {
|
||||
// The json() method steps are to return the result of running
|
||||
// consume body with this and parse JSON from bytes.
|
||||
return consumeBody(this, parseJSONFromBytes, instance, getInternalState)
|
||||
return consumeBody(this, parseJSONFromBytes, instance, getInternalState);
|
||||
},
|
||||
|
||||
formData () {
|
||||
formData() {
|
||||
// The formData() method steps are to return the result of running
|
||||
// consume body with this and the following step given a byte sequence bytes:
|
||||
return consumeBody(this, (value) => {
|
||||
// 1. Let mimeType be the result of get the MIME type with this.
|
||||
const mimeType = bodyMimeType(getInternalState(this))
|
||||
return consumeBody(
|
||||
this,
|
||||
(value) => {
|
||||
// 1. Let mimeType be the result of get the MIME type with this.
|
||||
const mimeType = bodyMimeType(getInternalState(this));
|
||||
|
||||
// 2. If mimeType is non-null, then switch on mimeType’s essence and run
|
||||
// the corresponding steps:
|
||||
if (mimeType !== null) {
|
||||
switch (mimeType.essence) {
|
||||
case 'multipart/form-data': {
|
||||
// 1. ... [long step]
|
||||
// 2. If that fails for some reason, then throw a TypeError.
|
||||
const parsed = multipartFormDataParser(value, mimeType)
|
||||
// 2. If mimeType is non-null, then switch on mimeType’s essence and run
|
||||
// the corresponding steps:
|
||||
if (mimeType !== null) {
|
||||
switch (mimeType.essence) {
|
||||
case 'multipart/form-data': {
|
||||
// 1. ... [long step]
|
||||
// 2. If that fails for some reason, then throw a TypeError.
|
||||
const parsed = multipartFormDataParser(value, mimeType);
|
||||
|
||||
// 3. Return a new FormData object, appending each entry,
|
||||
// resulting from the parsing operation, to its entry list.
|
||||
const fd = new FormData()
|
||||
setFormDataState(fd, parsed)
|
||||
// 3. Return a new FormData object, appending each entry,
|
||||
// resulting from the parsing operation, to its entry list.
|
||||
const fd = new FormData();
|
||||
setFormDataState(fd, parsed);
|
||||
|
||||
return fd
|
||||
}
|
||||
case 'application/x-www-form-urlencoded': {
|
||||
// 1. Let entries be the result of parsing bytes.
|
||||
const entries = new URLSearchParams(value.toString())
|
||||
|
||||
// 2. If entries is failure, then throw a TypeError.
|
||||
|
||||
// 3. Return a new FormData object whose entry list is entries.
|
||||
const fd = new FormData()
|
||||
|
||||
for (const [name, value] of entries) {
|
||||
fd.append(name, value)
|
||||
return fd;
|
||||
}
|
||||
case 'application/x-www-form-urlencoded': {
|
||||
// 1. Let entries be the result of parsing bytes.
|
||||
const entries = new URLSearchParams(value.toString());
|
||||
|
||||
return fd
|
||||
// 2. If entries is failure, then throw a TypeError.
|
||||
|
||||
// 3. Return a new FormData object whose entry list is entries.
|
||||
const fd = new FormData();
|
||||
|
||||
for (const [name, value] of entries) {
|
||||
fd.append(name, value);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Throw a TypeError.
|
||||
throw new TypeError(
|
||||
'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
|
||||
)
|
||||
}, instance, getInternalState)
|
||||
// 3. Throw a TypeError.
|
||||
throw new TypeError(
|
||||
'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
|
||||
);
|
||||
},
|
||||
instance,
|
||||
getInternalState
|
||||
);
|
||||
},
|
||||
|
||||
bytes () {
|
||||
bytes() {
|
||||
// The bytes() method steps are to return the result of running consume body
|
||||
// with this and the following step given a byte sequence bytes: return the
|
||||
// result of creating a Uint8Array from bytes in this’s relevant realm.
|
||||
return consumeBody(this, (bytes) => {
|
||||
return new Uint8Array(bytes)
|
||||
}, instance, getInternalState)
|
||||
}
|
||||
}
|
||||
return consumeBody(
|
||||
this,
|
||||
(bytes) => {
|
||||
return new Uint8Array(bytes);
|
||||
},
|
||||
instance,
|
||||
getInternalState
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return methods
|
||||
return methods;
|
||||
}
|
||||
|
||||
function mixinBody (prototype, getInternalState) {
|
||||
Object.assign(prototype.prototype, bodyMixinMethods(prototype, getInternalState))
|
||||
function mixinBody(prototype, getInternalState) {
|
||||
Object.assign(
|
||||
prototype.prototype,
|
||||
bodyMixinMethods(prototype, getInternalState)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -431,24 +469,29 @@ function mixinBody (prototype, getInternalState) {
|
||||
* @param {any} instance
|
||||
* @param {(target: any) => any} getInternalState
|
||||
*/
|
||||
async function consumeBody (object, convertBytesToJSValue, instance, getInternalState) {
|
||||
webidl.brandCheck(object, instance)
|
||||
async function consumeBody(
|
||||
object,
|
||||
convertBytesToJSValue,
|
||||
instance,
|
||||
getInternalState
|
||||
) {
|
||||
webidl.brandCheck(object, instance);
|
||||
|
||||
const state = getInternalState(object)
|
||||
const state = getInternalState(object);
|
||||
|
||||
// 1. If object is unusable, then return a promise rejected
|
||||
// with a TypeError.
|
||||
if (bodyUnusable(state)) {
|
||||
throw new TypeError('Body is unusable: Body has already been read')
|
||||
throw new TypeError('Body is unusable: Body has already been read');
|
||||
}
|
||||
|
||||
throwIfAborted(state)
|
||||
throwIfAborted(state);
|
||||
|
||||
// 2. Let promise be a new promise.
|
||||
const promise = createDeferredPromise()
|
||||
const promise = createDeferredPromise();
|
||||
|
||||
// 3. Let errorSteps given error be to reject promise with error.
|
||||
const errorSteps = (error) => promise.reject(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
|
||||
@ -456,69 +499,69 @@ async function consumeBody (object, convertBytesToJSValue, instance, getInternal
|
||||
// with that exception.
|
||||
const successSteps = (data) => {
|
||||
try {
|
||||
promise.resolve(convertBytesToJSValue(data))
|
||||
promise.resolve(convertBytesToJSValue(data));
|
||||
} catch (e) {
|
||||
errorSteps(e)
|
||||
errorSteps(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 5. If object’s body is null, then run successSteps with an
|
||||
// empty byte sequence.
|
||||
if (state.body == null) {
|
||||
successSteps(Buffer.allocUnsafe(0))
|
||||
return promise.promise
|
||||
successSteps(Buffer.allocUnsafe(0));
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
// 6. Otherwise, fully read object’s body given successSteps,
|
||||
// errorSteps, and object’s relevant global object.
|
||||
fullyReadBody(state.body, successSteps, errorSteps)
|
||||
fullyReadBody(state.body, successSteps, errorSteps);
|
||||
|
||||
// 7. Return promise.
|
||||
return promise.promise
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#body-unusable
|
||||
* @param {any} object internal state
|
||||
*/
|
||||
function bodyUnusable (object) {
|
||||
const body = object.body
|
||||
function bodyUnusable(object) {
|
||||
const body = object.body;
|
||||
|
||||
// An object including the Body interface mixin is
|
||||
// said to be unusable if its body is non-null and
|
||||
// its body’s stream is disturbed or locked.
|
||||
return body != null && (body.stream.locked || util.isDisturbed(body.stream))
|
||||
return body != null && (body.stream.locked || util.isDisturbed(body.stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function parseJSONFromBytes (bytes) {
|
||||
return JSON.parse(utf8DecodeBytes(bytes))
|
||||
function parseJSONFromBytes(bytes) {
|
||||
return JSON.parse(utf8DecodeBytes(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-body-mime-type
|
||||
* @param {any} requestOrResponse internal state
|
||||
*/
|
||||
function bodyMimeType (requestOrResponse) {
|
||||
function bodyMimeType(requestOrResponse) {
|
||||
// 1. Let headers be null.
|
||||
// 2. If requestOrResponse is a Request object, then set headers to requestOrResponse’s request’s header list.
|
||||
// 3. Otherwise, set headers to requestOrResponse’s response’s header list.
|
||||
/** @type {import('./headers').HeadersList} */
|
||||
const headers = requestOrResponse.headersList
|
||||
const headers = requestOrResponse.headersList;
|
||||
|
||||
// 4. Let mimeType be the result of extracting a MIME type from headers.
|
||||
const mimeType = extractMimeType(headers)
|
||||
const mimeType = extractMimeType(headers);
|
||||
|
||||
// 5. If mimeType is failure, then return null.
|
||||
if (mimeType === 'failure') {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// 6. Return mimeType.
|
||||
return mimeType
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -528,5 +571,5 @@ module.exports = {
|
||||
mixinBody,
|
||||
streamRegistry,
|
||||
hasFinalizationRegistry,
|
||||
bodyUnusable
|
||||
}
|
||||
bodyUnusable,
|
||||
};
|
||||
|
160
node_modules/undici/lib/web/fetch/constants.js
generated
vendored
160
node_modules/undici/lib/web/fetch/constants.js
generated
vendored
@ -1,25 +1,101 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST'])
|
||||
const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
|
||||
const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST']);
|
||||
const corsSafeListedMethodsSet = new Set(corsSafeListedMethods);
|
||||
|
||||
const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304])
|
||||
const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304]);
|
||||
|
||||
const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308])
|
||||
const redirectStatusSet = new Set(redirectStatus)
|
||||
const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308]);
|
||||
const redirectStatusSet = new Set(redirectStatus);
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#block-bad-port
|
||||
*/
|
||||
const badPorts = /** @type {const} */ ([
|
||||
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
|
||||
'87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
|
||||
'139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
|
||||
'540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
|
||||
'2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679',
|
||||
'6697', '10080'
|
||||
])
|
||||
const badPortsSet = new Set(badPorts)
|
||||
'1',
|
||||
'7',
|
||||
'9',
|
||||
'11',
|
||||
'13',
|
||||
'15',
|
||||
'17',
|
||||
'19',
|
||||
'20',
|
||||
'21',
|
||||
'22',
|
||||
'23',
|
||||
'25',
|
||||
'37',
|
||||
'42',
|
||||
'43',
|
||||
'53',
|
||||
'69',
|
||||
'77',
|
||||
'79',
|
||||
'87',
|
||||
'95',
|
||||
'101',
|
||||
'102',
|
||||
'103',
|
||||
'104',
|
||||
'109',
|
||||
'110',
|
||||
'111',
|
||||
'113',
|
||||
'115',
|
||||
'117',
|
||||
'119',
|
||||
'123',
|
||||
'135',
|
||||
'137',
|
||||
'139',
|
||||
'143',
|
||||
'161',
|
||||
'179',
|
||||
'389',
|
||||
'427',
|
||||
'465',
|
||||
'512',
|
||||
'513',
|
||||
'514',
|
||||
'515',
|
||||
'526',
|
||||
'530',
|
||||
'531',
|
||||
'532',
|
||||
'540',
|
||||
'548',
|
||||
'554',
|
||||
'556',
|
||||
'563',
|
||||
'587',
|
||||
'601',
|
||||
'636',
|
||||
'989',
|
||||
'990',
|
||||
'993',
|
||||
'995',
|
||||
'1719',
|
||||
'1720',
|
||||
'1723',
|
||||
'2049',
|
||||
'3659',
|
||||
'4045',
|
||||
'4190',
|
||||
'5060',
|
||||
'5061',
|
||||
'6000',
|
||||
'6566',
|
||||
'6665',
|
||||
'6666',
|
||||
'6667',
|
||||
'6668',
|
||||
'6669',
|
||||
'6679',
|
||||
'6697',
|
||||
'10080',
|
||||
]);
|
||||
const badPortsSet = new Set(badPorts);
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header
|
||||
@ -32,26 +108,32 @@ const referrerPolicyTokens = /** @type {const} */ ([
|
||||
'strict-origin',
|
||||
'origin-when-cross-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url'
|
||||
])
|
||||
'unsafe-url',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
||||
*/
|
||||
const referrerPolicy = /** @type {const} */ ([
|
||||
'',
|
||||
...referrerPolicyTokens
|
||||
])
|
||||
const referrerPolicyTokensSet = new Set(referrerPolicyTokens)
|
||||
const referrerPolicy = /** @type {const} */ (['', ...referrerPolicyTokens]);
|
||||
const referrerPolicyTokensSet = new Set(referrerPolicyTokens);
|
||||
|
||||
const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error'])
|
||||
const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error']);
|
||||
|
||||
const safeMethods = /** @type {const} */ (['GET', 'HEAD', 'OPTIONS', 'TRACE'])
|
||||
const safeMethodsSet = new Set(safeMethods)
|
||||
const safeMethods = /** @type {const} */ (['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
||||
const safeMethodsSet = new Set(safeMethods);
|
||||
|
||||
const requestMode = /** @type {const} */ (['navigate', 'same-origin', 'no-cors', 'cors'])
|
||||
const requestMode = /** @type {const} */ ([
|
||||
'navigate',
|
||||
'same-origin',
|
||||
'no-cors',
|
||||
'cors',
|
||||
]);
|
||||
|
||||
const requestCredentials = /** @type {const} */ (['omit', 'same-origin', 'include'])
|
||||
const requestCredentials = /** @type {const} */ ([
|
||||
'omit',
|
||||
'same-origin',
|
||||
'include',
|
||||
]);
|
||||
|
||||
const requestCache = /** @type {const} */ ([
|
||||
'default',
|
||||
@ -59,8 +141,8 @@ const requestCache = /** @type {const} */ ([
|
||||
'reload',
|
||||
'no-cache',
|
||||
'force-cache',
|
||||
'only-if-cached'
|
||||
])
|
||||
'only-if-cached',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#request-body-header-name
|
||||
@ -74,21 +156,19 @@ const requestBodyHeader = /** @type {const} */ ([
|
||||
// 'Content-Length' is a forbidden header name, which is typically
|
||||
// removed in the Headers implementation. However, undici doesn't
|
||||
// filter out headers, so we add it here.
|
||||
'content-length'
|
||||
])
|
||||
'content-length',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
|
||||
*/
|
||||
const requestDuplex = /** @type {const} */ ([
|
||||
'half'
|
||||
])
|
||||
const requestDuplex = /** @type {const} */ (['half']);
|
||||
|
||||
/**
|
||||
* @see http://fetch.spec.whatwg.org/#forbidden-method
|
||||
*/
|
||||
const forbiddenMethods = /** @type {const} */ (['CONNECT', 'TRACE', 'TRACK'])
|
||||
const forbiddenMethodsSet = new Set(forbiddenMethods)
|
||||
const forbiddenMethods = /** @type {const} */ (['CONNECT', 'TRACE', 'TRACK']);
|
||||
const forbiddenMethodsSet = new Set(forbiddenMethods);
|
||||
|
||||
const subresource = /** @type {const} */ ([
|
||||
'audio',
|
||||
@ -102,9 +182,9 @@ const subresource = /** @type {const} */ ([
|
||||
'track',
|
||||
'video',
|
||||
'xslt',
|
||||
''
|
||||
])
|
||||
const subresourceSet = new Set(subresource)
|
||||
'',
|
||||
]);
|
||||
const subresourceSet = new Set(subresource);
|
||||
|
||||
module.exports = {
|
||||
subresource,
|
||||
@ -127,5 +207,5 @@ module.exports = {
|
||||
corsSafeListedMethodsSet,
|
||||
safeMethodsSet,
|
||||
forbiddenMethodsSet,
|
||||
referrerPolicyTokens: referrerPolicyTokensSet
|
||||
}
|
||||
referrerPolicyTokens: referrerPolicyTokensSet,
|
||||
};
|
||||
|
412
node_modules/undici/lib/web/fetch/data-url.js
generated
vendored
412
node_modules/undici/lib/web/fetch/data-url.js
generated
vendored
@ -1,45 +1,41 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert')
|
||||
const assert = require('node:assert');
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
|
||||
*/
|
||||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/
|
||||
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line
|
||||
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
|
||||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/;
|
||||
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/; // eslint-disable-line
|
||||
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g; // eslint-disable-line
|
||||
/**
|
||||
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
|
||||
*/
|
||||
const HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/ // eslint-disable-line
|
||||
const HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/; // eslint-disable-line
|
||||
|
||||
// https://fetch.spec.whatwg.org/#data-url-processor
|
||||
/** @param {URL} dataURL */
|
||||
function dataURLProcessor (dataURL) {
|
||||
function dataURLProcessor(dataURL) {
|
||||
// 1. Assert: dataURL’s scheme is "data".
|
||||
assert(dataURL.protocol === '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)
|
||||
let input = URLSerializer(dataURL, true);
|
||||
|
||||
// 3. Remove the leading "data:" string from input.
|
||||
input = input.slice(5)
|
||||
input = input.slice(5);
|
||||
|
||||
// 4. Let position point at the start of input.
|
||||
const position = { position: 0 }
|
||||
const position = { position: 0 };
|
||||
|
||||
// 5. Let mimeType be the result of collecting a
|
||||
// sequence of code points that are not equal
|
||||
// to U+002C (,), given position.
|
||||
let mimeType = collectASequenceOfCodePointsFast(
|
||||
',',
|
||||
input,
|
||||
position
|
||||
)
|
||||
let mimeType = collectASequenceOfCodePointsFast(',', input, position);
|
||||
|
||||
// 6. Strip leading and trailing ASCII whitespace
|
||||
// from mimeType.
|
||||
@ -47,71 +43,71 @@ function dataURLProcessor (dataURL) {
|
||||
// length because if the mimetype has spaces removed,
|
||||
// the wrong amount will be sliced from the input in
|
||||
// step #9
|
||||
const mimeTypeLength = mimeType.length
|
||||
mimeType = removeASCIIWhitespace(mimeType, true, true)
|
||||
const mimeTypeLength = mimeType.length;
|
||||
mimeType = removeASCIIWhitespace(mimeType, true, true);
|
||||
|
||||
// 7. If position is past the end of input, then
|
||||
// return failure
|
||||
if (position.position >= input.length) {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 8. Advance position by 1.
|
||||
position.position++
|
||||
position.position++;
|
||||
|
||||
// 9. Let encodedBody be the remainder of input.
|
||||
const encodedBody = input.slice(mimeTypeLength + 1)
|
||||
const encodedBody = input.slice(mimeTypeLength + 1);
|
||||
|
||||
// 10. Let body be the percent-decoding of encodedBody.
|
||||
let body = stringPercentDecode(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)
|
||||
const stringBody = isomorphicDecode(body);
|
||||
|
||||
// 2. Set body to the forgiving-base64 decode of
|
||||
// stringBody.
|
||||
body = forgivingBase64(stringBody)
|
||||
body = forgivingBase64(stringBody);
|
||||
|
||||
// 3. If body is failure, then return failure.
|
||||
if (body === 'failure') {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 4. Remove the last 6 code points from mimeType.
|
||||
mimeType = mimeType.slice(0, -6)
|
||||
mimeType = mimeType.slice(0, -6);
|
||||
|
||||
// 5. Remove trailing U+0020 SPACE code points from mimeType,
|
||||
// if any.
|
||||
mimeType = mimeType.replace(/(\u0020)+$/, '')
|
||||
mimeType = mimeType.replace(/(\u0020)+$/, '');
|
||||
|
||||
// 6. Remove the last U+003B (;) code point from mimeType.
|
||||
mimeType = mimeType.slice(0, -1)
|
||||
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
|
||||
mimeType = 'text/plain' + mimeType;
|
||||
}
|
||||
|
||||
// 13. Let mimeTypeRecord be the result of parsing
|
||||
// mimeType.
|
||||
let mimeTypeRecord = parseMIMEType(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')
|
||||
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 }
|
||||
return { mimeType: mimeTypeRecord, body };
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-url-serializer
|
||||
@ -119,21 +115,22 @@ function dataURLProcessor (dataURL) {
|
||||
* @param {URL} url
|
||||
* @param {boolean} excludeFragment
|
||||
*/
|
||||
function URLSerializer (url, excludeFragment = false) {
|
||||
function URLSerializer(url, excludeFragment = false) {
|
||||
if (!excludeFragment) {
|
||||
return url.href
|
||||
return url.href;
|
||||
}
|
||||
|
||||
const href = url.href
|
||||
const hashLength = url.hash.length
|
||||
const href = url.href;
|
||||
const hashLength = url.hash.length;
|
||||
|
||||
const serialized = hashLength === 0 ? href : href.substring(0, href.length - hashLength)
|
||||
const serialized =
|
||||
hashLength === 0 ? href : href.substring(0, href.length - hashLength);
|
||||
|
||||
if (!hashLength && href.endsWith('#')) {
|
||||
return serialized.slice(0, -1)
|
||||
return serialized.slice(0, -1);
|
||||
}
|
||||
|
||||
return serialized
|
||||
return serialized;
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
||||
@ -142,22 +139,25 @@ function URLSerializer (url, excludeFragment = false) {
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfCodePoints (condition, input, position) {
|
||||
function collectASequenceOfCodePoints(condition, input, position) {
|
||||
// 1. Let result be the empty string.
|
||||
let result = ''
|
||||
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])) {
|
||||
while (
|
||||
position.position < input.length &&
|
||||
condition(input[position.position])
|
||||
) {
|
||||
// 1. Append that code point to the end of result.
|
||||
result += input[position.position]
|
||||
result += input[position.position];
|
||||
|
||||
// 2. Advance position by 1.
|
||||
position.position++
|
||||
position.position++;
|
||||
}
|
||||
|
||||
// 3. Return result.
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,150 +166,147 @@ function collectASequenceOfCodePoints (condition, input, position) {
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfCodePointsFast (char, input, position) {
|
||||
const idx = input.indexOf(char, position.position)
|
||||
const start = position.position
|
||||
function collectASequenceOfCodePointsFast(char, input, position) {
|
||||
const idx = input.indexOf(char, position.position);
|
||||
const start = position.position;
|
||||
|
||||
if (idx === -1) {
|
||||
position.position = input.length
|
||||
return input.slice(start)
|
||||
position.position = input.length;
|
||||
return input.slice(start);
|
||||
}
|
||||
|
||||
position.position = idx
|
||||
return input.slice(start, position.position)
|
||||
position.position = idx;
|
||||
return input.slice(start, position.position);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#string-percent-decode
|
||||
/** @param {string} input */
|
||||
function stringPercentDecode (input) {
|
||||
function stringPercentDecode(input) {
|
||||
// 1. Let bytes be the UTF-8 encoding of input.
|
||||
const bytes = encoder.encode(input)
|
||||
const bytes = encoder.encode(input);
|
||||
|
||||
// 2. Return the percent-decoding of bytes.
|
||||
return percentDecode(bytes)
|
||||
return percentDecode(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} byte
|
||||
*/
|
||||
function isHexCharByte (byte) {
|
||||
function isHexCharByte(byte) {
|
||||
// 0-9 A-F a-f
|
||||
return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) || (byte >= 0x61 && byte <= 0x66)
|
||||
return (
|
||||
(byte >= 0x30 && byte <= 0x39) ||
|
||||
(byte >= 0x41 && byte <= 0x46) ||
|
||||
(byte >= 0x61 && byte <= 0x66)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} byte
|
||||
*/
|
||||
function hexByteToNumber (byte) {
|
||||
function hexByteToNumber(byte) {
|
||||
return (
|
||||
// 0-9
|
||||
byte >= 0x30 && byte <= 0x39
|
||||
? (byte - 48)
|
||||
// Convert to uppercase
|
||||
// ((byte & 0xDF) - 65) + 10
|
||||
: ((byte & 0xDF) - 55)
|
||||
)
|
||||
byte >= 0x30 && byte <= 0x39 ?
|
||||
byte - 48
|
||||
// Convert to uppercase
|
||||
// ((byte & 0xDF) - 65) + 10
|
||||
: (byte & 0xdf) - 55
|
||||
);
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#percent-decode
|
||||
/** @param {Uint8Array} input */
|
||||
function percentDecode (input) {
|
||||
const length = input.length
|
||||
function percentDecode(input) {
|
||||
const length = input.length;
|
||||
// 1. Let output be an empty byte sequence.
|
||||
/** @type {Uint8Array} */
|
||||
const output = new Uint8Array(length)
|
||||
let j = 0
|
||||
const output = new Uint8Array(length);
|
||||
let j = 0;
|
||||
// 2. For each byte byte in input:
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const byte = input[i]
|
||||
const byte = input[i];
|
||||
|
||||
// 1. If byte is not 0x25 (%), then append byte to output.
|
||||
if (byte !== 0x25) {
|
||||
output[j++] = byte
|
||||
output[j++] = byte;
|
||||
|
||||
// 2. Otherwise, if byte is 0x25 (%) and the next two bytes
|
||||
// after byte in input are not in the ranges
|
||||
// 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
|
||||
// and 0x61 (a) to 0x66 (f), all inclusive, append byte
|
||||
// to output.
|
||||
// 2. Otherwise, if byte is 0x25 (%) and the next two bytes
|
||||
// after byte in input are not in the ranges
|
||||
// 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
|
||||
// and 0x61 (a) to 0x66 (f), all inclusive, append byte
|
||||
// to output.
|
||||
} else if (
|
||||
byte === 0x25 &&
|
||||
!(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
|
||||
) {
|
||||
output[j++] = 0x25
|
||||
output[j++] = 0x25;
|
||||
|
||||
// 3. Otherwise:
|
||||
// 3. Otherwise:
|
||||
} else {
|
||||
// 1. Let bytePoint be the two bytes after byte in input,
|
||||
// decoded, and then interpreted as hexadecimal number.
|
||||
// 2. Append a byte whose value is bytePoint to output.
|
||||
output[j++] = (hexByteToNumber(input[i + 1]) << 4) | hexByteToNumber(input[i + 2])
|
||||
output[j++] =
|
||||
(hexByteToNumber(input[i + 1]) << 4) | hexByteToNumber(input[i + 2]);
|
||||
|
||||
// 3. Skip the next two bytes in input.
|
||||
i += 2
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return output.
|
||||
return length === j ? output : output.subarray(0, j)
|
||||
return length === j ? output : output.subarray(0, j);
|
||||
}
|
||||
|
||||
// https://mimesniff.spec.whatwg.org/#parse-a-mime-type
|
||||
/** @param {string} input */
|
||||
function parseMIMEType (input) {
|
||||
function parseMIMEType(input) {
|
||||
// 1. Remove any leading and trailing HTTP whitespace
|
||||
// from input.
|
||||
input = removeHTTPWhitespace(input, true, true)
|
||||
input = removeHTTPWhitespace(input, true, true);
|
||||
|
||||
// 2. Let position be a position variable for input,
|
||||
// initially pointing at the start of input.
|
||||
const position = { position: 0 }
|
||||
const position = { position: 0 };
|
||||
|
||||
// 3. Let type be the result of collecting a sequence
|
||||
// of code points that are not U+002F (/) from
|
||||
// input, given position.
|
||||
const type = collectASequenceOfCodePointsFast(
|
||||
'/',
|
||||
input,
|
||||
position
|
||||
)
|
||||
const type = collectASequenceOfCodePointsFast('/', input, position);
|
||||
|
||||
// 4. If type is the empty string or does not solely
|
||||
// contain HTTP token code points, then return failure.
|
||||
// https://mimesniff.spec.whatwg.org/#http-token-code-point
|
||||
if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 5. If position is past the end of input, then return
|
||||
// failure
|
||||
if (position.position >= input.length) {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 6. Advance position by 1. (This skips past U+002F (/).)
|
||||
position.position++
|
||||
position.position++;
|
||||
|
||||
// 7. Let subtype be the result of collecting a sequence of
|
||||
// code points that are not U+003B (;) from input, given
|
||||
// position.
|
||||
let subtype = collectASequenceOfCodePointsFast(
|
||||
';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
let subtype = collectASequenceOfCodePointsFast(';', input, position);
|
||||
|
||||
// 8. Remove any trailing HTTP whitespace from subtype.
|
||||
subtype = removeHTTPWhitespace(subtype, false, true)
|
||||
subtype = removeHTTPWhitespace(subtype, false, true);
|
||||
|
||||
// 9. If subtype is the empty string or does not solely
|
||||
// contain HTTP token code points, then return failure.
|
||||
if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
const typeLowercase = type.toLowerCase()
|
||||
const subtypeLowercase = subtype.toLowerCase()
|
||||
const typeLowercase = type.toLowerCase();
|
||||
const subtypeLowercase = subtype.toLowerCase();
|
||||
|
||||
// 10. Let mimeType be a new MIME type record whose type
|
||||
// is type, in ASCII lowercase, and subtype is subtype,
|
||||
@ -321,22 +318,22 @@ function parseMIMEType (input) {
|
||||
/** @type {Map<string, string>} */
|
||||
parameters: new Map(),
|
||||
// https://mimesniff.spec.whatwg.org/#mime-type-essence
|
||||
essence: `${typeLowercase}/${subtypeLowercase}`
|
||||
}
|
||||
essence: `${typeLowercase}/${subtypeLowercase}`,
|
||||
};
|
||||
|
||||
// 11. While position is not past the end of input:
|
||||
while (position.position < input.length) {
|
||||
// 1. Advance position by 1. (This skips past U+003B (;).)
|
||||
position.position++
|
||||
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),
|
||||
(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 (;)
|
||||
@ -345,31 +342,31 @@ function parseMIMEType (input) {
|
||||
(char) => char !== ';' && char !== '=',
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 4. Set parameterName to parameterName, in ASCII
|
||||
// lowercase.
|
||||
parameterName = parameterName.toLowerCase()
|
||||
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
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Advance position by 1. (This skips past U+003D (=).)
|
||||
position.position++
|
||||
position.position++;
|
||||
}
|
||||
|
||||
// 6. If position is past the end of input, then break.
|
||||
if (position.position >= input.length) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
// 7. Let parameterValue be null.
|
||||
let parameterValue = null
|
||||
let parameterValue = null;
|
||||
|
||||
// 8. If the code point at position within input is
|
||||
// U+0022 ("), then:
|
||||
@ -377,33 +374,25 @@ function parseMIMEType (input) {
|
||||
// 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)
|
||||
parameterValue = collectAnHTTPQuotedString(input, position, true);
|
||||
|
||||
// 2. Collect a sequence of code points that are not
|
||||
// U+003B (;) from input, given position.
|
||||
collectASequenceOfCodePointsFast(
|
||||
';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
collectASequenceOfCodePointsFast(';', input, position);
|
||||
|
||||
// 9. Otherwise:
|
||||
// 9. Otherwise:
|
||||
} else {
|
||||
// 1. Set parameterValue to the result of collecting
|
||||
// a sequence of code points that are not U+003B (;)
|
||||
// from input, given position.
|
||||
parameterValue = collectASequenceOfCodePointsFast(
|
||||
';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
parameterValue = collectASequenceOfCodePointsFast(';', input, position);
|
||||
|
||||
// 2. Remove any trailing HTTP whitespace from parameterValue.
|
||||
parameterValue = removeHTTPWhitespace(parameterValue, false, true)
|
||||
parameterValue = removeHTTPWhitespace(parameterValue, false, true);
|
||||
|
||||
// 3. If parameterValue is the empty string, then continue.
|
||||
if (parameterValue.length === 0) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,33 +405,34 @@ function parseMIMEType (input) {
|
||||
if (
|
||||
parameterName.length !== 0 &&
|
||||
HTTP_TOKEN_CODEPOINTS.test(parameterName) &&
|
||||
(parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) &&
|
||||
(parameterValue.length === 0 ||
|
||||
HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) &&
|
||||
!mimeType.parameters.has(parameterName)
|
||||
) {
|
||||
mimeType.parameters.set(parameterName, parameterValue)
|
||||
mimeType.parameters.set(parameterName, parameterValue);
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Return mimeType.
|
||||
return mimeType
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#forgiving-base64-decode
|
||||
/** @param {string} data */
|
||||
function forgivingBase64 (data) {
|
||||
function forgivingBase64(data) {
|
||||
// 1. Remove all ASCII whitespace from data.
|
||||
data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '')
|
||||
data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '');
|
||||
|
||||
let dataLength = data.length
|
||||
let dataLength = data.length;
|
||||
// 2. If data’s code point length divides by 4 leaving
|
||||
// no remainder, then:
|
||||
if (dataLength % 4 === 0) {
|
||||
// 1. If data ends with one or two U+003D (=) code points,
|
||||
// then remove them from data.
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
||||
--dataLength
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003D) {
|
||||
--dataLength
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003d) {
|
||||
--dataLength;
|
||||
if (data.charCodeAt(dataLength - 1) === 0x003d) {
|
||||
--dataLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -450,7 +440,7 @@ function forgivingBase64 (data) {
|
||||
// 3. If data’s code point length divides by 4 leaving
|
||||
// a remainder of 1, then return failure.
|
||||
if (dataLength % 4 === 1) {
|
||||
return 'failure'
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 4. If data contains a code point that is not one of
|
||||
@ -458,12 +448,16 @@ function forgivingBase64 (data) {
|
||||
// U+002F (/)
|
||||
// ASCII alphanumeric
|
||||
// then return failure.
|
||||
if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
|
||||
return 'failure'
|
||||
if (
|
||||
/[^+/0-9A-Za-z]/.test(
|
||||
data.length === dataLength ? data : data.substring(0, dataLength)
|
||||
)
|
||||
) {
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(data, 'base64')
|
||||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
||||
const buffer = Buffer.from(data, 'base64');
|
||||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
||||
@ -473,19 +467,19 @@ function forgivingBase64 (data) {
|
||||
* @param {{ position: number }} position
|
||||
* @param {boolean} [extractValue=false]
|
||||
*/
|
||||
function collectAnHTTPQuotedString (input, position, extractValue = false) {
|
||||
function collectAnHTTPQuotedString(input, position, extractValue = false) {
|
||||
// 1. Let positionStart be position.
|
||||
const positionStart = position.position
|
||||
const positionStart = position.position;
|
||||
|
||||
// 2. Let value be the empty string.
|
||||
let value = ''
|
||||
let value = '';
|
||||
|
||||
// 3. Assert: the code point at position within input
|
||||
// is U+0022 (").
|
||||
assert(input[position.position] === '"')
|
||||
assert(input[position.position] === '"');
|
||||
|
||||
// 4. Advance position by 1.
|
||||
position.position++
|
||||
position.position++;
|
||||
|
||||
// 5. While true:
|
||||
while (true) {
|
||||
@ -496,106 +490,106 @@ function collectAnHTTPQuotedString (input, position, extractValue = false) {
|
||||
(char) => char !== '"' && char !== '\\',
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 2. If position is past the end of input, then break.
|
||||
if (position.position >= input.length) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. Let quoteOrBackslash be the code point at position within
|
||||
// input.
|
||||
const quoteOrBackslash = input[position.position]
|
||||
const quoteOrBackslash = input[position.position];
|
||||
|
||||
// 4. Advance position by 1.
|
||||
position.position++
|
||||
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
|
||||
value += '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Append the code point at position within input to value.
|
||||
value += input[position.position]
|
||||
value += input[position.position];
|
||||
|
||||
// 3. Advance position by 1.
|
||||
position.position++
|
||||
position.position++;
|
||||
|
||||
// 6. Otherwise:
|
||||
// 6. Otherwise:
|
||||
} else {
|
||||
// 1. Assert: quoteOrBackslash is U+0022 (").
|
||||
assert(quoteOrBackslash === '"')
|
||||
assert(quoteOrBackslash === '"');
|
||||
|
||||
// 2. Break.
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. If the extract-value flag is set, then return value.
|
||||
if (extractValue) {
|
||||
return value
|
||||
return value;
|
||||
}
|
||||
|
||||
// 7. Return the code points from positionStart to position,
|
||||
// inclusive, within input.
|
||||
return input.slice(positionStart, position.position)
|
||||
return input.slice(positionStart, position.position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type
|
||||
*/
|
||||
function serializeAMimeType (mimeType) {
|
||||
assert(mimeType !== 'failure')
|
||||
const { parameters, essence } = mimeType
|
||||
function serializeAMimeType(mimeType) {
|
||||
assert(mimeType !== 'failure');
|
||||
const { parameters, essence } = mimeType;
|
||||
|
||||
// 1. Let serialization be the concatenation of mimeType’s
|
||||
// type, U+002F (/), and mimeType’s subtype.
|
||||
let serialization = essence
|
||||
let serialization = essence;
|
||||
|
||||
// 2. For each name → value of mimeType’s parameters:
|
||||
for (let [name, value] of parameters.entries()) {
|
||||
// 1. Append U+003B (;) to serialization.
|
||||
serialization += ';'
|
||||
serialization += ';';
|
||||
|
||||
// 2. Append name to serialization.
|
||||
serialization += name
|
||||
serialization += name;
|
||||
|
||||
// 3. Append U+003D (=) to serialization.
|
||||
serialization += '='
|
||||
serialization += '=';
|
||||
|
||||
// 4. If value does not solely contain HTTP token code
|
||||
// points or value is the empty string, then:
|
||||
if (!HTTP_TOKEN_CODEPOINTS.test(value)) {
|
||||
// 1. Precede each occurrence of U+0022 (") or
|
||||
// U+005C (\) in value with U+005C (\).
|
||||
value = value.replace(/(\\|")/g, '\\$1')
|
||||
value = value.replace(/(\\|")/g, '\\$1');
|
||||
|
||||
// 2. Prepend U+0022 (") to value.
|
||||
value = '"' + value
|
||||
value = '"' + value;
|
||||
|
||||
// 3. Append U+0022 (") to value.
|
||||
value += '"'
|
||||
value += '"';
|
||||
}
|
||||
|
||||
// 5. Append value to serialization.
|
||||
serialization += value
|
||||
serialization += value;
|
||||
}
|
||||
|
||||
// 3. Return serialization.
|
||||
return serialization
|
||||
return serialization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#http-whitespace
|
||||
* @param {number} char
|
||||
*/
|
||||
function isHTTPWhiteSpace (char) {
|
||||
function isHTTPWhiteSpace(char) {
|
||||
// "\r\n\t "
|
||||
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x020
|
||||
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x020;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -604,17 +598,23 @@ function isHTTPWhiteSpace (char) {
|
||||
* @param {boolean} [leading=true]
|
||||
* @param {boolean} [trailing=true]
|
||||
*/
|
||||
function removeHTTPWhitespace (str, leading = true, trailing = true) {
|
||||
return removeChars(str, leading, trailing, isHTTPWhiteSpace)
|
||||
function removeHTTPWhitespace(str, leading = true, trailing = true) {
|
||||
return removeChars(str, leading, trailing, isHTTPWhiteSpace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://infra.spec.whatwg.org/#ascii-whitespace
|
||||
* @param {number} char
|
||||
*/
|
||||
function isASCIIWhitespace (char) {
|
||||
function isASCIIWhitespace(char) {
|
||||
// "\r\n\t\f "
|
||||
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x00c || char === 0x020
|
||||
return (
|
||||
char === 0x00d ||
|
||||
char === 0x00a ||
|
||||
char === 0x009 ||
|
||||
char === 0x00c ||
|
||||
char === 0x020
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -623,8 +623,8 @@ function isASCIIWhitespace (char) {
|
||||
* @param {boolean} [leading=true]
|
||||
* @param {boolean} [trailing=true]
|
||||
*/
|
||||
function removeASCIIWhitespace (str, leading = true, trailing = true) {
|
||||
return removeChars(str, leading, trailing, isASCIIWhitespace)
|
||||
function removeASCIIWhitespace(str, leading = true, trailing = true) {
|
||||
return removeChars(str, leading, trailing, isASCIIWhitespace);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -634,19 +634,21 @@ function removeASCIIWhitespace (str, leading = true, trailing = true) {
|
||||
* @param {(charCode: number) => boolean} predicate
|
||||
* @returns
|
||||
*/
|
||||
function removeChars (str, leading, trailing, predicate) {
|
||||
let lead = 0
|
||||
let trail = str.length - 1
|
||||
function removeChars(str, leading, trailing, predicate) {
|
||||
let lead = 0;
|
||||
let trail = str.length - 1;
|
||||
|
||||
if (leading) {
|
||||
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
|
||||
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++;
|
||||
}
|
||||
|
||||
if (trailing) {
|
||||
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
|
||||
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--;
|
||||
}
|
||||
|
||||
return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
|
||||
return lead === 0 && trail === str.length - 1 ?
|
||||
str
|
||||
: str.slice(lead, trail + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -654,30 +656,34 @@ function removeChars (str, leading, trailing, predicate) {
|
||||
* @param {Uint8Array} input
|
||||
* @returns {string}
|
||||
*/
|
||||
function isomorphicDecode (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.
|
||||
const length = input.length
|
||||
const length = input.length;
|
||||
if ((2 << 15) - 1 > length) {
|
||||
return String.fromCharCode.apply(null, input)
|
||||
return String.fromCharCode.apply(null, input);
|
||||
}
|
||||
let result = ''; let i = 0
|
||||
let addition = (2 << 15) - 1
|
||||
let result = '';
|
||||
let i = 0;
|
||||
let addition = (2 << 15) - 1;
|
||||
while (i < length) {
|
||||
if (i + addition > length) {
|
||||
addition = length - i
|
||||
addition = length - i;
|
||||
}
|
||||
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
|
||||
result += String.fromCharCode.apply(
|
||||
null,
|
||||
input.subarray(i, (i += addition))
|
||||
);
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://mimesniff.spec.whatwg.org/#minimize-a-supported-mime-type
|
||||
* @param {Exclude<ReturnType<typeof parseMIMEType>, 'failure'>} mimeType
|
||||
*/
|
||||
function minimizeSupportedMimeType (mimeType) {
|
||||
function minimizeSupportedMimeType(mimeType) {
|
||||
switch (mimeType.essence) {
|
||||
case 'application/ecmascript':
|
||||
case 'application/javascript':
|
||||
@ -696,35 +702,35 @@ function minimizeSupportedMimeType (mimeType) {
|
||||
case 'text/x-ecmascript':
|
||||
case 'text/x-javascript':
|
||||
// 1. If mimeType is a JavaScript MIME type, then return "text/javascript".
|
||||
return 'text/javascript'
|
||||
return 'text/javascript';
|
||||
case 'application/json':
|
||||
case 'text/json':
|
||||
// 2. If mimeType is a JSON MIME type, then return "application/json".
|
||||
return 'application/json'
|
||||
return 'application/json';
|
||||
case 'image/svg+xml':
|
||||
// 3. If mimeType’s essence is "image/svg+xml", then return "image/svg+xml".
|
||||
return 'image/svg+xml'
|
||||
return 'image/svg+xml';
|
||||
case 'text/xml':
|
||||
case 'application/xml':
|
||||
// 4. If mimeType is an XML MIME type, then return "application/xml".
|
||||
return 'application/xml'
|
||||
return 'application/xml';
|
||||
}
|
||||
|
||||
// 2. If mimeType is a JSON MIME type, then return "application/json".
|
||||
if (mimeType.subtype.endsWith('+json')) {
|
||||
return 'application/json'
|
||||
return 'application/json';
|
||||
}
|
||||
|
||||
// 4. If mimeType is an XML MIME type, then return "application/xml".
|
||||
if (mimeType.subtype.endsWith('+xml')) {
|
||||
return 'application/xml'
|
||||
return 'application/xml';
|
||||
}
|
||||
|
||||
// 5. If mimeType is supported by the user agent, then return mimeType’s essence.
|
||||
// Technically, node doesn't support any mimetypes.
|
||||
|
||||
// 6. Return the empty string.
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -740,5 +746,5 @@ module.exports = {
|
||||
removeHTTPWhitespace,
|
||||
minimizeSupportedMimeType,
|
||||
HTTP_TOKEN_CODEPOINTS,
|
||||
isomorphicDecode
|
||||
}
|
||||
isomorphicDecode,
|
||||
};
|
||||
|
38
node_modules/undici/lib/web/fetch/dispatcher-weakref.js
generated
vendored
38
node_modules/undici/lib/web/fetch/dispatcher-weakref.js
generated
vendored
@ -1,46 +1,46 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { kConnected, kSize } = require('../../core/symbols')
|
||||
const { kConnected, kSize } = require('../../core/symbols');
|
||||
|
||||
class CompatWeakRef {
|
||||
constructor (value) {
|
||||
this.value = value
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
deref () {
|
||||
return this.value[kConnected] === 0 && this.value[kSize] === 0
|
||||
? undefined
|
||||
: this.value
|
||||
deref() {
|
||||
return this.value[kConnected] === 0 && this.value[kSize] === 0 ?
|
||||
undefined
|
||||
: this.value;
|
||||
}
|
||||
}
|
||||
|
||||
class CompatFinalizer {
|
||||
constructor (finalizer) {
|
||||
this.finalizer = finalizer
|
||||
constructor(finalizer) {
|
||||
this.finalizer = finalizer;
|
||||
}
|
||||
|
||||
register (dispatcher, key) {
|
||||
register(dispatcher, key) {
|
||||
if (dispatcher.on) {
|
||||
dispatcher.on('disconnect', () => {
|
||||
if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) {
|
||||
this.finalizer(key)
|
||||
this.finalizer(key);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unregister (key) {}
|
||||
unregister(key) {}
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
// FIXME: remove workaround when the Node bug is backported to v18
|
||||
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
|
||||
if (process.env.NODE_V8_COVERAGE && process.version.startsWith('v18')) {
|
||||
process._rawDebug('Using compatibility WeakRef and FinalizationRegistry')
|
||||
process._rawDebug('Using compatibility WeakRef and FinalizationRegistry');
|
||||
return {
|
||||
WeakRef: CompatWeakRef,
|
||||
FinalizationRegistry: CompatFinalizer
|
||||
}
|
||||
FinalizationRegistry: CompatFinalizer,
|
||||
};
|
||||
}
|
||||
return { WeakRef, FinalizationRegistry }
|
||||
}
|
||||
return { WeakRef, FinalizationRegistry };
|
||||
};
|
||||
|
346
node_modules/undici/lib/web/fetch/formdata-parser.js
generated
vendored
346
node_modules/undici/lib/web/fetch/formdata-parser.js
generated
vendored
@ -1,63 +1,68 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
|
||||
const { utf8DecodeBytes } = require('./util')
|
||||
const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
|
||||
const { makeEntry } = require('./formdata')
|
||||
const { webidl } = require('./webidl')
|
||||
const assert = require('node:assert')
|
||||
const { File: NodeFile } = require('node:buffer')
|
||||
const {
|
||||
isUSVString,
|
||||
bufferToLowerCasedHeaderName,
|
||||
} = require('../../core/util');
|
||||
const { utf8DecodeBytes } = require('./util');
|
||||
const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url');
|
||||
const { makeEntry } = require('./formdata');
|
||||
const { webidl } = require('./webidl');
|
||||
const assert = require('node:assert');
|
||||
const { File: NodeFile } = require('node:buffer');
|
||||
|
||||
const File = globalThis.File ?? NodeFile
|
||||
const File = globalThis.File ?? NodeFile;
|
||||
|
||||
const formDataNameBuffer = Buffer.from('form-data; name="')
|
||||
const filenameBuffer = Buffer.from('filename')
|
||||
const dd = Buffer.from('--')
|
||||
const ddcrlf = Buffer.from('--\r\n')
|
||||
const formDataNameBuffer = Buffer.from('form-data; name="');
|
||||
const filenameBuffer = Buffer.from('filename');
|
||||
const dd = Buffer.from('--');
|
||||
const ddcrlf = Buffer.from('--\r\n');
|
||||
|
||||
/**
|
||||
* @param {string} chars
|
||||
*/
|
||||
function isAsciiString (chars) {
|
||||
function isAsciiString(chars) {
|
||||
for (let i = 0; i < chars.length; ++i) {
|
||||
if ((chars.charCodeAt(i) & ~0x7F) !== 0) {
|
||||
return false
|
||||
if ((chars.charCodeAt(i) & ~0x7f) !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-boundary
|
||||
* @param {string} boundary
|
||||
*/
|
||||
function validateBoundary (boundary) {
|
||||
const length = boundary.length
|
||||
function validateBoundary(boundary) {
|
||||
const length = boundary.length;
|
||||
|
||||
// - its length is greater or equal to 27 and lesser or equal to 70, and
|
||||
if (length < 27 || length > 70) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// - it is composed by bytes in the ranges 0x30 to 0x39, 0x41 to 0x5A, or
|
||||
// 0x61 to 0x7A, inclusive (ASCII alphanumeric), or which are 0x27 ('),
|
||||
// 0x2D (-) or 0x5F (_).
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const cp = boundary.charCodeAt(i)
|
||||
const cp = boundary.charCodeAt(i);
|
||||
|
||||
if (!(
|
||||
(cp >= 0x30 && cp <= 0x39) ||
|
||||
(cp >= 0x41 && cp <= 0x5a) ||
|
||||
(cp >= 0x61 && cp <= 0x7a) ||
|
||||
cp === 0x27 ||
|
||||
cp === 0x2d ||
|
||||
cp === 0x5f
|
||||
)) {
|
||||
return false
|
||||
if (
|
||||
!(
|
||||
(cp >= 0x30 && cp <= 0x39) ||
|
||||
(cp >= 0x41 && cp <= 0x5a) ||
|
||||
(cp >= 0x61 && cp <= 0x7a) ||
|
||||
cp === 0x27 ||
|
||||
cp === 0x2d ||
|
||||
cp === 0x5f
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,41 +70,44 @@ function validateBoundary (boundary) {
|
||||
* @param {Buffer} input
|
||||
* @param {ReturnType<import('./data-url')['parseMIMEType']>} mimeType
|
||||
*/
|
||||
function multipartFormDataParser (input, mimeType) {
|
||||
function multipartFormDataParser(input, mimeType) {
|
||||
// 1. Assert: mimeType’s essence is "multipart/form-data".
|
||||
assert(mimeType !== 'failure' && mimeType.essence === 'multipart/form-data')
|
||||
assert(mimeType !== 'failure' && mimeType.essence === 'multipart/form-data');
|
||||
|
||||
const boundaryString = mimeType.parameters.get('boundary')
|
||||
const boundaryString = mimeType.parameters.get('boundary');
|
||||
|
||||
// 2. If mimeType’s parameters["boundary"] does not exist, return failure.
|
||||
// Otherwise, let boundary be the result of UTF-8 decoding mimeType’s
|
||||
// parameters["boundary"].
|
||||
if (boundaryString === undefined) {
|
||||
throw parsingError('missing boundary in content-type header')
|
||||
throw parsingError('missing boundary in content-type header');
|
||||
}
|
||||
|
||||
const boundary = Buffer.from(`--${boundaryString}`, 'utf8')
|
||||
const boundary = Buffer.from(`--${boundaryString}`, 'utf8');
|
||||
|
||||
// 3. Let entry list be an empty entry list.
|
||||
const entryList = []
|
||||
const entryList = [];
|
||||
|
||||
// 4. Let position be a pointer to a byte in input, initially pointing at
|
||||
// the first byte.
|
||||
const position = { position: 0 }
|
||||
const position = { position: 0 };
|
||||
|
||||
// Note: undici addition, allows leading and trailing CRLFs.
|
||||
while (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
||||
position.position += 2
|
||||
while (
|
||||
input[position.position] === 0x0d &&
|
||||
input[position.position + 1] === 0x0a
|
||||
) {
|
||||
position.position += 2;
|
||||
}
|
||||
|
||||
let trailing = input.length
|
||||
let trailing = input.length;
|
||||
|
||||
while (input[trailing - 1] === 0x0a && input[trailing - 2] === 0x0d) {
|
||||
trailing -= 2
|
||||
trailing -= 2;
|
||||
}
|
||||
|
||||
if (trailing !== input.length) {
|
||||
input = input.subarray(0, trailing)
|
||||
input = input.subarray(0, trailing);
|
||||
}
|
||||
|
||||
// 5. While true:
|
||||
@ -108,103 +116,120 @@ function multipartFormDataParser (input, mimeType) {
|
||||
// (`--`) followed by boundary, advance position by 2 + the length of
|
||||
// boundary. Otherwise, return failure.
|
||||
// Note: boundary is padded with 2 dashes already, no need to add 2.
|
||||
if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) {
|
||||
position.position += boundary.length
|
||||
if (
|
||||
input
|
||||
.subarray(position.position, position.position + boundary.length)
|
||||
.equals(boundary)
|
||||
) {
|
||||
position.position += boundary.length;
|
||||
} else {
|
||||
throw parsingError('expected a value starting with -- and the boundary')
|
||||
throw parsingError('expected a value starting with -- and the boundary');
|
||||
}
|
||||
|
||||
// 5.2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A
|
||||
// (`--` followed by CR LF) followed by the end of input, return entry list.
|
||||
// Note: a body does NOT need to end with CRLF. It can end with --.
|
||||
if (
|
||||
(position.position === input.length - 2 && bufferStartsWith(input, dd, position)) ||
|
||||
(position.position === input.length - 4 && bufferStartsWith(input, ddcrlf, position))
|
||||
(position.position === input.length - 2 &&
|
||||
bufferStartsWith(input, dd, position)) ||
|
||||
(position.position === input.length - 4 &&
|
||||
bufferStartsWith(input, ddcrlf, position))
|
||||
) {
|
||||
return entryList
|
||||
return entryList;
|
||||
}
|
||||
|
||||
// 5.3. If position does not point to a sequence of bytes starting with 0x0D
|
||||
// 0x0A (CR LF), return failure.
|
||||
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
||||
throw parsingError('expected CRLF')
|
||||
if (
|
||||
input[position.position] !== 0x0d ||
|
||||
input[position.position + 1] !== 0x0a
|
||||
) {
|
||||
throw parsingError('expected CRLF');
|
||||
}
|
||||
|
||||
// 5.4. Advance position by 2. (This skips past the newline.)
|
||||
position.position += 2
|
||||
position.position += 2;
|
||||
|
||||
// 5.5. Let name, filename and contentType be the result of parsing
|
||||
// multipart/form-data headers on input and position, if the result
|
||||
// is not failure. Otherwise, return failure.
|
||||
const result = parseMultipartFormDataHeaders(input, position)
|
||||
const result = parseMultipartFormDataHeaders(input, position);
|
||||
|
||||
let { name, filename, contentType, encoding } = result
|
||||
let { name, filename, contentType, encoding } = result;
|
||||
|
||||
// 5.6. Advance position by 2. (This skips past the empty line that marks
|
||||
// the end of the headers.)
|
||||
position.position += 2
|
||||
position.position += 2;
|
||||
|
||||
// 5.7. Let body be the empty byte sequence.
|
||||
let body
|
||||
let body;
|
||||
|
||||
// 5.8. Body loop: While position is not past the end of input:
|
||||
// TODO: the steps here are completely wrong
|
||||
{
|
||||
const boundaryIndex = input.indexOf(boundary.subarray(2), position.position)
|
||||
const boundaryIndex = input.indexOf(
|
||||
boundary.subarray(2),
|
||||
position.position
|
||||
);
|
||||
|
||||
if (boundaryIndex === -1) {
|
||||
throw parsingError('expected boundary after body')
|
||||
throw parsingError('expected boundary after body');
|
||||
}
|
||||
|
||||
body = input.subarray(position.position, boundaryIndex - 4)
|
||||
body = input.subarray(position.position, boundaryIndex - 4);
|
||||
|
||||
position.position += body.length
|
||||
position.position += body.length;
|
||||
|
||||
// Note: position must be advanced by the body's length before being
|
||||
// decoded, otherwise the parsing will fail.
|
||||
if (encoding === 'base64') {
|
||||
body = Buffer.from(body.toString(), 'base64')
|
||||
body = Buffer.from(body.toString(), 'base64');
|
||||
}
|
||||
}
|
||||
|
||||
// 5.9. If position does not point to a sequence of bytes starting with
|
||||
// 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2.
|
||||
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
||||
throw parsingError('expected CRLF')
|
||||
if (
|
||||
input[position.position] !== 0x0d ||
|
||||
input[position.position + 1] !== 0x0a
|
||||
) {
|
||||
throw parsingError('expected CRLF');
|
||||
} else {
|
||||
position.position += 2
|
||||
position.position += 2;
|
||||
}
|
||||
|
||||
// 5.10. If filename is not null:
|
||||
let value
|
||||
let value;
|
||||
|
||||
if (filename !== null) {
|
||||
// 5.10.1. If contentType is null, set contentType to "text/plain".
|
||||
contentType ??= 'text/plain'
|
||||
contentType ??= 'text/plain';
|
||||
|
||||
// 5.10.2. If contentType is not an ASCII string, set contentType to the empty string.
|
||||
|
||||
// Note: `buffer.isAscii` can be used at zero-cost, but converting a string to a buffer is a high overhead.
|
||||
// Content-Type is a relatively small string, so it is faster to use `String#charCodeAt`.
|
||||
if (!isAsciiString(contentType)) {
|
||||
contentType = ''
|
||||
contentType = '';
|
||||
}
|
||||
|
||||
// 5.10.3. Let value be a new File object with name filename, type contentType, and body body.
|
||||
value = new File([body], filename, { type: contentType })
|
||||
value = new File([body], filename, { type: contentType });
|
||||
} else {
|
||||
// 5.11. Otherwise:
|
||||
|
||||
// 5.11.1. Let value be the UTF-8 decoding without BOM of body.
|
||||
value = utf8DecodeBytes(Buffer.from(body))
|
||||
value = utf8DecodeBytes(Buffer.from(body));
|
||||
}
|
||||
|
||||
// 5.12. Assert: name is a scalar value string and value is either a scalar value string or a File object.
|
||||
assert(isUSVString(name))
|
||||
assert((typeof value === 'string' && isUSVString(value)) || webidl.is.File(value))
|
||||
assert(isUSVString(name));
|
||||
assert(
|
||||
(typeof value === 'string' && isUSVString(value)) || webidl.is.File(value)
|
||||
);
|
||||
|
||||
// 5.13. Create an entry with name and value, and append it to entry list.
|
||||
entryList.push(makeEntry(name, value, filename))
|
||||
entryList.push(makeEntry(name, value, filename));
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,24 +238,27 @@ function multipartFormDataParser (input, mimeType) {
|
||||
* @param {Buffer} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function parseMultipartFormDataHeaders (input, position) {
|
||||
function parseMultipartFormDataHeaders(input, position) {
|
||||
// 1. Let name, filename and contentType be null.
|
||||
let name = null
|
||||
let filename = null
|
||||
let contentType = null
|
||||
let encoding = null
|
||||
let name = null;
|
||||
let filename = null;
|
||||
let contentType = null;
|
||||
let encoding = null;
|
||||
|
||||
// 2. While true:
|
||||
while (true) {
|
||||
// 2.1. If position points to a sequence of bytes starting with 0x0D 0x0A (CR LF):
|
||||
if (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
||||
if (
|
||||
input[position.position] === 0x0d &&
|
||||
input[position.position + 1] === 0x0a
|
||||
) {
|
||||
// 2.1.1. If name is null, return failure.
|
||||
if (name === null) {
|
||||
throw parsingError('header name is null')
|
||||
throw parsingError('header name is null');
|
||||
}
|
||||
|
||||
// 2.1.2. Return name, filename and contentType.
|
||||
return { name, filename, contentType, encoding }
|
||||
return { name, filename, contentType, encoding };
|
||||
}
|
||||
|
||||
// 2.2. Let header name be the result of collecting a sequence of bytes that are
|
||||
@ -239,23 +267,30 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char !== 0x0a && char !== 0x0d && char !== 0x3a,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 2.3. Remove any HTTP tab or space bytes from the start or end of header name.
|
||||
headerName = removeChars(headerName, true, true, (char) => char === 0x9 || char === 0x20)
|
||||
headerName = removeChars(
|
||||
headerName,
|
||||
true,
|
||||
true,
|
||||
(char) => char === 0x9 || char === 0x20
|
||||
);
|
||||
|
||||
// 2.4. If header name does not match the field-name token production, return failure.
|
||||
if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) {
|
||||
throw parsingError('header name does not match the field-name token production')
|
||||
throw parsingError(
|
||||
'header name does not match the field-name token production'
|
||||
);
|
||||
}
|
||||
|
||||
// 2.5. If the byte at position is not 0x3A (:), return failure.
|
||||
if (input[position.position] !== 0x3a) {
|
||||
throw parsingError('expected :')
|
||||
throw parsingError('expected :');
|
||||
}
|
||||
|
||||
// 2.6. Advance position by 1.
|
||||
position.position++
|
||||
position.position++;
|
||||
|
||||
// 2.7. Collect a sequence of bytes that are HTTP tab or space bytes given position.
|
||||
// (Do nothing with those bytes.)
|
||||
@ -263,36 +298,41 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char === 0x20 || char === 0x09,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 2.8. Byte-lowercase header name and switch on the result:
|
||||
switch (bufferToLowerCasedHeaderName(headerName)) {
|
||||
case 'content-disposition': {
|
||||
// 1. Set name and filename to null.
|
||||
name = filename = null
|
||||
name = filename = null;
|
||||
|
||||
// 2. If position does not point to a sequence of bytes starting with
|
||||
// `form-data; name="`, return failure.
|
||||
if (!bufferStartsWith(input, formDataNameBuffer, position)) {
|
||||
throw parsingError('expected form-data; name=" for content-disposition header')
|
||||
throw parsingError(
|
||||
'expected form-data; name=" for content-disposition header'
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Advance position so it points at the byte after the next 0x22 (")
|
||||
// byte (the one in the sequence of bytes matched above).
|
||||
position.position += 17
|
||||
position.position += 17;
|
||||
|
||||
// 4. Set name to the result of parsing a multipart/form-data name given
|
||||
// input and position, if the result is not failure. Otherwise, return
|
||||
// failure.
|
||||
name = parseMultipartFormDataName(input, position)
|
||||
name = parseMultipartFormDataName(input, position);
|
||||
|
||||
// 5. If position points to a sequence of bytes starting with `; filename="`:
|
||||
if (input[position.position] === 0x3b /* ; */ && input[position.position + 1] === 0x20 /* ' ' */) {
|
||||
const at = { position: position.position + 2 }
|
||||
if (
|
||||
input[position.position] === 0x3b /* ; */ &&
|
||||
input[position.position + 1] === 0x20 /* ' ' */
|
||||
) {
|
||||
const at = { position: position.position + 2 };
|
||||
|
||||
if (bufferStartsWith(input, filenameBuffer, at)) {
|
||||
if (input[at.position + 8] === 0x2a /* '*' */) {
|
||||
at.position += 10 // skip past filename*=
|
||||
at.position += 10; // skip past filename*=
|
||||
|
||||
// Remove leading http tab and spaces. See RFC for examples.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
||||
@ -300,13 +340,13 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char === 0x20 || char === 0x09,
|
||||
input,
|
||||
at
|
||||
)
|
||||
);
|
||||
|
||||
const headerValue = collectASequenceOfBytes(
|
||||
(char) => char !== 0x20 && char !== 0x0d && char !== 0x0a, // ' ' or CRLF
|
||||
input,
|
||||
at
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
(headerValue[0] !== 0x75 && headerValue[0] !== 0x55) || // u or U
|
||||
@ -315,17 +355,19 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
headerValue[3] !== 0x2d || // -
|
||||
headerValue[4] !== 0x38 // 8
|
||||
) {
|
||||
throw parsingError('unknown encoding, expected utf-8\'\'')
|
||||
throw parsingError("unknown encoding, expected utf-8''");
|
||||
}
|
||||
|
||||
// skip utf-8''
|
||||
filename = decodeURIComponent(new TextDecoder().decode(headerValue.subarray(7)))
|
||||
filename = decodeURIComponent(
|
||||
new TextDecoder().decode(headerValue.subarray(7))
|
||||
);
|
||||
|
||||
position.position = at.position
|
||||
position.position = at.position;
|
||||
} else {
|
||||
// 1. Advance position so it points at the byte after the next 0x22 (") byte
|
||||
// (the one in the sequence of bytes matched above).
|
||||
position.position += 11
|
||||
position.position += 11;
|
||||
|
||||
// Remove leading http tab and spaces. See RFC for examples.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
||||
@ -333,18 +375,18 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char === 0x20 || char === 0x09,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
position.position++ // skip past " after removing whitespace
|
||||
position.position++; // skip past " after removing whitespace
|
||||
|
||||
// 2. Set filename to the result of parsing a multipart/form-data name given
|
||||
// input and position, if the result is not failure. Otherwise, return failure.
|
||||
filename = parseMultipartFormDataName(input, position)
|
||||
filename = parseMultipartFormDataName(input, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
break;
|
||||
}
|
||||
case 'content-type': {
|
||||
// 1. Let header value be the result of collecting a sequence of bytes that are
|
||||
@ -353,28 +395,38 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char !== 0x0a && char !== 0x0d,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 2. Remove any HTTP tab or space bytes from the end of header value.
|
||||
headerValue = removeChars(headerValue, false, true, (char) => char === 0x9 || char === 0x20)
|
||||
headerValue = removeChars(
|
||||
headerValue,
|
||||
false,
|
||||
true,
|
||||
(char) => char === 0x9 || char === 0x20
|
||||
);
|
||||
|
||||
// 3. Set contentType to the isomorphic decoding of header value.
|
||||
contentType = isomorphicDecode(headerValue)
|
||||
contentType = isomorphicDecode(headerValue);
|
||||
|
||||
break
|
||||
break;
|
||||
}
|
||||
case 'content-transfer-encoding': {
|
||||
let headerValue = collectASequenceOfBytes(
|
||||
(char) => char !== 0x0a && char !== 0x0d,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
headerValue = removeChars(headerValue, false, true, (char) => char === 0x9 || char === 0x20)
|
||||
headerValue = removeChars(
|
||||
headerValue,
|
||||
false,
|
||||
true,
|
||||
(char) => char === 0x9 || char === 0x20
|
||||
);
|
||||
|
||||
encoding = isomorphicDecode(headerValue)
|
||||
encoding = isomorphicDecode(headerValue);
|
||||
|
||||
break
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Collect a sequence of bytes that are not 0x0A (LF) or 0x0D (CR), given position.
|
||||
@ -383,16 +435,19 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
(char) => char !== 0x0a && char !== 0x0d,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2.9. If position does not point to a sequence of bytes starting with 0x0D 0x0A
|
||||
// (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
|
||||
if (input[position.position] !== 0x0d && input[position.position + 1] !== 0x0a) {
|
||||
throw parsingError('expected CRLF')
|
||||
if (
|
||||
input[position.position] !== 0x0d &&
|
||||
input[position.position + 1] !== 0x0a
|
||||
) {
|
||||
throw parsingError('expected CRLF');
|
||||
} else {
|
||||
position.position += 2
|
||||
position.position += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,9 +457,9 @@ function parseMultipartFormDataHeaders (input, position) {
|
||||
* @param {Buffer} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function parseMultipartFormDataName (input, position) {
|
||||
function parseMultipartFormDataName(input, position) {
|
||||
// 1. Assert: The byte at (position - 1) is 0x22 (").
|
||||
assert(input[position.position - 1] === 0x22)
|
||||
assert(input[position.position - 1] === 0x22);
|
||||
|
||||
// 2. Let name be the result of collecting a sequence of bytes that are not 0x0A (LF), 0x0D (CR) or 0x22 ("), given position.
|
||||
/** @type {string | Buffer} */
|
||||
@ -412,26 +467,27 @@ function parseMultipartFormDataName (input, position) {
|
||||
(char) => char !== 0x0a && char !== 0x0d && char !== 0x22,
|
||||
input,
|
||||
position
|
||||
)
|
||||
);
|
||||
|
||||
// 3. If the byte at position is not 0x22 ("), return failure. Otherwise, advance position by 1.
|
||||
if (input[position.position] !== 0x22) {
|
||||
throw parsingError('expected "')
|
||||
throw parsingError('expected "');
|
||||
} else {
|
||||
position.position++
|
||||
position.position++;
|
||||
}
|
||||
|
||||
// 4. Replace any occurrence of the following subsequences in name with the given byte:
|
||||
// - `%0A`: 0x0A (LF)
|
||||
// - `%0D`: 0x0D (CR)
|
||||
// - `%22`: 0x22 (")
|
||||
name = new TextDecoder().decode(name)
|
||||
.replace(/%0A/ig, '\n')
|
||||
.replace(/%0D/ig, '\r')
|
||||
.replace(/%22/g, '"')
|
||||
name = new TextDecoder()
|
||||
.decode(name)
|
||||
.replace(/%0A/gi, '\n')
|
||||
.replace(/%0D/gi, '\r')
|
||||
.replace(/%22/g, '"');
|
||||
|
||||
// 5. Return the UTF-8 decoding without BOM of name.
|
||||
return name
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,14 +495,14 @@ function parseMultipartFormDataName (input, position) {
|
||||
* @param {Buffer} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfBytes (condition, input, position) {
|
||||
let start = position.position
|
||||
function collectASequenceOfBytes(condition, input, position) {
|
||||
let start = position.position;
|
||||
|
||||
while (start < input.length && condition(input[start])) {
|
||||
++start
|
||||
++start;
|
||||
}
|
||||
|
||||
return input.subarray(position.position, (position.position = start))
|
||||
return input.subarray(position.position, (position.position = start));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,19 +512,21 @@ function collectASequenceOfBytes (condition, input, position) {
|
||||
* @param {(charCode: number) => boolean} predicate
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
function removeChars (buf, leading, trailing, predicate) {
|
||||
let lead = 0
|
||||
let trail = buf.length - 1
|
||||
function removeChars(buf, leading, trailing, predicate) {
|
||||
let lead = 0;
|
||||
let trail = buf.length - 1;
|
||||
|
||||
if (leading) {
|
||||
while (lead < buf.length && predicate(buf[lead])) lead++
|
||||
while (lead < buf.length && predicate(buf[lead])) lead++;
|
||||
}
|
||||
|
||||
if (trailing) {
|
||||
while (trail > 0 && predicate(buf[trail])) trail--
|
||||
while (trail > 0 && predicate(buf[trail])) trail--;
|
||||
}
|
||||
|
||||
return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1)
|
||||
return lead === 0 && trail === buf.length - 1 ?
|
||||
buf
|
||||
: buf.subarray(lead, trail + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -477,25 +535,27 @@ function removeChars (buf, leading, trailing, predicate) {
|
||||
* @param {Buffer} start
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function bufferStartsWith (buffer, start, position) {
|
||||
function bufferStartsWith(buffer, start, position) {
|
||||
if (buffer.length < start.length) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < start.length; i++) {
|
||||
if (start[i] !== buffer[position.position + i]) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
function parsingError (cause) {
|
||||
return new TypeError('Failed to parse body as FormData.', { cause: new TypeError(cause) })
|
||||
function parsingError(cause) {
|
||||
return new TypeError('Failed to parse body as FormData.', {
|
||||
cause: new TypeError(cause),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
multipartFormDataParser,
|
||||
validateBoundary
|
||||
}
|
||||
validateBoundary,
|
||||
};
|
||||
|
193
node_modules/undici/lib/web/fetch/formdata.js
generated
vendored
193
node_modules/undici/lib/web/fetch/formdata.js
generated
vendored
@ -1,98 +1,98 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { iteratorMixin } = require('./util')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { webidl } = require('./webidl')
|
||||
const { File: NativeFile } = require('node:buffer')
|
||||
const nodeUtil = require('node:util')
|
||||
const { iteratorMixin } = require('./util');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const { webidl } = require('./webidl');
|
||||
const { File: NativeFile } = require('node:buffer');
|
||||
const nodeUtil = require('node:util');
|
||||
|
||||
/** @type {globalThis['File']} */
|
||||
const File = globalThis.File ?? NativeFile
|
||||
const File = globalThis.File ?? NativeFile;
|
||||
|
||||
// https://xhr.spec.whatwg.org/#formdata
|
||||
class FormData {
|
||||
#state = []
|
||||
#state = [];
|
||||
|
||||
constructor (form) {
|
||||
webidl.util.markAsUncloneable(this)
|
||||
constructor(form) {
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
if (form !== undefined) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'FormData constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['undefined']
|
||||
})
|
||||
types: ['undefined'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
append (name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
append(name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.append'
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix)
|
||||
const prefix = 'FormData.append';
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
name = webidl.converters.USVString(name);
|
||||
|
||||
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
||||
value = webidl.converters.Blob(value, prefix, 'value')
|
||||
value = webidl.converters.Blob(value, prefix, 'value');
|
||||
|
||||
if (filename !== undefined) {
|
||||
filename = webidl.converters.USVString(filename)
|
||||
filename = webidl.converters.USVString(filename);
|
||||
}
|
||||
} else {
|
||||
value = webidl.converters.USVString(value)
|
||||
value = webidl.converters.USVString(value);
|
||||
}
|
||||
|
||||
// 1. Let value be value if given; otherwise blobValue.
|
||||
|
||||
// 2. Let entry be the result of creating an entry with
|
||||
// name, value, and filename if given.
|
||||
const entry = makeEntry(name, value, filename)
|
||||
const entry = makeEntry(name, value, filename);
|
||||
|
||||
// 3. Append entry to this’s entry list.
|
||||
this.#state.push(entry)
|
||||
this.#state.push(entry);
|
||||
}
|
||||
|
||||
delete (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
delete(name) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.delete'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'FormData.delete';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
name = webidl.converters.USVString(name);
|
||||
|
||||
// The delete(name) method steps are to remove all entries whose name
|
||||
// is name from this’s entry list.
|
||||
this.#state = this.#state.filter(entry => entry.name !== name)
|
||||
this.#state = this.#state.filter((entry) => entry.name !== name);
|
||||
}
|
||||
|
||||
get (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
get(name) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.get'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'FormData.get';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
name = webidl.converters.USVString(name);
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return null.
|
||||
const idx = this.#state.findIndex((entry) => entry.name === name)
|
||||
const idx = this.#state.findIndex((entry) => entry.name === name);
|
||||
if (idx === -1) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Return the value of the first entry whose name is name from
|
||||
// this’s entry list.
|
||||
return this.#state[idx].value
|
||||
return this.#state[idx].value;
|
||||
}
|
||||
|
||||
getAll (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
getAll(name) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.getAll'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'FormData.getAll';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
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.
|
||||
@ -100,38 +100,38 @@ class FormData {
|
||||
// from this’s entry list.
|
||||
return this.#state
|
||||
.filter((entry) => entry.name === name)
|
||||
.map((entry) => entry.value)
|
||||
.map((entry) => entry.value);
|
||||
}
|
||||
|
||||
has (name) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
has(name) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.has'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'FormData.has';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
name = webidl.converters.USVString(name);
|
||||
|
||||
// The has(name) method steps are to return true if there is an entry
|
||||
// whose name is name in this’s entry list; otherwise false.
|
||||
return this.#state.findIndex((entry) => entry.name === name) !== -1
|
||||
return this.#state.findIndex((entry) => entry.name === name) !== -1;
|
||||
}
|
||||
|
||||
set (name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData)
|
||||
set(name, value, filename = undefined) {
|
||||
webidl.brandCheck(this, FormData);
|
||||
|
||||
const prefix = 'FormData.set'
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix)
|
||||
const prefix = 'FormData.set';
|
||||
webidl.argumentLengthCheck(arguments, 2, prefix);
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
name = webidl.converters.USVString(name);
|
||||
|
||||
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
||||
value = webidl.converters.Blob(value, prefix, 'value')
|
||||
value = webidl.converters.Blob(value, prefix, 'value');
|
||||
|
||||
if (filename !== undefined) {
|
||||
filename = webidl.converters.USVString(filename)
|
||||
filename = webidl.converters.USVString(filename);
|
||||
}
|
||||
} else {
|
||||
value = webidl.converters.USVString(value)
|
||||
value = webidl.converters.USVString(value);
|
||||
}
|
||||
|
||||
// The set(name, value) and set(name, blobValue, filename) method steps
|
||||
@ -141,68 +141,71 @@ class FormData {
|
||||
|
||||
// 2. Let entry be the result of creating an entry with name, value, and
|
||||
// filename if given.
|
||||
const entry = makeEntry(name, value, filename)
|
||||
const entry = makeEntry(name, value, filename);
|
||||
|
||||
// 3. If there are entries in this’s entry list whose name is name, then
|
||||
// replace the first such entry with entry and remove the others.
|
||||
const idx = this.#state.findIndex((entry) => entry.name === name)
|
||||
const idx = this.#state.findIndex((entry) => entry.name === name);
|
||||
if (idx !== -1) {
|
||||
this.#state = [
|
||||
...this.#state.slice(0, idx),
|
||||
entry,
|
||||
...this.#state.slice(idx + 1).filter((entry) => entry.name !== name)
|
||||
]
|
||||
...this.#state.slice(idx + 1).filter((entry) => entry.name !== name),
|
||||
];
|
||||
} else {
|
||||
// 4. Otherwise, append entry to this’s entry list.
|
||||
this.#state.push(entry)
|
||||
this.#state.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
[nodeUtil.inspect.custom] (depth, options) {
|
||||
const state = this.#state.reduce((a, b) => {
|
||||
if (a[b.name]) {
|
||||
if (Array.isArray(a[b.name])) {
|
||||
a[b.name].push(b.value)
|
||||
[nodeUtil.inspect.custom](depth, options) {
|
||||
const state = this.#state.reduce(
|
||||
(a, b) => {
|
||||
if (a[b.name]) {
|
||||
if (Array.isArray(a[b.name])) {
|
||||
a[b.name].push(b.value);
|
||||
} else {
|
||||
a[b.name] = [a[b.name], b.value];
|
||||
}
|
||||
} else {
|
||||
a[b.name] = [a[b.name], b.value]
|
||||
a[b.name] = b.value;
|
||||
}
|
||||
} else {
|
||||
a[b.name] = b.value
|
||||
}
|
||||
|
||||
return a
|
||||
}, { __proto__: null })
|
||||
return a;
|
||||
},
|
||||
{ __proto__: null }
|
||||
);
|
||||
|
||||
options.depth ??= depth
|
||||
options.colors ??= true
|
||||
options.depth ??= depth;
|
||||
options.colors ??= true;
|
||||
|
||||
const output = nodeUtil.formatWithOptions(options, state)
|
||||
const output = nodeUtil.formatWithOptions(options, state);
|
||||
|
||||
// remove [Object null prototype]
|
||||
return `FormData ${output.slice(output.indexOf(']') + 2)}`
|
||||
return `FormData ${output.slice(output.indexOf(']') + 2)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FormData} formData
|
||||
*/
|
||||
static getFormDataState (formData) {
|
||||
return formData.#state
|
||||
static getFormDataState(formData) {
|
||||
return formData.#state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FormData} formData
|
||||
* @param {any[]} newState
|
||||
*/
|
||||
static setFormDataState (formData, newState) {
|
||||
formData.#state = newState
|
||||
static setFormDataState(formData, newState) {
|
||||
formData.#state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
const { getFormDataState, setFormDataState } = FormData
|
||||
Reflect.deleteProperty(FormData, 'getFormDataState')
|
||||
Reflect.deleteProperty(FormData, 'setFormDataState')
|
||||
const { getFormDataState, setFormDataState } = FormData;
|
||||
Reflect.deleteProperty(FormData, 'getFormDataState');
|
||||
Reflect.deleteProperty(FormData, 'setFormDataState');
|
||||
|
||||
iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value')
|
||||
iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value');
|
||||
|
||||
Object.defineProperties(FormData.prototype, {
|
||||
append: kEnumerableProperty,
|
||||
@ -213,9 +216,9 @@ Object.defineProperties(FormData.prototype, {
|
||||
set: kEnumerableProperty,
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'FormData',
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
|
||||
@ -224,7 +227,7 @@ Object.defineProperties(FormData.prototype, {
|
||||
* @param {?string} filename
|
||||
* @returns
|
||||
*/
|
||||
function makeEntry (name, value, filename) {
|
||||
function makeEntry(name, value, filename) {
|
||||
// 1. Set name to the result of converting name into a scalar value string.
|
||||
// Note: This operation was done by the webidl converter USVString.
|
||||
|
||||
@ -238,7 +241,7 @@ function makeEntry (name, value, filename) {
|
||||
// 1. If value is not a File object, then set value to a new File object,
|
||||
// representing the same bytes, whose name attribute value is "blob"
|
||||
if (!webidl.is.File(value)) {
|
||||
value = new File([value], 'blob', { type: value.type })
|
||||
value = new File([value], 'blob', { type: value.type });
|
||||
}
|
||||
|
||||
// 2. If filename is given, then set value to a new File object,
|
||||
@ -247,17 +250,17 @@ function makeEntry (name, value, filename) {
|
||||
/** @type {FilePropertyBag} */
|
||||
const options = {
|
||||
type: value.type,
|
||||
lastModified: value.lastModified
|
||||
}
|
||||
lastModified: value.lastModified,
|
||||
};
|
||||
|
||||
value = new File([value], filename, options)
|
||||
value = new File([value], filename, options);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return an entry whose name is name and whose value is value.
|
||||
return { name, value }
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
|
||||
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData);
|
||||
|
||||
module.exports = { FormData, makeEntry, setFormDataState }
|
||||
module.exports = { FormData, makeEntry, setFormDataState };
|
||||
|
30
node_modules/undici/lib/web/fetch/global.js
generated
vendored
30
node_modules/undici/lib/web/fetch/global.js
generated
vendored
@ -1,40 +1,42 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// In case of breaking changes, increase the version
|
||||
// number to avoid conflicts.
|
||||
const globalOrigin = Symbol.for('undici.globalOrigin.1')
|
||||
const globalOrigin = Symbol.for('undici.globalOrigin.1');
|
||||
|
||||
function getGlobalOrigin () {
|
||||
return globalThis[globalOrigin]
|
||||
function getGlobalOrigin() {
|
||||
return globalThis[globalOrigin];
|
||||
}
|
||||
|
||||
function setGlobalOrigin (newOrigin) {
|
||||
function setGlobalOrigin(newOrigin) {
|
||||
if (newOrigin === undefined) {
|
||||
Object.defineProperty(globalThis, globalOrigin, {
|
||||
value: undefined,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
})
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedURL = new URL(newOrigin)
|
||||
const parsedURL = new URL(newOrigin);
|
||||
|
||||
if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
|
||||
throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`)
|
||||
throw new TypeError(
|
||||
`Only http & https urls are allowed, received ${parsedURL.protocol}`
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, globalOrigin, {
|
||||
value: parsedURL,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
})
|
||||
configurable: false,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGlobalOrigin,
|
||||
setGlobalOrigin
|
||||
}
|
||||
setGlobalOrigin,
|
||||
};
|
||||
|
458
node_modules/undici/lib/web/fetch/headers.js
generated
vendored
458
node_modules/undici/lib/web/fetch/headers.js
generated
vendored
@ -1,24 +1,24 @@
|
||||
// https://github.com/Ethan-Arrowood/undici-fetch
|
||||
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { kConstruct } = require('../../core/symbols')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { kConstruct } = require('../../core/symbols');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const {
|
||||
iteratorMixin,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue
|
||||
} = require('./util')
|
||||
const { webidl } = require('./webidl')
|
||||
const assert = require('node:assert')
|
||||
const util = require('node:util')
|
||||
isValidHeaderValue,
|
||||
} = require('./util');
|
||||
const { webidl } = require('./webidl');
|
||||
const assert = require('node:assert');
|
||||
const util = require('node:util');
|
||||
|
||||
/**
|
||||
* @param {number} code
|
||||
* @returns {code is (0x0a | 0x0d | 0x09 | 0x20)}
|
||||
*/
|
||||
function isHTTPWhiteSpaceCharCode (code) {
|
||||
return code === 0x0a || code === 0x0d || code === 0x09 || code === 0x20
|
||||
function isHTTPWhiteSpaceCharCode(code) {
|
||||
return code === 0x0a || code === 0x0d || code === 0x09 || code === 0x20;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,56 +26,63 @@ function isHTTPWhiteSpaceCharCode (code) {
|
||||
* @param {string} potentialValue
|
||||
* @returns {string}
|
||||
*/
|
||||
function headerValueNormalize (potentialValue) {
|
||||
function headerValueNormalize(potentialValue) {
|
||||
// To normalize a byte sequence potentialValue, remove
|
||||
// any leading and trailing HTTP whitespace bytes from
|
||||
// potentialValue.
|
||||
let i = 0; let j = potentialValue.length
|
||||
let i = 0;
|
||||
let j = potentialValue.length;
|
||||
|
||||
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j
|
||||
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i
|
||||
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1)))
|
||||
--j;
|
||||
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i;
|
||||
|
||||
return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j)
|
||||
return i === 0 && j === potentialValue.length ?
|
||||
potentialValue
|
||||
: potentialValue.substring(i, j);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {Array|Object} object
|
||||
*/
|
||||
function fill (headers, object) {
|
||||
function fill(headers, object) {
|
||||
// To fill a Headers object headers with a given object object, run these steps:
|
||||
|
||||
// 1. If object is a sequence, then for each header in object:
|
||||
// Note: webidl conversion to array has already been done.
|
||||
if (Array.isArray(object)) {
|
||||
for (let i = 0; i < object.length; ++i) {
|
||||
const header = object[i]
|
||||
const header = object[i];
|
||||
// 1. If header does not contain exactly two items, then throw a TypeError.
|
||||
if (header.length !== 2) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Headers constructor',
|
||||
message: `expected name/value pair to be length 2, found ${header.length}.`
|
||||
})
|
||||
message: `expected name/value pair to be length 2, found ${header.length}.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Append (header’s first item, header’s second item) to headers.
|
||||
appendHeader(headers, header[0], header[1])
|
||||
appendHeader(headers, header[0], header[1]);
|
||||
}
|
||||
} else if (typeof object === 'object' && object !== null) {
|
||||
// Note: null should throw
|
||||
|
||||
// 2. Otherwise, object is a record, then for each key → value in object,
|
||||
// append (key, value) to headers
|
||||
const keys = Object.keys(object)
|
||||
const keys = Object.keys(object);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
appendHeader(headers, keys[i], object[keys[i]])
|
||||
appendHeader(headers, keys[i], object[keys[i]]);
|
||||
}
|
||||
} else {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
types: [
|
||||
'sequence<sequence<ByteString>>',
|
||||
'record<ByteString, ByteString>',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,9 +92,9 @@ function fill (headers, object) {
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
*/
|
||||
function appendHeader (headers, name, value) {
|
||||
function appendHeader(headers, name, value) {
|
||||
// 1. Normalize value.
|
||||
value = headerValueNormalize(value)
|
||||
value = headerValueNormalize(value);
|
||||
|
||||
// 2. If name is not a header name or value is not a
|
||||
// header value, then throw a TypeError.
|
||||
@ -95,14 +102,14 @@ function appendHeader (headers, name, value) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.append',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
type: 'header name',
|
||||
});
|
||||
} else if (!isValidHeaderValue(value)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.append',
|
||||
value,
|
||||
type: 'header value'
|
||||
})
|
||||
type: 'header value',
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If headers’s guard is "immutable", then throw a TypeError.
|
||||
@ -112,14 +119,14 @@ function appendHeader (headers, name, value) {
|
||||
// TODO
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (getHeadersGuard(headers) === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
throw new TypeError('immutable');
|
||||
}
|
||||
|
||||
// 6. Otherwise, if headers’s guard is "response" and name is a
|
||||
// forbidden response-header name, return.
|
||||
|
||||
// 7. Append (name, value) to headers’s header list.
|
||||
return getHeadersList(headers).append(name, value, false)
|
||||
return getHeadersList(headers).append(name, value, false);
|
||||
|
||||
// 8. If headers’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from headers
|
||||
@ -129,36 +136,36 @@ function appendHeader (headers, name, value) {
|
||||
/**
|
||||
* @param {Headers} target
|
||||
*/
|
||||
function headersListSortAndCombine (target) {
|
||||
const headersList = getHeadersList(target)
|
||||
function headersListSortAndCombine(target) {
|
||||
const headersList = getHeadersList(target);
|
||||
|
||||
if (!headersList) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
if (headersList.sortedMap) {
|
||||
return headersList.sortedMap
|
||||
return headersList.sortedMap;
|
||||
}
|
||||
|
||||
// 1. Let headers be an empty list of headers with the key being the name
|
||||
// and value the value.
|
||||
const headers = []
|
||||
const headers = [];
|
||||
|
||||
// 2. Let names be the result of convert header names to a sorted-lowercase
|
||||
// set with all the names of the headers in list.
|
||||
const names = headersList.toSortedArray()
|
||||
const names = headersList.toSortedArray();
|
||||
|
||||
const cookies = headersList.cookies
|
||||
const cookies = headersList.cookies;
|
||||
|
||||
// fast-path
|
||||
if (cookies === null || cookies.length === 1) {
|
||||
// Note: The non-null assertion of value has already been done by `HeadersList#toSortedArray`
|
||||
return (headersList.sortedMap = names)
|
||||
return (headersList.sortedMap = names);
|
||||
}
|
||||
|
||||
// 3. For each name of names:
|
||||
for (let i = 0; i < names.length; ++i) {
|
||||
const { 0: name, 1: value } = names[i]
|
||||
const { 0: name, 1: value } = names[i];
|
||||
// 1. If name is `set-cookie`, then:
|
||||
if (name === 'set-cookie') {
|
||||
// 1. Let values be a list of all values of headers in list whose name
|
||||
@ -167,7 +174,7 @@ function headersListSortAndCombine (target) {
|
||||
// 2. For each value of values:
|
||||
// 1. Append (name, value) to headers.
|
||||
for (let j = 0; j < cookies.length; ++j) {
|
||||
headers.push([name, cookies[j]])
|
||||
headers.push([name, cookies[j]]);
|
||||
}
|
||||
} else {
|
||||
// 2. Otherwise:
|
||||
@ -178,33 +185,33 @@ function headersListSortAndCombine (target) {
|
||||
// Note: This operation was done by `HeadersList#toSortedArray`.
|
||||
|
||||
// 3. Append (name, value) to headers.
|
||||
headers.push([name, value])
|
||||
headers.push([name, value]);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return headers.
|
||||
return (headersList.sortedMap = headers)
|
||||
return (headersList.sortedMap = headers);
|
||||
}
|
||||
|
||||
function compareHeaderName (a, b) {
|
||||
return a[0] < b[0] ? -1 : 1
|
||||
function compareHeaderName(a, b) {
|
||||
return a[0] < b[0] ? -1 : 1;
|
||||
}
|
||||
|
||||
class HeadersList {
|
||||
/** @type {[string, string][]|null} */
|
||||
cookies = null
|
||||
cookies = null;
|
||||
|
||||
sortedMap
|
||||
headersMap
|
||||
sortedMap;
|
||||
headersMap;
|
||||
|
||||
constructor (init) {
|
||||
constructor(init) {
|
||||
if (init instanceof HeadersList) {
|
||||
this.headersMap = new Map(init.headersMap)
|
||||
this.sortedMap = init.sortedMap
|
||||
this.cookies = init.cookies === null ? null : [...init.cookies]
|
||||
this.headersMap = new Map(init.headersMap);
|
||||
this.sortedMap = init.sortedMap;
|
||||
this.cookies = init.cookies === null ? null : [...init.cookies];
|
||||
} else {
|
||||
this.headersMap = new Map(init)
|
||||
this.sortedMap = null
|
||||
this.headersMap = new Map(init);
|
||||
this.sortedMap = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,18 +220,18 @@ class HeadersList {
|
||||
* @param {string} name
|
||||
* @param {boolean} isLowerCase
|
||||
*/
|
||||
contains (name, isLowerCase) {
|
||||
contains(name, isLowerCase) {
|
||||
// A header list list contains a header name name if list
|
||||
// contains a header whose name is a byte-case-insensitive
|
||||
// match for name.
|
||||
|
||||
return this.headersMap.has(isLowerCase ? name : name.toLowerCase())
|
||||
return this.headersMap.has(isLowerCase ? name : name.toLowerCase());
|
||||
}
|
||||
|
||||
clear () {
|
||||
this.headersMap.clear()
|
||||
this.sortedMap = null
|
||||
this.cookies = null
|
||||
clear() {
|
||||
this.headersMap.clear();
|
||||
this.sortedMap = null;
|
||||
this.cookies = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,27 +240,27 @@ class HeadersList {
|
||||
* @param {string} value
|
||||
* @param {boolean} isLowerCase
|
||||
*/
|
||||
append (name, value, isLowerCase) {
|
||||
this.sortedMap = null
|
||||
append(name, value, isLowerCase) {
|
||||
this.sortedMap = null;
|
||||
|
||||
// 1. If list contains name, then set name to the first such
|
||||
// header’s name.
|
||||
const lowercaseName = isLowerCase ? name : name.toLowerCase()
|
||||
const exists = this.headersMap.get(lowercaseName)
|
||||
const lowercaseName = isLowerCase ? name : name.toLowerCase();
|
||||
const exists = this.headersMap.get(lowercaseName);
|
||||
|
||||
// 2. Append (name, value) to list.
|
||||
if (exists) {
|
||||
const delimiter = lowercaseName === 'cookie' ? '; ' : ', '
|
||||
const delimiter = lowercaseName === 'cookie' ? '; ' : ', ';
|
||||
this.headersMap.set(lowercaseName, {
|
||||
name: exists.name,
|
||||
value: `${exists.value}${delimiter}${value}`
|
||||
})
|
||||
value: `${exists.value}${delimiter}${value}`,
|
||||
});
|
||||
} else {
|
||||
this.headersMap.set(lowercaseName, { name, value })
|
||||
this.headersMap.set(lowercaseName, { name, value });
|
||||
}
|
||||
|
||||
if (lowercaseName === 'set-cookie') {
|
||||
(this.cookies ??= []).push(value)
|
||||
(this.cookies ??= []).push(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,19 +270,19 @@ class HeadersList {
|
||||
* @param {string} value
|
||||
* @param {boolean} isLowerCase
|
||||
*/
|
||||
set (name, value, isLowerCase) {
|
||||
this.sortedMap = null
|
||||
const lowercaseName = isLowerCase ? name : name.toLowerCase()
|
||||
set(name, value, isLowerCase) {
|
||||
this.sortedMap = null;
|
||||
const lowercaseName = isLowerCase ? name : name.toLowerCase();
|
||||
|
||||
if (lowercaseName === 'set-cookie') {
|
||||
this.cookies = [value]
|
||||
this.cookies = [value];
|
||||
}
|
||||
|
||||
// 1. If list contains name, then set the value of
|
||||
// the first such header to value and remove the
|
||||
// others.
|
||||
// 2. Otherwise, append header (name, value) to list.
|
||||
this.headersMap.set(lowercaseName, { name, value })
|
||||
this.headersMap.set(lowercaseName, { name, value });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,15 +290,15 @@ class HeadersList {
|
||||
* @param {string} name
|
||||
* @param {boolean} isLowerCase
|
||||
*/
|
||||
delete (name, isLowerCase) {
|
||||
this.sortedMap = null
|
||||
if (!isLowerCase) name = name.toLowerCase()
|
||||
delete(name, isLowerCase) {
|
||||
this.sortedMap = null;
|
||||
if (!isLowerCase) name = name.toLowerCase();
|
||||
|
||||
if (name === 'set-cookie') {
|
||||
this.cookies = null
|
||||
this.cookies = null;
|
||||
}
|
||||
|
||||
this.headersMap.delete(name)
|
||||
this.headersMap.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,191 +307,203 @@ class HeadersList {
|
||||
* @param {boolean} isLowerCase
|
||||
* @returns {string | null}
|
||||
*/
|
||||
get (name, isLowerCase) {
|
||||
get(name, isLowerCase) {
|
||||
// 1. If list does not contain name, then return null.
|
||||
// 2. Return the values of all headers in list whose name
|
||||
// is a byte-case-insensitive match for name,
|
||||
// separated from each other by 0x2C 0x20, in order.
|
||||
return this.headersMap.get(isLowerCase ? name : name.toLowerCase())?.value ?? null
|
||||
return (
|
||||
this.headersMap.get(isLowerCase ? name : name.toLowerCase())?.value ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
* [Symbol.iterator] () {
|
||||
*[Symbol.iterator]() {
|
||||
// use the lowercased name
|
||||
for (const { 0: name, 1: { value } } of this.headersMap) {
|
||||
yield [name, value]
|
||||
for (const {
|
||||
0: name,
|
||||
1: { value },
|
||||
} of this.headersMap) {
|
||||
yield [name, value];
|
||||
}
|
||||
}
|
||||
|
||||
get entries () {
|
||||
const headers = {}
|
||||
get entries() {
|
||||
const headers = {};
|
||||
|
||||
if (this.headersMap.size !== 0) {
|
||||
for (const { name, value } of this.headersMap.values()) {
|
||||
headers[name] = value
|
||||
headers[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
return headers;
|
||||
}
|
||||
|
||||
rawValues () {
|
||||
return this.headersMap.values()
|
||||
rawValues() {
|
||||
return this.headersMap.values();
|
||||
}
|
||||
|
||||
get entriesList () {
|
||||
const headers = []
|
||||
get entriesList() {
|
||||
const headers = [];
|
||||
|
||||
if (this.headersMap.size !== 0) {
|
||||
for (const { 0: lowerName, 1: { name, value } } of this.headersMap) {
|
||||
for (const {
|
||||
0: lowerName,
|
||||
1: { name, value },
|
||||
} of this.headersMap) {
|
||||
if (lowerName === 'set-cookie') {
|
||||
for (const cookie of this.cookies) {
|
||||
headers.push([name, cookie])
|
||||
headers.push([name, cookie]);
|
||||
}
|
||||
} else {
|
||||
headers.push([name, value])
|
||||
headers.push([name, value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
return headers;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
|
||||
toSortedArray () {
|
||||
const size = this.headersMap.size
|
||||
const array = new Array(size)
|
||||
toSortedArray() {
|
||||
const size = this.headersMap.size;
|
||||
const array = new Array(size);
|
||||
// In most cases, you will use the fast-path.
|
||||
// fast-path: Use binary insertion sort for small arrays.
|
||||
if (size <= 32) {
|
||||
if (size === 0) {
|
||||
// If empty, it is an empty array. To avoid the first index assignment.
|
||||
return array
|
||||
return array;
|
||||
}
|
||||
// Improve performance by unrolling loop and avoiding double-loop.
|
||||
// Double-loop-less version of the binary insertion sort.
|
||||
const iterator = this.headersMap[Symbol.iterator]()
|
||||
const firstValue = iterator.next().value
|
||||
const iterator = this.headersMap[Symbol.iterator]();
|
||||
const firstValue = iterator.next().value;
|
||||
// set [name, value] to first index.
|
||||
array[0] = [firstValue[0], firstValue[1].value]
|
||||
array[0] = [firstValue[0], firstValue[1].value];
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
||||
// 3.2.2. Assert: value is non-null.
|
||||
assert(firstValue[1].value !== null)
|
||||
assert(firstValue[1].value !== null);
|
||||
for (
|
||||
let i = 1, j = 0, right = 0, left = 0, pivot = 0, x, value;
|
||||
i < size;
|
||||
++i
|
||||
) {
|
||||
// get next value
|
||||
value = iterator.next().value
|
||||
value = iterator.next().value;
|
||||
// set [name, value] to current index.
|
||||
x = array[i] = [value[0], value[1].value]
|
||||
x = array[i] = [value[0], value[1].value];
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
||||
// 3.2.2. Assert: value is non-null.
|
||||
assert(x[1] !== null)
|
||||
left = 0
|
||||
right = i
|
||||
assert(x[1] !== null);
|
||||
left = 0;
|
||||
right = i;
|
||||
// binary search
|
||||
while (left < right) {
|
||||
// middle index
|
||||
pivot = left + ((right - left) >> 1)
|
||||
pivot = left + ((right - left) >> 1);
|
||||
// compare header name
|
||||
if (array[pivot][0] <= x[0]) {
|
||||
left = pivot + 1
|
||||
left = pivot + 1;
|
||||
} else {
|
||||
right = pivot
|
||||
right = pivot;
|
||||
}
|
||||
}
|
||||
if (i !== pivot) {
|
||||
j = i
|
||||
j = i;
|
||||
while (j > left) {
|
||||
array[j] = array[--j]
|
||||
array[j] = array[--j];
|
||||
}
|
||||
array[left] = x
|
||||
array[left] = x;
|
||||
}
|
||||
}
|
||||
/* c8 ignore next 4 */
|
||||
if (!iterator.next().done) {
|
||||
// This is for debugging and will never be called.
|
||||
throw new TypeError('Unreachable')
|
||||
throw new TypeError('Unreachable');
|
||||
}
|
||||
return array
|
||||
return array;
|
||||
} else {
|
||||
// This case would be a rare occurrence.
|
||||
// slow-path: fallback
|
||||
let i = 0
|
||||
for (const { 0: name, 1: { value } } of this.headersMap) {
|
||||
array[i++] = [name, value]
|
||||
let i = 0;
|
||||
for (const {
|
||||
0: name,
|
||||
1: { value },
|
||||
} of this.headersMap) {
|
||||
array[i++] = [name, value];
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
||||
// 3.2.2. Assert: value is non-null.
|
||||
assert(value !== null)
|
||||
assert(value !== null);
|
||||
}
|
||||
return array.sort(compareHeaderName)
|
||||
return array.sort(compareHeaderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#headers-class
|
||||
class Headers {
|
||||
#guard
|
||||
#guard;
|
||||
/**
|
||||
* @type {HeadersList}
|
||||
*/
|
||||
#headersList
|
||||
#headersList;
|
||||
|
||||
/**
|
||||
* @param {HeadersInit|Symbol} [init]
|
||||
* @returns
|
||||
*/
|
||||
constructor (init = undefined) {
|
||||
webidl.util.markAsUncloneable(this)
|
||||
constructor(init = undefined) {
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
if (init === kConstruct) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.#headersList = new HeadersList()
|
||||
this.#headersList = new HeadersList();
|
||||
|
||||
// The new Headers(init) constructor steps are:
|
||||
|
||||
// 1. Set this’s guard to "none".
|
||||
this.#guard = 'none'
|
||||
this.#guard = 'none';
|
||||
|
||||
// 2. If init is given, then fill this with init.
|
||||
if (init !== undefined) {
|
||||
init = webidl.converters.HeadersInit(init, 'Headers constructor', 'init')
|
||||
fill(this, init)
|
||||
init = webidl.converters.HeadersInit(init, 'Headers constructor', 'init');
|
||||
fill(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-append
|
||||
append (name, value) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
append(name, value) {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, 'Headers.append')
|
||||
webidl.argumentLengthCheck(arguments, 2, 'Headers.append');
|
||||
|
||||
const prefix = 'Headers.append'
|
||||
name = webidl.converters.ByteString(name, prefix, 'name')
|
||||
value = webidl.converters.ByteString(value, prefix, 'value')
|
||||
const prefix = 'Headers.append';
|
||||
name = webidl.converters.ByteString(name, prefix, 'name');
|
||||
value = webidl.converters.ByteString(value, prefix, 'value');
|
||||
|
||||
return appendHeader(this, name, value)
|
||||
return appendHeader(this, name, value);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
||||
delete (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
delete(name) {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.delete')
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.delete');
|
||||
|
||||
const prefix = 'Headers.delete'
|
||||
name = webidl.converters.ByteString(name, prefix, 'name')
|
||||
const prefix = 'Headers.delete';
|
||||
name = webidl.converters.ByteString(name, prefix, 'name');
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.delete',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
type: 'header name',
|
||||
});
|
||||
}
|
||||
|
||||
// 2. If this’s guard is "immutable", then throw a TypeError.
|
||||
@ -498,79 +517,79 @@ class Headers {
|
||||
// a forbidden response-header name, return.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this.#guard === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
throw new TypeError('immutable');
|
||||
}
|
||||
|
||||
// 6. If this’s header list does not contain name, then
|
||||
// return.
|
||||
if (!this.#headersList.contains(name, false)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Delete name from this’s header list.
|
||||
// 8. If this’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from this.
|
||||
this.#headersList.delete(name, false)
|
||||
this.#headersList.delete(name, false);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-get
|
||||
get (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
get(name) {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.get')
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.get');
|
||||
|
||||
const prefix = 'Headers.get'
|
||||
name = webidl.converters.ByteString(name, prefix, 'name')
|
||||
const prefix = 'Headers.get';
|
||||
name = webidl.converters.ByteString(name, prefix, 'name');
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix,
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
type: 'header name',
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Return the result of getting name from this’s header
|
||||
// list.
|
||||
return this.#headersList.get(name, false)
|
||||
return this.#headersList.get(name, false);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-has
|
||||
has (name) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
has(name) {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.has')
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Headers.has');
|
||||
|
||||
const prefix = 'Headers.has'
|
||||
name = webidl.converters.ByteString(name, prefix, 'name')
|
||||
const prefix = 'Headers.has';
|
||||
name = webidl.converters.ByteString(name, prefix, 'name');
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix,
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
type: 'header name',
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Return true if this’s header list contains name;
|
||||
// otherwise false.
|
||||
return this.#headersList.contains(name, false)
|
||||
return this.#headersList.contains(name, false);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-set
|
||||
set (name, value) {
|
||||
webidl.brandCheck(this, Headers)
|
||||
set(name, value) {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 2, 'Headers.set')
|
||||
webidl.argumentLengthCheck(arguments, 2, 'Headers.set');
|
||||
|
||||
const prefix = 'Headers.set'
|
||||
name = webidl.converters.ByteString(name, prefix, 'name')
|
||||
value = webidl.converters.ByteString(value, prefix, 'value')
|
||||
const prefix = 'Headers.set';
|
||||
name = webidl.converters.ByteString(name, prefix, 'name');
|
||||
value = webidl.converters.ByteString(value, prefix, 'value');
|
||||
|
||||
// 1. Normalize value.
|
||||
value = headerValueNormalize(value)
|
||||
value = headerValueNormalize(value);
|
||||
|
||||
// 2. If name is not a header name or value is not a
|
||||
// header value, then throw a TypeError.
|
||||
@ -578,14 +597,14 @@ class Headers {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix,
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
type: 'header name',
|
||||
});
|
||||
} else if (!isValidHeaderValue(value)) {
|
||||
throw webidl.errors.invalidArgument({
|
||||
prefix,
|
||||
value,
|
||||
type: 'header value'
|
||||
})
|
||||
type: 'header value',
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If this’s guard is "immutable", then throw a TypeError.
|
||||
@ -598,69 +617,70 @@ class Headers {
|
||||
// forbidden response-header name, return.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this.#guard === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
throw new TypeError('immutable');
|
||||
}
|
||||
|
||||
// 7. Set (name, value) in this’s header list.
|
||||
// 8. If this’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from this
|
||||
this.#headersList.set(name, value, false)
|
||||
this.#headersList.set(name, value, false);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
|
||||
getSetCookie () {
|
||||
webidl.brandCheck(this, Headers)
|
||||
getSetCookie() {
|
||||
webidl.brandCheck(this, Headers);
|
||||
|
||||
// 1. If this’s header list does not contain `Set-Cookie`, then return « ».
|
||||
// 2. Return the values of all headers in this’s header list whose name is
|
||||
// a byte-case-insensitive match for `Set-Cookie`, in order.
|
||||
|
||||
const list = this.#headersList.cookies
|
||||
const list = this.#headersList.cookies;
|
||||
|
||||
if (list) {
|
||||
return [...list]
|
||||
return [...list];
|
||||
}
|
||||
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
[util.inspect.custom] (depth, options) {
|
||||
options.depth ??= depth
|
||||
[util.inspect.custom](depth, options) {
|
||||
options.depth ??= depth;
|
||||
|
||||
return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`
|
||||
return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`;
|
||||
}
|
||||
|
||||
static getHeadersGuard (o) {
|
||||
return o.#guard
|
||||
static getHeadersGuard(o) {
|
||||
return o.#guard;
|
||||
}
|
||||
|
||||
static setHeadersGuard (o, guard) {
|
||||
o.#guard = guard
|
||||
static setHeadersGuard(o, guard) {
|
||||
o.#guard = guard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} o
|
||||
*/
|
||||
static getHeadersList (o) {
|
||||
return o.#headersList
|
||||
static getHeadersList(o) {
|
||||
return o.#headersList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} target
|
||||
* @param {HeadersList} list
|
||||
*/
|
||||
static setHeadersList (target, list) {
|
||||
target.#headersList = list
|
||||
static setHeadersList(target, list) {
|
||||
target.#headersList = list;
|
||||
}
|
||||
}
|
||||
|
||||
const { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } = Headers
|
||||
Reflect.deleteProperty(Headers, 'getHeadersGuard')
|
||||
Reflect.deleteProperty(Headers, 'setHeadersGuard')
|
||||
Reflect.deleteProperty(Headers, 'getHeadersList')
|
||||
Reflect.deleteProperty(Headers, 'setHeadersList')
|
||||
const { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } =
|
||||
Headers;
|
||||
Reflect.deleteProperty(Headers, 'getHeadersGuard');
|
||||
Reflect.deleteProperty(Headers, 'setHeadersGuard');
|
||||
Reflect.deleteProperty(Headers, 'getHeadersList');
|
||||
Reflect.deleteProperty(Headers, 'setHeadersList');
|
||||
|
||||
iteratorMixin('Headers', Headers, headersListSortAndCombine, 0, 1)
|
||||
iteratorMixin('Headers', Headers, headersListSortAndCombine, 0, 1);
|
||||
|
||||
Object.defineProperties(Headers.prototype, {
|
||||
append: kEnumerableProperty,
|
||||
@ -671,40 +691,50 @@ Object.defineProperties(Headers.prototype, {
|
||||
getSetCookie: kEnumerableProperty,
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'Headers',
|
||||
configurable: true
|
||||
configurable: true,
|
||||
},
|
||||
[util.inspect.custom]: {
|
||||
enumerable: false
|
||||
}
|
||||
})
|
||||
enumerable: false,
|
||||
},
|
||||
});
|
||||
|
||||
webidl.converters.HeadersInit = function (V, prefix, argument) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
||||
const iterator = Reflect.get(V, Symbol.iterator)
|
||||
const iterator = Reflect.get(V, Symbol.iterator);
|
||||
|
||||
// A work-around to ensure we send the properly-cased Headers when V is a Headers object.
|
||||
// Read https://github.com/nodejs/undici/pull/3159#issuecomment-2075537226 before touching, please.
|
||||
if (!util.types.isProxy(V) && iterator === Headers.prototype.entries) { // Headers object
|
||||
if (!util.types.isProxy(V) && iterator === Headers.prototype.entries) {
|
||||
// Headers object
|
||||
try {
|
||||
return getHeadersList(V).entriesList
|
||||
return getHeadersList(V).entriesList;
|
||||
} catch {
|
||||
// fall-through
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof iterator === 'function') {
|
||||
return webidl.converters['sequence<sequence<ByteString>>'](V, prefix, argument, iterator.bind(V))
|
||||
return webidl.converters['sequence<sequence<ByteString>>'](
|
||||
V,
|
||||
prefix,
|
||||
argument,
|
||||
iterator.bind(V)
|
||||
);
|
||||
}
|
||||
|
||||
return webidl.converters['record<ByteString, ByteString>'](V, prefix, argument)
|
||||
return webidl.converters['record<ByteString, ByteString>'](
|
||||
V,
|
||||
prefix,
|
||||
argument
|
||||
);
|
||||
}
|
||||
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
}
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>'],
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
fill,
|
||||
@ -715,5 +745,5 @@ module.exports = {
|
||||
getHeadersGuard,
|
||||
setHeadersGuard,
|
||||
setHeadersList,
|
||||
getHeadersList
|
||||
}
|
||||
getHeadersList,
|
||||
};
|
||||
|
1343
node_modules/undici/lib/web/fetch/index.js
generated
vendored
1343
node_modules/undici/lib/web/fetch/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
657
node_modules/undici/lib/web/fetch/request.js
generated
vendored
657
node_modules/undici/lib/web/fetch/request.js
generated
vendored
File diff suppressed because it is too large
Load Diff
440
node_modules/undici/lib/web/fetch/response.js
generated
vendored
440
node_modules/undici/lib/web/fetch/response.js
generated
vendored
@ -1,10 +1,24 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
|
||||
const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require('./body')
|
||||
const util = require('../../core/util')
|
||||
const nodeUtil = require('node:util')
|
||||
const { kEnumerableProperty } = util
|
||||
const {
|
||||
Headers,
|
||||
HeadersList,
|
||||
fill,
|
||||
getHeadersGuard,
|
||||
setHeadersGuard,
|
||||
setHeadersList,
|
||||
} = require('./headers');
|
||||
const {
|
||||
extractBody,
|
||||
cloneBody,
|
||||
mixinBody,
|
||||
hasFinalizationRegistry,
|
||||
streamRegistry,
|
||||
bodyUnusable,
|
||||
} = require('./body');
|
||||
const util = require('../../core/util');
|
||||
const nodeUtil = require('node:util');
|
||||
const { kEnumerableProperty } = util;
|
||||
const {
|
||||
isValidReasonPhrase,
|
||||
isCancelled,
|
||||
@ -12,248 +26,248 @@ const {
|
||||
serializeJavascriptValueToJSONString,
|
||||
isErrorLike,
|
||||
isomorphicEncode,
|
||||
environmentSettingsObject: relevantRealm
|
||||
} = require('./util')
|
||||
const {
|
||||
redirectStatusSet,
|
||||
nullBodyStatus
|
||||
} = require('./constants')
|
||||
const { webidl } = require('./webidl')
|
||||
const { URLSerializer } = require('./data-url')
|
||||
const { kConstruct } = require('../../core/symbols')
|
||||
const assert = require('node:assert')
|
||||
const { types } = require('node:util')
|
||||
environmentSettingsObject: relevantRealm,
|
||||
} = require('./util');
|
||||
const { redirectStatusSet, nullBodyStatus } = require('./constants');
|
||||
const { webidl } = require('./webidl');
|
||||
const { URLSerializer } = require('./data-url');
|
||||
const { kConstruct } = require('../../core/symbols');
|
||||
const assert = require('node:assert');
|
||||
const { types } = require('node:util');
|
||||
|
||||
const textEncoder = new TextEncoder('utf-8')
|
||||
const textEncoder = new TextEncoder('utf-8');
|
||||
|
||||
// https://fetch.spec.whatwg.org/#response-class
|
||||
class Response {
|
||||
/** @type {Headers} */
|
||||
#headers
|
||||
#headers;
|
||||
|
||||
#state
|
||||
#state;
|
||||
|
||||
// Creates network error Response.
|
||||
static error () {
|
||||
static error() {
|
||||
// The static error() method steps are to return the result of creating a
|
||||
// Response object, given a new network error, "immutable", and this’s
|
||||
// relevant Realm.
|
||||
const responseObject = fromInnerResponse(makeNetworkError(), 'immutable')
|
||||
const responseObject = fromInnerResponse(makeNetworkError(), 'immutable');
|
||||
|
||||
return responseObject
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response-json
|
||||
static json (data, init = undefined) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Response.json')
|
||||
static json(data, init = undefined) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Response.json');
|
||||
|
||||
if (init !== null) {
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
init = webidl.converters.ResponseInit(init);
|
||||
}
|
||||
|
||||
// 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
|
||||
const bytes = textEncoder.encode(
|
||||
serializeJavascriptValueToJSONString(data)
|
||||
)
|
||||
);
|
||||
|
||||
// 2. Let body be the result of extracting bytes.
|
||||
const body = extractBody(bytes)
|
||||
const body = extractBody(bytes);
|
||||
|
||||
// 3. Let responseObject be the result of creating a Response object, given a new response,
|
||||
// "response", and this’s relevant Realm.
|
||||
const responseObject = fromInnerResponse(makeResponse({}), 'response')
|
||||
const responseObject = fromInnerResponse(makeResponse({}), 'response');
|
||||
|
||||
// 4. Perform initialize a response given responseObject, init, and (body, "application/json").
|
||||
initializeResponse(responseObject, init, { body: body[0], type: 'application/json' })
|
||||
initializeResponse(responseObject, init, {
|
||||
body: body[0],
|
||||
type: 'application/json',
|
||||
});
|
||||
|
||||
// 5. Return responseObject.
|
||||
return responseObject
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
// Creates a redirect Response that redirects to url with status status.
|
||||
static redirect (url, status = 302) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Response.redirect')
|
||||
static redirect(url, status = 302) {
|
||||
webidl.argumentLengthCheck(arguments, 1, 'Response.redirect');
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
status = webidl.converters['unsigned short'](status)
|
||||
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
|
||||
let parsedURL;
|
||||
try {
|
||||
parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl)
|
||||
parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl);
|
||||
} catch (err) {
|
||||
throw new TypeError(`Failed to parse URL from ${url}`, { cause: err })
|
||||
throw new TypeError(`Failed to parse URL from ${url}`, { cause: err });
|
||||
}
|
||||
|
||||
// 3. If status is not a redirect status, then throw a RangeError.
|
||||
if (!redirectStatusSet.has(status)) {
|
||||
throw new RangeError(`Invalid status code ${status}`)
|
||||
throw new RangeError(`Invalid status code ${status}`);
|
||||
}
|
||||
|
||||
// 4. Let responseObject be the result of creating a Response object,
|
||||
// given a new response, "immutable", and this’s relevant Realm.
|
||||
const responseObject = fromInnerResponse(makeResponse({}), 'immutable')
|
||||
const responseObject = fromInnerResponse(makeResponse({}), 'immutable');
|
||||
|
||||
// 5. Set responseObject’s response’s status to status.
|
||||
responseObject.#state.status = status
|
||||
responseObject.#state.status = status;
|
||||
|
||||
// 6. Let value be parsedURL, serialized and isomorphic encoded.
|
||||
const value = isomorphicEncode(URLSerializer(parsedURL))
|
||||
const value = isomorphicEncode(URLSerializer(parsedURL));
|
||||
|
||||
// 7. Append `Location`/value to responseObject’s response’s header list.
|
||||
responseObject.#state.headersList.append('location', value, true)
|
||||
responseObject.#state.headersList.append('location', value, true);
|
||||
|
||||
// 8. Return responseObject.
|
||||
return responseObject
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response
|
||||
constructor (body = null, init = undefined) {
|
||||
webidl.util.markAsUncloneable(this)
|
||||
constructor(body = null, init = undefined) {
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
if (body === kConstruct) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (body !== null) {
|
||||
body = webidl.converters.BodyInit(body)
|
||||
body = webidl.converters.BodyInit(body);
|
||||
}
|
||||
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
init = webidl.converters.ResponseInit(init);
|
||||
|
||||
// 1. Set this’s response to a new response.
|
||||
this.#state = makeResponse({})
|
||||
this.#state = makeResponse({});
|
||||
|
||||
// 2. Set this’s headers to a new Headers object with this’s relevant
|
||||
// Realm, whose header list is this’s response’s header list and guard
|
||||
// is "response".
|
||||
this.#headers = new Headers(kConstruct)
|
||||
setHeadersGuard(this.#headers, 'response')
|
||||
setHeadersList(this.#headers, this.#state.headersList)
|
||||
this.#headers = new Headers(kConstruct);
|
||||
setHeadersGuard(this.#headers, 'response');
|
||||
setHeadersList(this.#headers, this.#state.headersList);
|
||||
|
||||
// 3. Let bodyWithType be null.
|
||||
let bodyWithType = 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 }
|
||||
const [extractedBody, type] = extractBody(body);
|
||||
bodyWithType = { body: extractedBody, type };
|
||||
}
|
||||
|
||||
// 5. Perform initialize a response given this, init, and bodyWithType.
|
||||
initializeResponse(this, init, bodyWithType)
|
||||
initializeResponse(this, init, bodyWithType);
|
||||
}
|
||||
|
||||
// Returns response’s type, e.g., "cors".
|
||||
get type () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get type() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The type getter steps are to return this’s response’s type.
|
||||
return this.#state.type
|
||||
return this.#state.type;
|
||||
}
|
||||
|
||||
// Returns response’s URL, if it has one; otherwise the empty string.
|
||||
get url () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get url() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
const urlList = this.#state.urlList
|
||||
const urlList = this.#state.urlList;
|
||||
|
||||
// The url getter steps are to return the empty string if this’s
|
||||
// response’s URL is null; otherwise this’s response’s URL,
|
||||
// serialized with exclude fragment set to true.
|
||||
const url = urlList[urlList.length - 1] ?? null
|
||||
const url = urlList[urlList.length - 1] ?? null;
|
||||
|
||||
if (url === null) {
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
return URLSerializer(url, true)
|
||||
return URLSerializer(url, true);
|
||||
}
|
||||
|
||||
// Returns whether response was obtained through a redirect.
|
||||
get redirected () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get redirected() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The redirected getter steps are to return true if this’s response’s URL
|
||||
// list has more than one item; otherwise false.
|
||||
return this.#state.urlList.length > 1
|
||||
return this.#state.urlList.length > 1;
|
||||
}
|
||||
|
||||
// Returns response’s status.
|
||||
get status () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get status() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The status getter steps are to return this’s response’s status.
|
||||
return this.#state.status
|
||||
return this.#state.status;
|
||||
}
|
||||
|
||||
// Returns whether response’s status is an ok status.
|
||||
get ok () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get ok() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The ok getter steps are to return true if this’s response’s status is an
|
||||
// ok status; otherwise false.
|
||||
return this.#state.status >= 200 && this.#state.status <= 299
|
||||
return this.#state.status >= 200 && this.#state.status <= 299;
|
||||
}
|
||||
|
||||
// Returns response’s status message.
|
||||
get statusText () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get statusText() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The statusText getter steps are to return this’s response’s status
|
||||
// message.
|
||||
return this.#state.statusText
|
||||
return this.#state.statusText;
|
||||
}
|
||||
|
||||
// Returns response’s headers as Headers.
|
||||
get headers () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get headers() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// The headers getter steps are to return this’s headers.
|
||||
return this.#headers
|
||||
return this.#headers;
|
||||
}
|
||||
|
||||
get body () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get body() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
return this.#state.body ? this.#state.body.stream : null
|
||||
return this.#state.body ? this.#state.body.stream : null;
|
||||
}
|
||||
|
||||
get bodyUsed () {
|
||||
webidl.brandCheck(this, Response)
|
||||
get bodyUsed() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
return !!this.#state.body && util.isDisturbed(this.#state.body.stream)
|
||||
return !!this.#state.body && util.isDisturbed(this.#state.body.stream);
|
||||
}
|
||||
|
||||
// Returns a clone of response.
|
||||
clone () {
|
||||
webidl.brandCheck(this, Response)
|
||||
clone() {
|
||||
webidl.brandCheck(this, Response);
|
||||
|
||||
// 1. If this is unusable, then throw a TypeError.
|
||||
if (bodyUnusable(this.#state)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Response.clone',
|
||||
message: 'Body has already been consumed.'
|
||||
})
|
||||
message: 'Body has already been consumed.',
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Let clonedResponse be the result of cloning this’s response.
|
||||
const clonedResponse = cloneResponse(this.#state)
|
||||
const clonedResponse = cloneResponse(this.#state);
|
||||
|
||||
// 3. Return the result of creating a Response object, given
|
||||
// clonedResponse, this’s headers’s guard, and this’s relevant Realm.
|
||||
return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers))
|
||||
return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers));
|
||||
}
|
||||
|
||||
[nodeUtil.inspect.custom] (depth, options) {
|
||||
[nodeUtil.inspect.custom](depth, options) {
|
||||
if (options.depth === null) {
|
||||
options.depth = 2
|
||||
options.depth = 2;
|
||||
}
|
||||
|
||||
options.colors ??= true
|
||||
options.colors ??= true;
|
||||
|
||||
const properties = {
|
||||
status: this.status,
|
||||
@ -264,50 +278,55 @@ class Response {
|
||||
ok: this.ok,
|
||||
redirected: this.redirected,
|
||||
type: this.type,
|
||||
url: this.url
|
||||
}
|
||||
url: this.url,
|
||||
};
|
||||
|
||||
return `Response ${nodeUtil.formatWithOptions(options, properties)}`
|
||||
return `Response ${nodeUtil.formatWithOptions(options, properties)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
*/
|
||||
static getResponseHeaders (response) {
|
||||
return response.#headers
|
||||
static getResponseHeaders(response) {
|
||||
return response.#headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
* @param {Headers} newHeaders
|
||||
*/
|
||||
static setResponseHeaders (response, newHeaders) {
|
||||
response.#headers = newHeaders
|
||||
static setResponseHeaders(response, newHeaders) {
|
||||
response.#headers = newHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
*/
|
||||
static getResponseState (response) {
|
||||
return response.#state
|
||||
static getResponseState(response) {
|
||||
return response.#state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
* @param {any} newState
|
||||
*/
|
||||
static setResponseState (response, newState) {
|
||||
response.#state = newState
|
||||
static setResponseState(response, newState) {
|
||||
response.#state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
const { getResponseHeaders, setResponseHeaders, getResponseState, setResponseState } = Response
|
||||
Reflect.deleteProperty(Response, 'getResponseHeaders')
|
||||
Reflect.deleteProperty(Response, 'setResponseHeaders')
|
||||
Reflect.deleteProperty(Response, 'getResponseState')
|
||||
Reflect.deleteProperty(Response, 'setResponseState')
|
||||
const {
|
||||
getResponseHeaders,
|
||||
setResponseHeaders,
|
||||
getResponseState,
|
||||
setResponseState,
|
||||
} = Response;
|
||||
Reflect.deleteProperty(Response, 'getResponseHeaders');
|
||||
Reflect.deleteProperty(Response, 'setResponseHeaders');
|
||||
Reflect.deleteProperty(Response, 'getResponseState');
|
||||
Reflect.deleteProperty(Response, 'setResponseState');
|
||||
|
||||
mixinBody(Response, getResponseState)
|
||||
mixinBody(Response, getResponseState);
|
||||
|
||||
Object.defineProperties(Response.prototype, {
|
||||
type: kEnumerableProperty,
|
||||
@ -322,18 +341,18 @@ Object.defineProperties(Response.prototype, {
|
||||
bodyUsed: kEnumerableProperty,
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'Response',
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperties(Response, {
|
||||
json: kEnumerableProperty,
|
||||
redirect: kEnumerableProperty,
|
||||
error: kEnumerableProperty
|
||||
})
|
||||
error: kEnumerableProperty,
|
||||
});
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-clone
|
||||
function cloneResponse (response) {
|
||||
function cloneResponse(response) {
|
||||
// To clone a response response, run these steps:
|
||||
|
||||
// 1. If response is a filtered response, then return a new identical
|
||||
@ -343,23 +362,23 @@ function cloneResponse (response) {
|
||||
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 })
|
||||
const newResponse = makeResponse({ ...response, body: null });
|
||||
|
||||
// 3. If response’s body is non-null, then set newResponse’s body to the
|
||||
// result of cloning response’s body.
|
||||
if (response.body != null) {
|
||||
newResponse.body = cloneBody(newResponse, response.body)
|
||||
newResponse.body = cloneBody(newResponse, response.body);
|
||||
}
|
||||
|
||||
// 4. Return newResponse.
|
||||
return newResponse
|
||||
return newResponse;
|
||||
}
|
||||
|
||||
function makeResponse (init) {
|
||||
function makeResponse(init) {
|
||||
return {
|
||||
aborted: false,
|
||||
rangeRequested: false,
|
||||
@ -371,55 +390,54 @@ function makeResponse (init) {
|
||||
cacheState: '',
|
||||
statusText: '',
|
||||
...init,
|
||||
headersList: init?.headersList
|
||||
? new HeadersList(init?.headersList)
|
||||
headersList:
|
||||
init?.headersList ?
|
||||
new HeadersList(init?.headersList)
|
||||
: new HeadersList(),
|
||||
urlList: init?.urlList ? [...init.urlList] : []
|
||||
}
|
||||
urlList: init?.urlList ? [...init.urlList] : [],
|
||||
};
|
||||
}
|
||||
|
||||
function makeNetworkError (reason) {
|
||||
const isError = isErrorLike(reason)
|
||||
function makeNetworkError(reason) {
|
||||
const isError = isErrorLike(reason);
|
||||
return makeResponse({
|
||||
type: 'error',
|
||||
status: 0,
|
||||
error: isError
|
||||
? reason
|
||||
: new Error(reason ? String(reason) : reason),
|
||||
aborted: reason && reason.name === 'AbortError'
|
||||
})
|
||||
error: isError ? reason : new Error(reason ? String(reason) : reason),
|
||||
aborted: reason && reason.name === 'AbortError',
|
||||
});
|
||||
}
|
||||
|
||||
// @see https://fetch.spec.whatwg.org/#concept-network-error
|
||||
function isNetworkError (response) {
|
||||
function isNetworkError(response) {
|
||||
return (
|
||||
// A network error is a response whose type is "error",
|
||||
response.type === 'error' &&
|
||||
// status is 0
|
||||
response.status === 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function makeFilteredResponse (response, state) {
|
||||
function makeFilteredResponse(response, state) {
|
||||
state = {
|
||||
internalResponse: response,
|
||||
...state
|
||||
}
|
||||
...state,
|
||||
};
|
||||
|
||||
return new Proxy(response, {
|
||||
get (target, p) {
|
||||
return p in state ? state[p] : target[p]
|
||||
get(target, p) {
|
||||
return p in state ? state[p] : target[p];
|
||||
},
|
||||
set (target, p, value) {
|
||||
assert(!(p in state))
|
||||
target[p] = value
|
||||
return true
|
||||
}
|
||||
})
|
||||
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) {
|
||||
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') {
|
||||
@ -430,8 +448,8 @@ function filterResponse (response, type) {
|
||||
// Note: undici does not implement forbidden response-header names
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'basic',
|
||||
headersList: response.headersList
|
||||
})
|
||||
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
|
||||
@ -441,8 +459,8 @@ function filterResponse (response, type) {
|
||||
// Note: undici does not implement CORS-safelisted response-header names
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'cors',
|
||||
headersList: response.headersList
|
||||
})
|
||||
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
|
||||
@ -453,8 +471,8 @@ function filterResponse (response, type) {
|
||||
urlList: Object.freeze([]),
|
||||
status: 0,
|
||||
statusText: '',
|
||||
body: null
|
||||
})
|
||||
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
|
||||
@ -465,31 +483,42 @@ function filterResponse (response, type) {
|
||||
status: 0,
|
||||
statusText: '',
|
||||
headersList: [],
|
||||
body: null
|
||||
})
|
||||
body: null,
|
||||
});
|
||||
} else {
|
||||
assert(false)
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#appropriate-network-error
|
||||
function makeAppropriateNetworkError (fetchParams, err = null) {
|
||||
function makeAppropriateNetworkError(fetchParams, err = null) {
|
||||
// 1. Assert: fetchParams is canceled.
|
||||
assert(isCancelled(fetchParams))
|
||||
assert(isCancelled(fetchParams));
|
||||
|
||||
// 2. Return an aborted network error if fetchParams is aborted;
|
||||
// otherwise return a network error.
|
||||
return isAborted(fetchParams)
|
||||
? makeNetworkError(Object.assign(new DOMException('The operation was aborted.', 'AbortError'), { cause: err }))
|
||||
: makeNetworkError(Object.assign(new DOMException('Request was cancelled.'), { cause: err }))
|
||||
return isAborted(fetchParams) ?
|
||||
makeNetworkError(
|
||||
Object.assign(
|
||||
new DOMException('The operation was aborted.', 'AbortError'),
|
||||
{ cause: err }
|
||||
)
|
||||
)
|
||||
: makeNetworkError(
|
||||
Object.assign(new DOMException('Request was cancelled.'), {
|
||||
cause: err,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// https://whatpr.org/fetch/1392.html#initialize-a-response
|
||||
function initializeResponse (response, init, body) {
|
||||
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.')
|
||||
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,
|
||||
@ -498,23 +527,23 @@ function initializeResponse (response, init, body) {
|
||||
// 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')
|
||||
throw new TypeError('Invalid statusText');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set response’s response’s status to init["status"].
|
||||
if ('status' in init && init.status != null) {
|
||||
getResponseState(response).status = init.status
|
||||
getResponseState(response).status = init.status;
|
||||
}
|
||||
|
||||
// 4. Set response’s response’s status message to init["statusText"].
|
||||
if ('statusText' in init && init.statusText != null) {
|
||||
getResponseState(response).statusText = init.statusText
|
||||
getResponseState(response).statusText = init.statusText;
|
||||
}
|
||||
|
||||
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
|
||||
if ('headers' in init && init.headers != null) {
|
||||
fill(getResponseHeaders(response), init.headers)
|
||||
fill(getResponseHeaders(response), init.headers);
|
||||
}
|
||||
|
||||
// 6. If body was given, then:
|
||||
@ -523,17 +552,24 @@ function initializeResponse (response, init, body) {
|
||||
if (nullBodyStatus.includes(response.status)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Response constructor',
|
||||
message: `Invalid response status code ${response.status}`
|
||||
})
|
||||
message: `Invalid response status code ${response.status}`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Set response's body to body's body.
|
||||
getResponseState(response).body = body.body
|
||||
getResponseState(response).body = body.body;
|
||||
|
||||
// 3. If body's type is non-null and response's header list does not contain
|
||||
// `Content-Type`, then append (`Content-Type`, body's type) to response's header list.
|
||||
if (body.type != null && !getResponseState(response).headersList.contains('content-type', true)) {
|
||||
getResponseState(response).headersList.append('content-type', body.type, true)
|
||||
if (
|
||||
body.type != null &&
|
||||
!getResponseState(response).headersList.contains('content-type', true)
|
||||
) {
|
||||
getResponseState(response).headersList.append(
|
||||
'content-type',
|
||||
body.type,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -544,13 +580,13 @@ function initializeResponse (response, init, body) {
|
||||
* @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
|
||||
* @returns {Response}
|
||||
*/
|
||||
function fromInnerResponse (innerResponse, guard) {
|
||||
const response = new Response(kConstruct)
|
||||
setResponseState(response, innerResponse)
|
||||
const headers = new Headers(kConstruct)
|
||||
setResponseHeaders(response, headers)
|
||||
setHeadersList(headers, innerResponse.headersList)
|
||||
setHeadersGuard(headers, guard)
|
||||
function fromInnerResponse(innerResponse, guard) {
|
||||
const response = new Response(kConstruct);
|
||||
setResponseState(response, innerResponse);
|
||||
const headers = new Headers(kConstruct);
|
||||
setResponseHeaders(response, headers);
|
||||
setHeadersList(headers, innerResponse.headersList);
|
||||
setHeadersGuard(headers, guard);
|
||||
|
||||
if (hasFinalizationRegistry && innerResponse.body?.stream) {
|
||||
// If the target (response) is reclaimed, the cleanup callback may be called at some point with
|
||||
@ -558,70 +594,70 @@ function fromInnerResponse (innerResponse, guard) {
|
||||
// a primitive or an object, even undefined. If the held value is an object, the registry keeps
|
||||
// a strong reference to it (so it can pass it to the cleanup callback later). Reworded from
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
|
||||
streamRegistry.register(response, new WeakRef(innerResponse.body.stream))
|
||||
streamRegistry.register(response, new WeakRef(innerResponse.body.stream));
|
||||
}
|
||||
|
||||
return response
|
||||
return response;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
|
||||
webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) {
|
||||
if (typeof V === 'string') {
|
||||
return webidl.converters.USVString(V, prefix, name)
|
||||
return webidl.converters.USVString(V, prefix, name);
|
||||
}
|
||||
|
||||
if (webidl.is.Blob(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
if (webidl.is.FormData(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
if (webidl.is.URLSearchParams(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V, prefix, name)
|
||||
}
|
||||
return webidl.converters.DOMString(V, prefix, name);
|
||||
};
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit
|
||||
webidl.converters.BodyInit = function (V, prefix, argument) {
|
||||
if (webidl.is.ReadableStream(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
// Note: the spec doesn't include async iterables,
|
||||
// this is an undici extension.
|
||||
if (V?.[Symbol.asyncIterator]) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument)
|
||||
}
|
||||
return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument);
|
||||
};
|
||||
|
||||
webidl.converters.ResponseInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'status',
|
||||
converter: webidl.converters['unsigned short'],
|
||||
defaultValue: () => 200
|
||||
defaultValue: () => 200,
|
||||
},
|
||||
{
|
||||
key: 'statusText',
|
||||
converter: webidl.converters.ByteString,
|
||||
defaultValue: () => ''
|
||||
defaultValue: () => '',
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.converters.HeadersInit
|
||||
}
|
||||
])
|
||||
converter: webidl.converters.HeadersInit,
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.is.Response = webidl.util.MakeTypeAssertion(Response)
|
||||
webidl.is.Response = webidl.util.MakeTypeAssertion(Response);
|
||||
|
||||
module.exports = {
|
||||
isNetworkError,
|
||||
@ -632,5 +668,5 @@ module.exports = {
|
||||
Response,
|
||||
cloneResponse,
|
||||
fromInnerResponse,
|
||||
getResponseState
|
||||
}
|
||||
getResponseState,
|
||||
};
|
||||
|
1036
node_modules/undici/lib/web/fetch/util.js
generated
vendored
1036
node_modules/undici/lib/web/fetch/util.js
generated
vendored
File diff suppressed because it is too large
Load Diff
521
node_modules/undici/lib/web/fetch/webidl.js
generated
vendored
521
node_modules/undici/lib/web/fetch/webidl.js
generated
vendored
@ -1,112 +1,121 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { types, inspect } = require('node:util')
|
||||
const { markAsUncloneable } = require('node:worker_threads')
|
||||
const { toUSVString } = require('../../core/util')
|
||||
const { types, inspect } = require('node:util');
|
||||
const { markAsUncloneable } = require('node:worker_threads');
|
||||
const { toUSVString } = require('../../core/util');
|
||||
|
||||
const UNDEFINED = 1
|
||||
const BOOLEAN = 2
|
||||
const STRING = 3
|
||||
const SYMBOL = 4
|
||||
const NUMBER = 5
|
||||
const BIGINT = 6
|
||||
const NULL = 7
|
||||
const OBJECT = 8 // function and object
|
||||
const UNDEFINED = 1;
|
||||
const BOOLEAN = 2;
|
||||
const STRING = 3;
|
||||
const SYMBOL = 4;
|
||||
const NUMBER = 5;
|
||||
const BIGINT = 6;
|
||||
const NULL = 7;
|
||||
const OBJECT = 8; // function and object
|
||||
|
||||
const FunctionPrototypeSymbolHasInstance = Function.call.bind(Function.prototype[Symbol.hasInstance])
|
||||
const FunctionPrototypeSymbolHasInstance = Function.call.bind(
|
||||
Function.prototype[Symbol.hasInstance]
|
||||
);
|
||||
|
||||
/** @type {import('../../../types/webidl').Webidl} */
|
||||
const webidl = {
|
||||
converters: {},
|
||||
util: {},
|
||||
errors: {},
|
||||
is: {}
|
||||
}
|
||||
is: {},
|
||||
};
|
||||
|
||||
webidl.errors.exception = function (message) {
|
||||
return new TypeError(`${message.header}: ${message.message}`)
|
||||
}
|
||||
return new TypeError(`${message.header}: ${message.message}`);
|
||||
};
|
||||
|
||||
webidl.errors.conversionFailed = function (context) {
|
||||
const plural = context.types.length === 1 ? '' : ' one of'
|
||||
const plural = context.types.length === 1 ? '' : ' one of';
|
||||
const message =
|
||||
`${context.argument} could not be converted to` +
|
||||
`${plural}: ${context.types.join(', ')}.`
|
||||
`${plural}: ${context.types.join(', ')}.`;
|
||||
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message
|
||||
})
|
||||
}
|
||||
message,
|
||||
});
|
||||
};
|
||||
|
||||
webidl.errors.invalidArgument = function (context) {
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message: `"${context.value}" is an invalid ${context.type}.`
|
||||
})
|
||||
}
|
||||
message: `"${context.value}" is an invalid ${context.type}.`,
|
||||
});
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#implements
|
||||
webidl.brandCheck = function (V, I) {
|
||||
if (!FunctionPrototypeSymbolHasInstance(I, V)) {
|
||||
const err = new TypeError('Illegal invocation')
|
||||
err.code = 'ERR_INVALID_THIS' // node compat.
|
||||
throw err
|
||||
const err = new TypeError('Illegal invocation');
|
||||
err.code = 'ERR_INVALID_THIS'; // node compat.
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
webidl.brandCheckMultiple = function (List) {
|
||||
const prototypes = List.map((c) => webidl.util.MakeTypeAssertion(c))
|
||||
const prototypes = List.map((c) => webidl.util.MakeTypeAssertion(c));
|
||||
|
||||
return (V) => {
|
||||
if (prototypes.every(typeCheck => !typeCheck(V))) {
|
||||
const err = new TypeError('Illegal invocation')
|
||||
err.code = 'ERR_INVALID_THIS' // node compat.
|
||||
throw err
|
||||
if (prototypes.every((typeCheck) => !typeCheck(V))) {
|
||||
const err = new TypeError('Illegal invocation');
|
||||
err.code = 'ERR_INVALID_THIS'; // node compat.
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
webidl.argumentLengthCheck = function ({ length }, min, ctx) {
|
||||
if (length < min) {
|
||||
throw webidl.errors.exception({
|
||||
message: `${min} argument${min !== 1 ? 's' : ''} required, ` +
|
||||
`but${length ? ' only' : ''} ${length} found.`,
|
||||
header: ctx
|
||||
})
|
||||
message:
|
||||
`${min} argument${min !== 1 ? 's' : ''} required, ` +
|
||||
`but${length ? ' only' : ''} ${length} found.`,
|
||||
header: ctx,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
webidl.illegalConstructor = function () {
|
||||
throw webidl.errors.exception({
|
||||
header: 'TypeError',
|
||||
message: 'Illegal constructor'
|
||||
})
|
||||
}
|
||||
message: 'Illegal constructor',
|
||||
});
|
||||
};
|
||||
|
||||
webidl.util.MakeTypeAssertion = function (I) {
|
||||
return (O) => FunctionPrototypeSymbolHasInstance(I, O)
|
||||
}
|
||||
return (O) => FunctionPrototypeSymbolHasInstance(I, O);
|
||||
};
|
||||
|
||||
// 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 '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 NULL;
|
||||
}
|
||||
|
||||
return OBJECT
|
||||
return OBJECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
webidl.util.Types = {
|
||||
UNDEFINED,
|
||||
@ -116,65 +125,73 @@ webidl.util.Types = {
|
||||
NUMBER,
|
||||
BIGINT,
|
||||
NULL,
|
||||
OBJECT
|
||||
}
|
||||
OBJECT,
|
||||
};
|
||||
|
||||
webidl.util.TypeValueToString = function (o) {
|
||||
switch (webidl.util.Type(o)) {
|
||||
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 NULL: return 'Null'
|
||||
case OBJECT: return 'Object'
|
||||
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 NULL:
|
||||
return 'Null';
|
||||
case OBJECT:
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
|
||||
webidl.util.markAsUncloneable = markAsUncloneable || (() => {});
|
||||
|
||||
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
||||
webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
||||
let upperBound
|
||||
let lowerBound
|
||||
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
|
||||
upperBound = Math.pow(2, 53) - 1;
|
||||
|
||||
// 2. If signedness is "unsigned", then let lowerBound be 0.
|
||||
if (signedness === 'unsigned') {
|
||||
lowerBound = 0
|
||||
lowerBound = 0;
|
||||
} else {
|
||||
// 3. Otherwise let lowerBound be −2^53 + 1.
|
||||
lowerBound = Math.pow(-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
|
||||
lowerBound = 0;
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1.
|
||||
upperBound = Math.pow(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
|
||||
lowerBound = Math.pow(-2, bitLength) - 1;
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1 − 1.
|
||||
upperBound = Math.pow(2, bitLength - 1) - 1
|
||||
upperBound = Math.pow(2, bitLength - 1) - 1;
|
||||
}
|
||||
|
||||
// 4. Let x be ? ToNumber(V).
|
||||
let x = Number(V)
|
||||
let x = Number(V);
|
||||
|
||||
// 5. If x is −0, then set x to +0.
|
||||
if (x === 0) {
|
||||
x = 0
|
||||
x = 0;
|
||||
}
|
||||
|
||||
// 6. If the conversion is to an IDL type associated
|
||||
@ -188,24 +205,24 @@ webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
||||
) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'Integer conversion',
|
||||
message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`
|
||||
})
|
||||
message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Set x to IntegerPart(x).
|
||||
x = webidl.util.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}.`
|
||||
})
|
||||
message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Return x.
|
||||
return x
|
||||
return x;
|
||||
}
|
||||
|
||||
// 7. If x is not NaN and the conversion is to an IDL
|
||||
@ -213,19 +230,19 @@ webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
||||
// 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)
|
||||
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)
|
||||
x = Math.floor(x);
|
||||
} else {
|
||||
x = Math.ceil(x)
|
||||
x = Math.ceil(x);
|
||||
}
|
||||
|
||||
// 3. Return x.
|
||||
return x
|
||||
return x;
|
||||
}
|
||||
|
||||
// 8. If x is NaN, +0, +∞, or −∞, then return +0.
|
||||
@ -235,53 +252,53 @@ webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
||||
x === Number.POSITIVE_INFINITY ||
|
||||
x === Number.NEGATIVE_INFINITY
|
||||
) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 9. Set x to IntegerPart(x).
|
||||
x = webidl.util.IntegerPart(x)
|
||||
x = webidl.util.IntegerPart(x);
|
||||
|
||||
// 10. Set x to x modulo 2^bitLength.
|
||||
x = x % Math.pow(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)
|
||||
return x - Math.pow(2, bitLength);
|
||||
}
|
||||
|
||||
// 12. Otherwise, return x.
|
||||
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))
|
||||
const r = Math.floor(Math.abs(n));
|
||||
|
||||
// 2. If n < 0, then return -1 × r.
|
||||
if (n < 0) {
|
||||
return -1 * r
|
||||
return -1 * r;
|
||||
}
|
||||
|
||||
// 3. Otherwise, return r.
|
||||
return r
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
webidl.util.Stringify = function (V) {
|
||||
const type = webidl.util.Type(V)
|
||||
const type = webidl.util.Type(V);
|
||||
|
||||
switch (type) {
|
||||
case SYMBOL:
|
||||
return `Symbol(${V.description})`
|
||||
return `Symbol(${V.description})`;
|
||||
case OBJECT:
|
||||
return inspect(V)
|
||||
return inspect(V);
|
||||
case STRING:
|
||||
return `"${V}"`
|
||||
return `"${V}"`;
|
||||
default:
|
||||
return `${V}`
|
||||
return `${V}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-sequence
|
||||
webidl.sequenceConverter = function (converter) {
|
||||
@ -290,41 +307,39 @@ webidl.sequenceConverter = function (converter) {
|
||||
if (webidl.util.Type(V) !== OBJECT) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`
|
||||
})
|
||||
message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Let method be ? GetMethod(V, @@iterator).
|
||||
/** @type {Generator} */
|
||||
const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.()
|
||||
const seq = []
|
||||
let index = 0
|
||||
const method =
|
||||
typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.();
|
||||
const seq = [];
|
||||
let index = 0;
|
||||
|
||||
// 3. If method is undefined, throw a TypeError.
|
||||
if (
|
||||
method === undefined ||
|
||||
typeof method.next !== 'function'
|
||||
) {
|
||||
if (method === undefined || typeof method.next !== 'function') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} is not iterable.`
|
||||
})
|
||||
message: `${argument} is not iterable.`,
|
||||
});
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
|
||||
while (true) {
|
||||
const { done, value } = method.next()
|
||||
const { done, value } = method.next();
|
||||
|
||||
if (done) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
seq.push(converter(value, prefix, `${argument}[${index++}]`))
|
||||
seq.push(converter(value, prefix, `${argument}[${index++}]`));
|
||||
}
|
||||
|
||||
return seq
|
||||
}
|
||||
}
|
||||
return seq;
|
||||
};
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-to-record
|
||||
webidl.recordConverter = function (keyConverter, valueConverter) {
|
||||
@ -333,148 +348,158 @@ webidl.recordConverter = function (keyConverter, valueConverter) {
|
||||
if (webidl.util.Type(O) !== OBJECT) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} ("${webidl.util.TypeValueToString(O)}") is not an Object.`
|
||||
})
|
||||
message: `${argument} ("${webidl.util.TypeValueToString(O)}") is not an Object.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Let result be a new empty instance of record<K, V>.
|
||||
const result = {}
|
||||
const result = {};
|
||||
|
||||
if (!types.isProxy(O)) {
|
||||
// 1. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]
|
||||
const keys = [
|
||||
...Object.getOwnPropertyNames(O),
|
||||
...Object.getOwnPropertySymbols(O),
|
||||
];
|
||||
|
||||
for (const key of keys) {
|
||||
const keyName = webidl.util.Stringify(key)
|
||||
const keyName = webidl.util.Stringify(key);
|
||||
|
||||
// 1. Let typedKey be key converted to an IDL value of type K.
|
||||
const typedKey = keyConverter(key, prefix, `Key ${keyName} in ${argument}`)
|
||||
const typedKey = keyConverter(
|
||||
key,
|
||||
prefix,
|
||||
`Key ${keyName} in ${argument}`
|
||||
);
|
||||
|
||||
// 2. Let value be ? Get(O, key).
|
||||
// 3. Let typedValue be value converted to an IDL value of type V.
|
||||
const typedValue = valueConverter(O[key], prefix, `${argument}[${keyName}]`)
|
||||
const typedValue = valueConverter(
|
||||
O[key],
|
||||
prefix,
|
||||
`${argument}[${keyName}]`
|
||||
);
|
||||
|
||||
// 4. Set result[typedKey] to typedValue.
|
||||
result[typedKey] = typedValue
|
||||
result[typedKey] = typedValue;
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
// 3. Let keys be ? O.[[OwnPropertyKeys]]().
|
||||
const keys = Reflect.ownKeys(O)
|
||||
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)
|
||||
const desc = Reflect.getOwnPropertyDescriptor(O, key);
|
||||
|
||||
// 2. If desc is not undefined and desc.[[Enumerable]] is true:
|
||||
if (desc?.enumerable) {
|
||||
// 1. Let typedKey be key converted to an IDL value of type K.
|
||||
const typedKey = keyConverter(key, prefix, argument)
|
||||
const typedKey = keyConverter(key, prefix, argument);
|
||||
|
||||
// 2. Let value be ? Get(O, key).
|
||||
// 3. Let typedValue be value converted to an IDL value of type V.
|
||||
const typedValue = valueConverter(O[key], prefix, argument)
|
||||
const typedValue = valueConverter(O[key], prefix, argument);
|
||||
|
||||
// 4. Set result[typedKey] to typedValue.
|
||||
result[typedKey] = typedValue
|
||||
result[typedKey] = typedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
return result
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
webidl.interfaceConverter = function (TypeCheck, name) {
|
||||
return (V, prefix, argument) => {
|
||||
if (!TypeCheck(V)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${name}.`
|
||||
})
|
||||
message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${name}.`,
|
||||
});
|
||||
}
|
||||
|
||||
return V
|
||||
}
|
||||
}
|
||||
return V;
|
||||
};
|
||||
};
|
||||
|
||||
webidl.dictionaryConverter = function (converters) {
|
||||
return (dictionary, prefix, argument) => {
|
||||
const dict = {}
|
||||
const dict = {};
|
||||
|
||||
if (dictionary != null && webidl.util.Type(dictionary) !== OBJECT) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`
|
||||
})
|
||||
message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`,
|
||||
});
|
||||
}
|
||||
|
||||
for (const options of converters) {
|
||||
const { key, defaultValue, required, converter } = options
|
||||
const { key, defaultValue, required, converter } = options;
|
||||
|
||||
if (required === true) {
|
||||
if (dictionary == null || !Object.hasOwn(dictionary, key)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `Missing required key "${key}".`
|
||||
})
|
||||
message: `Missing required key "${key}".`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let value = dictionary?.[key]
|
||||
const hasDefault = defaultValue !== undefined
|
||||
let value = dictionary?.[key];
|
||||
const hasDefault = defaultValue !== undefined;
|
||||
|
||||
// Only use defaultValue if value is undefined and
|
||||
// a defaultValue options was provided.
|
||||
if (hasDefault && value === undefined) {
|
||||
value = defaultValue()
|
||||
value = defaultValue();
|
||||
}
|
||||
|
||||
// A key can be optional and have no default value.
|
||||
// When this happens, do not perform a conversion,
|
||||
// and do not assign the key a value.
|
||||
if (required || hasDefault || value !== undefined) {
|
||||
value = converter(value, prefix, `${argument}.${key}`)
|
||||
value = converter(value, prefix, `${argument}.${key}`);
|
||||
|
||||
if (
|
||||
options.allowedValues &&
|
||||
!options.allowedValues.includes(value)
|
||||
) {
|
||||
if (options.allowedValues && !options.allowedValues.includes(value)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`
|
||||
})
|
||||
message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`,
|
||||
});
|
||||
}
|
||||
|
||||
dict[key] = value
|
||||
dict[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
};
|
||||
};
|
||||
|
||||
webidl.nullableConverter = function (converter) {
|
||||
return (V, prefix, argument) => {
|
||||
if (V === null) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
return converter(V, prefix, argument)
|
||||
}
|
||||
}
|
||||
return converter(V, prefix, argument);
|
||||
};
|
||||
};
|
||||
|
||||
webidl.is.ReadableStream = webidl.util.MakeTypeAssertion(ReadableStream)
|
||||
webidl.is.Blob = webidl.util.MakeTypeAssertion(Blob)
|
||||
webidl.is.URLSearchParams = webidl.util.MakeTypeAssertion(URLSearchParams)
|
||||
webidl.is.File = webidl.util.MakeTypeAssertion(globalThis.File ?? require('node:buffer').File)
|
||||
webidl.is.URL = webidl.util.MakeTypeAssertion(URL)
|
||||
webidl.is.AbortSignal = webidl.util.MakeTypeAssertion(AbortSignal)
|
||||
webidl.is.MessagePort = webidl.util.MakeTypeAssertion(MessagePort)
|
||||
webidl.is.ReadableStream = webidl.util.MakeTypeAssertion(ReadableStream);
|
||||
webidl.is.Blob = webidl.util.MakeTypeAssertion(Blob);
|
||||
webidl.is.URLSearchParams = webidl.util.MakeTypeAssertion(URLSearchParams);
|
||||
webidl.is.File = webidl.util.MakeTypeAssertion(
|
||||
globalThis.File ?? require('node:buffer').File
|
||||
);
|
||||
webidl.is.URL = webidl.util.MakeTypeAssertion(URL);
|
||||
webidl.is.AbortSignal = webidl.util.MakeTypeAssertion(AbortSignal);
|
||||
webidl.is.MessagePort = webidl.util.MakeTypeAssertion(MessagePort);
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-DOMString
|
||||
webidl.converters.DOMString = function (V, prefix, argument, opts) {
|
||||
@ -483,22 +508,22 @@ webidl.converters.DOMString = function (V, prefix, argument, opts) {
|
||||
// extended attribute, then return the DOMString value
|
||||
// that represents the empty string.
|
||||
if (V === null && opts?.legacyNullToEmptyString) {
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
// 2. Let x be ? ToString(V).
|
||||
if (typeof V === 'symbol') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} is a symbol, which cannot be converted to a DOMString.`
|
||||
})
|
||||
message: `${argument} is a symbol, which cannot be converted to a DOMString.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Return the IDL DOMString value that represents the
|
||||
// same sequence of code units as the one the
|
||||
// ECMAScript String value x represents.
|
||||
return String(V)
|
||||
}
|
||||
return String(V);
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-ByteString
|
||||
webidl.converters.ByteString = function (V, prefix, argument) {
|
||||
@ -506,11 +531,11 @@ webidl.converters.ByteString = function (V, prefix, argument) {
|
||||
if (typeof V === 'symbol') {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${argument} is a symbol, which cannot be converted to a ByteString.`
|
||||
})
|
||||
message: `${argument} is a symbol, which cannot be converted to a ByteString.`,
|
||||
});
|
||||
}
|
||||
|
||||
const x = String(V)
|
||||
const x = String(V);
|
||||
|
||||
// 2. If the value of any element of x is greater than
|
||||
// 255, then throw a TypeError.
|
||||
@ -518,75 +543,96 @@ webidl.converters.ByteString = function (V, prefix, argument) {
|
||||
if (x.charCodeAt(index) > 255) {
|
||||
throw new TypeError(
|
||||
'Cannot convert argument to a ByteString because the character at ' +
|
||||
`index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
|
||||
)
|
||||
`index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return an IDL ByteString value whose length is the
|
||||
// length of x, and where the value of each element is
|
||||
// the value of the corresponding element of x.
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-USVString
|
||||
// TODO: rewrite this so we can control the errors thrown
|
||||
webidl.converters.USVString = toUSVString
|
||||
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)
|
||||
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
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-any
|
||||
webidl.converters.any = function (V) {
|
||||
return V
|
||||
}
|
||||
return V;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-long-long
|
||||
webidl.converters['long long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "signed").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'signed', undefined, prefix, argument)
|
||||
const x = webidl.util.ConvertToInt(
|
||||
V,
|
||||
64,
|
||||
'signed',
|
||||
undefined,
|
||||
prefix,
|
||||
argument
|
||||
);
|
||||
|
||||
// 2. Return the IDL long long value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-long-long
|
||||
webidl.converters['unsigned long long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'unsigned', undefined, prefix, argument)
|
||||
const x = webidl.util.ConvertToInt(
|
||||
V,
|
||||
64,
|
||||
'unsigned',
|
||||
undefined,
|
||||
prefix,
|
||||
argument
|
||||
);
|
||||
|
||||
// 2. Return the IDL unsigned long long value that
|
||||
// represents the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-long
|
||||
webidl.converters['unsigned long'] = function (V, prefix, argument) {
|
||||
// 1. Let x be ? ConvertToInt(V, 32, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 32, 'unsigned', undefined, prefix, argument)
|
||||
const x = webidl.util.ConvertToInt(
|
||||
V,
|
||||
32,
|
||||
'unsigned',
|
||||
undefined,
|
||||
prefix,
|
||||
argument
|
||||
);
|
||||
|
||||
// 2. Return the IDL unsigned long value that
|
||||
// represents the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-short
|
||||
webidl.converters['unsigned short'] = function (V, prefix, argument, opts) {
|
||||
// 1. Let x be ? ConvertToInt(V, 16, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts, prefix, argument)
|
||||
const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts, prefix, argument);
|
||||
|
||||
// 2. Return the IDL unsigned short value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
// https://webidl.spec.whatwg.org/#idl-ArrayBuffer
|
||||
webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
||||
@ -595,15 +641,12 @@ webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
||||
// 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)
|
||||
) {
|
||||
if (webidl.util.Type(V) !== OBJECT || !types.isAnyArrayBuffer(V)) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix,
|
||||
argument: `${argument} ("${webidl.util.Stringify(V)}")`,
|
||||
types: ['ArrayBuffer']
|
||||
})
|
||||
types: ['ArrayBuffer'],
|
||||
});
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
@ -613,8 +656,8 @@ webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
message: 'SharedArrayBuffer is not allowed.',
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
@ -624,14 +667,14 @@ webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
||||
if (V.resizable || V.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
message: 'Received a resizable ArrayBuffer.',
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Return the IDL ArrayBuffer value that is a
|
||||
// reference to the same object as V.
|
||||
return V
|
||||
}
|
||||
return V;
|
||||
};
|
||||
|
||||
webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
||||
// 1. Let T be the IDL type V is being converted to.
|
||||
@ -647,8 +690,8 @@ webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
||||
throw webidl.errors.conversionFailed({
|
||||
prefix,
|
||||
argument: `${name} ("${webidl.util.Stringify(V)}")`,
|
||||
types: [T.name]
|
||||
})
|
||||
types: [T.name],
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
@ -658,8 +701,8 @@ webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
message: 'SharedArrayBuffer is not allowed.',
|
||||
});
|
||||
}
|
||||
|
||||
// 4. If the conversion is not to an IDL type associated
|
||||
@ -669,14 +712,14 @@ webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
||||
if (V.buffer.resizable || V.buffer.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
message: 'Received a resizable ArrayBuffer.',
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Return the IDL value of type T that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
return V;
|
||||
};
|
||||
|
||||
webidl.converters.DataView = function (V, prefix, name, opts) {
|
||||
// 1. If Type(V) is not Object, or V does not have a
|
||||
@ -684,8 +727,8 @@ webidl.converters.DataView = function (V, prefix, name, opts) {
|
||||
if (webidl.util.Type(V) !== OBJECT || !types.isDataView(V)) {
|
||||
throw webidl.errors.exception({
|
||||
header: prefix,
|
||||
message: `${name} is not a DataView.`
|
||||
})
|
||||
message: `${name} is not a DataView.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
@ -695,8 +738,8 @@ webidl.converters.DataView = function (V, prefix, name, opts) {
|
||||
if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
message: 'SharedArrayBuffer is not allowed.',
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
@ -706,35 +749,35 @@ webidl.converters.DataView = function (V, prefix, name, opts) {
|
||||
if (V.buffer.resizable || V.buffer.growable) {
|
||||
throw webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'Received a resizable ArrayBuffer.'
|
||||
})
|
||||
message: 'Received a resizable ArrayBuffer.',
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Return the IDL DataView value that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
return V;
|
||||
};
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
webidl.converters.Blob = webidl.interfaceConverter(webidl.is.Blob, 'Blob')
|
||||
webidl.converters.Blob = webidl.interfaceConverter(webidl.is.Blob, 'Blob');
|
||||
|
||||
webidl.converters.AbortSignal = webidl.interfaceConverter(
|
||||
webidl.is.AbortSignal,
|
||||
'AbortSignal'
|
||||
)
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
webidl
|
||||
}
|
||||
webidl,
|
||||
};
|
||||
|
242
node_modules/undici/lib/web/websocket/connection.js
generated
vendored
242
node_modules/undici/lib/web/websocket/connection.js
generated
vendored
@ -1,23 +1,33 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
||||
const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
|
||||
const { channels } = require('../../core/diagnostics')
|
||||
const { makeRequest } = require('../fetch/request')
|
||||
const { fetching } = require('../fetch/index')
|
||||
const { Headers, getHeadersList } = require('../fetch/headers')
|
||||
const { getDecodeSplit } = require('../fetch/util')
|
||||
const { WebsocketFrameSend } = require('./frame')
|
||||
const assert = require('node:assert')
|
||||
const {
|
||||
uid,
|
||||
states,
|
||||
sentCloseFrameState,
|
||||
emptyBuffer,
|
||||
opcodes,
|
||||
} = require('./constants');
|
||||
const {
|
||||
parseExtensions,
|
||||
isClosed,
|
||||
isClosing,
|
||||
isEstablished,
|
||||
validateCloseCodeAndReason,
|
||||
} = require('./util');
|
||||
const { channels } = require('../../core/diagnostics');
|
||||
const { makeRequest } = require('../fetch/request');
|
||||
const { fetching } = require('../fetch/index');
|
||||
const { Headers, getHeadersList } = require('../fetch/headers');
|
||||
const { getDecodeSplit } = require('../fetch/util');
|
||||
const { WebsocketFrameSend } = require('./frame');
|
||||
const assert = require('node:assert');
|
||||
|
||||
/** @type {import('crypto')} */
|
||||
let crypto
|
||||
let crypto;
|
||||
try {
|
||||
crypto = require('node:crypto')
|
||||
/* c8 ignore next 3 */
|
||||
} catch {
|
||||
|
||||
}
|
||||
crypto = require('node:crypto');
|
||||
/* c8 ignore next 3 */
|
||||
} catch {}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#concept-websocket-establish
|
||||
@ -26,12 +36,18 @@ try {
|
||||
* @param {import('./websocket').Handler} handler
|
||||
* @param {Partial<import('../../../types/websocket').WebSocketInit>} options
|
||||
*/
|
||||
function establishWebSocketConnection (url, protocols, client, handler, options) {
|
||||
function establishWebSocketConnection(
|
||||
url,
|
||||
protocols,
|
||||
client,
|
||||
handler,
|
||||
options
|
||||
) {
|
||||
// 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s
|
||||
// scheme is "ws", and to "https" otherwise.
|
||||
const requestURL = url
|
||||
const requestURL = url;
|
||||
|
||||
requestURL.protocol = url.protocol === 'ws:' ? 'http:' : 'https:'
|
||||
requestURL.protocol = url.protocol === 'ws:' ? 'http:' : 'https:';
|
||||
|
||||
// 2. Let request be a new request, whose URL is requestURL, client is client,
|
||||
// service-workers mode is "none", referrer is "no-referrer", mode is
|
||||
@ -45,14 +61,14 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
mode: 'websocket',
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
redirect: 'error'
|
||||
})
|
||||
redirect: 'error',
|
||||
});
|
||||
|
||||
// Note: undici extension, allow setting custom headers.
|
||||
if (options.headers) {
|
||||
const headersList = getHeadersList(new Headers(options.headers))
|
||||
const headersList = getHeadersList(new Headers(options.headers));
|
||||
|
||||
request.headersList = headersList
|
||||
request.headersList = headersList;
|
||||
}
|
||||
|
||||
// 3. Append (`Upgrade`, `websocket`) to request’s header list.
|
||||
@ -63,31 +79,35 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// 5. Let keyValue be a nonce consisting of a randomly selected
|
||||
// 16-byte value that has been forgiving-base64-encoded and
|
||||
// isomorphic encoded.
|
||||
const keyValue = crypto.randomBytes(16).toString('base64')
|
||||
const keyValue = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
// 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s
|
||||
// header list.
|
||||
request.headersList.append('sec-websocket-key', keyValue, true)
|
||||
request.headersList.append('sec-websocket-key', keyValue, true);
|
||||
|
||||
// 7. Append (`Sec-WebSocket-Version`, `13`) to request’s
|
||||
// header list.
|
||||
request.headersList.append('sec-websocket-version', '13', true)
|
||||
request.headersList.append('sec-websocket-version', '13', true);
|
||||
|
||||
// 8. For each protocol in protocols, combine
|
||||
// (`Sec-WebSocket-Protocol`, protocol) in request’s header
|
||||
// list.
|
||||
for (const protocol of protocols) {
|
||||
request.headersList.append('sec-websocket-protocol', protocol, true)
|
||||
request.headersList.append('sec-websocket-protocol', protocol, true);
|
||||
}
|
||||
|
||||
// 9. Let permessageDeflate be a user-agent defined
|
||||
// "permessage-deflate" extension header value.
|
||||
// https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673
|
||||
const permessageDeflate = 'permessage-deflate; client_max_window_bits'
|
||||
const permessageDeflate = 'permessage-deflate; client_max_window_bits';
|
||||
|
||||
// 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
|
||||
// request’s header list.
|
||||
request.headersList.append('sec-websocket-extensions', permessageDeflate, true)
|
||||
request.headersList.append(
|
||||
'sec-websocket-extensions',
|
||||
permessageDeflate,
|
||||
true
|
||||
);
|
||||
|
||||
// 11. Fetch request with useParallelQueue set to true, and
|
||||
// processResponse given response being these steps:
|
||||
@ -95,27 +115,38 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
request,
|
||||
useParallelQueue: true,
|
||||
dispatcher: options.dispatcher,
|
||||
processResponse (response) {
|
||||
processResponse(response) {
|
||||
if (response.type === 'error') {
|
||||
// If the WebSocket connection could not be established, it is also said
|
||||
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
||||
handler.readyState = states.CLOSED
|
||||
handler.readyState = states.CLOSED;
|
||||
}
|
||||
|
||||
// 1. If response is a network error or its status is not 101,
|
||||
// fail the WebSocket connection.
|
||||
if (response.type === 'error' || response.status !== 101) {
|
||||
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Received network error or non-101 status code.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If protocols is not the empty list and extracting header
|
||||
// list values given `Sec-WebSocket-Protocol` and response’s
|
||||
// header list results in null, failure, or the empty byte
|
||||
// sequence, then fail the WebSocket connection.
|
||||
if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
|
||||
failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
|
||||
return
|
||||
if (
|
||||
protocols.length !== 0 &&
|
||||
!response.headersList.get('Sec-WebSocket-Protocol')
|
||||
) {
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Server did not respond with sent protocols.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Follow the requirements stated step 2 to step 6, inclusive,
|
||||
@ -129,8 +160,12 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// insensitive match for the value "websocket", the client MUST
|
||||
// _Fail the WebSocket Connection_.
|
||||
if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
||||
failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Server did not set Upgrade header to "websocket".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. If the response lacks a |Connection| header field or the
|
||||
@ -138,8 +173,12 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// ASCII case-insensitive match for the value "Upgrade", the client
|
||||
// MUST _Fail the WebSocket Connection_.
|
||||
if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
|
||||
failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Server did not set Connection header to "upgrade".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. If the response lacks a |Sec-WebSocket-Accept| header field or
|
||||
@ -149,11 +188,18 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
|
||||
// trailing whitespace, the client MUST _Fail the WebSocket
|
||||
// Connection_.
|
||||
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
||||
const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
|
||||
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept');
|
||||
const digest = crypto
|
||||
.createHash('sha1')
|
||||
.update(keyValue + uid)
|
||||
.digest('base64');
|
||||
if (secWSAccept !== digest) {
|
||||
failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Incorrect hash received in Sec-WebSocket-Accept header.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. If the response includes a |Sec-WebSocket-Extensions| header
|
||||
@ -163,15 +209,19 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// MUST _Fail the WebSocket Connection_. (The parsing of this
|
||||
// header field to determine which extensions are requested is
|
||||
// discussed in Section 9.1.)
|
||||
const secExtension = response.headersList.get('Sec-WebSocket-Extensions')
|
||||
let extensions
|
||||
const secExtension = response.headersList.get('Sec-WebSocket-Extensions');
|
||||
let extensions;
|
||||
|
||||
if (secExtension !== null) {
|
||||
extensions = parseExtensions(secExtension)
|
||||
extensions = parseExtensions(secExtension);
|
||||
|
||||
if (!extensions.has('permessage-deflate')) {
|
||||
failWebsocketConnection(handler, 1002, 'Sec-WebSocket-Extensions header does not match.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Sec-WebSocket-Extensions header does not match.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,10 +230,13 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// not present in the client's handshake (the server has indicated a
|
||||
// subprotocol not requested by the client), the client MUST _Fail
|
||||
// the WebSocket Connection_.
|
||||
const secProtocol = response.headersList.get('Sec-WebSocket-Protocol')
|
||||
const secProtocol = response.headersList.get('Sec-WebSocket-Protocol');
|
||||
|
||||
if (secProtocol !== null) {
|
||||
const requestProtocols = getDecodeSplit('sec-websocket-protocol', request.headersList)
|
||||
const requestProtocols = getDecodeSplit(
|
||||
'sec-websocket-protocol',
|
||||
request.headersList
|
||||
);
|
||||
|
||||
// The client can request that the server use a specific subprotocol by
|
||||
// including the |Sec-WebSocket-Protocol| field in its handshake. If it
|
||||
@ -191,29 +244,33 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
// the selected subprotocol values in its response for the connection to
|
||||
// be established.
|
||||
if (!requestProtocols.includes(secProtocol)) {
|
||||
failWebsocketConnection(handler, 1002, 'Protocol was not set in the opening handshake.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
handler,
|
||||
1002,
|
||||
'Protocol was not set in the opening handshake.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
response.socket.on('data', handler.onSocketData)
|
||||
response.socket.on('close', handler.onSocketClose)
|
||||
response.socket.on('error', handler.onSocketError)
|
||||
response.socket.on('data', handler.onSocketData);
|
||||
response.socket.on('close', handler.onSocketClose);
|
||||
response.socket.on('error', handler.onSocketError);
|
||||
|
||||
if (channels.open.hasSubscribers) {
|
||||
channels.open.publish({
|
||||
address: response.socket.address(),
|
||||
protocol: secProtocol,
|
||||
extensions: secExtension
|
||||
})
|
||||
extensions: secExtension,
|
||||
});
|
||||
}
|
||||
|
||||
handler.wasEverConnected = true
|
||||
handler.onConnectionEstablished(response, extensions)
|
||||
}
|
||||
})
|
||||
handler.wasEverConnected = true;
|
||||
handler.onConnectionEstablished(response, extensions);
|
||||
},
|
||||
});
|
||||
|
||||
return controller
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,15 +279,15 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
||||
* @param {number} [code=null]
|
||||
* @param {string} [reason='']
|
||||
*/
|
||||
function closeWebSocketConnection (object, code, reason, validate = false) {
|
||||
function closeWebSocketConnection(object, code, reason, validate = false) {
|
||||
// 1. If code was not supplied, let code be null.
|
||||
code ??= null
|
||||
code ??= null;
|
||||
|
||||
// 2. If reason was not supplied, let reason be the empty string.
|
||||
reason ??= ''
|
||||
reason ??= '';
|
||||
|
||||
// 3. Validate close code and reason with code and reason.
|
||||
if (validate) validateCloseCodeAndReason(code, reason)
|
||||
if (validate) validateCloseCodeAndReason(code, reason);
|
||||
|
||||
// 4. Run the first matching steps from the following list:
|
||||
// - If object’s ready state is CLOSING (2) or CLOSED (3)
|
||||
@ -241,14 +298,17 @@ function closeWebSocketConnection (object, code, reason, validate = false) {
|
||||
// Do nothing.
|
||||
} else if (!isEstablished(object.readyState)) {
|
||||
// Fail the WebSocket connection and set object’s ready state to CLOSING (2). [WSP]
|
||||
failWebsocketConnection(object)
|
||||
object.readyState = states.CLOSING
|
||||
} else if (!object.closeState.has(sentCloseFrameState.SENT) && !object.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
failWebsocketConnection(object);
|
||||
object.readyState = states.CLOSING;
|
||||
} else if (
|
||||
!object.closeState.has(sentCloseFrameState.SENT) &&
|
||||
!object.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
) {
|
||||
// Upon either sending or receiving a Close control frame, it is said
|
||||
// that _The WebSocket Closing Handshake is Started_ and that the
|
||||
// WebSocket connection is in the CLOSING state.
|
||||
|
||||
const frame = new WebsocketFrameSend()
|
||||
const frame = new WebsocketFrameSend();
|
||||
|
||||
// If neither code nor reason is present, the WebSocket Close
|
||||
// message must not have a body.
|
||||
@ -258,39 +318,39 @@ function closeWebSocketConnection (object, code, reason, validate = false) {
|
||||
// If code is null and reason is the empty string, the WebSocket Close frame must not have a body.
|
||||
// If reason is non-empty but code is null, then set code to 1000 ("Normal Closure").
|
||||
if (reason.length !== 0 && code === null) {
|
||||
code = 1000
|
||||
code = 1000;
|
||||
}
|
||||
|
||||
// If code is set, then the status code to use in the WebSocket Close frame must be the integer given by code.
|
||||
assert(code === null || Number.isInteger(code))
|
||||
assert(code === null || Number.isInteger(code));
|
||||
|
||||
if (code === null && reason.length === 0) {
|
||||
frame.frameData = emptyBuffer
|
||||
frame.frameData = emptyBuffer;
|
||||
} else if (code !== null && reason === null) {
|
||||
frame.frameData = Buffer.allocUnsafe(2)
|
||||
frame.frameData.writeUInt16BE(code, 0)
|
||||
frame.frameData = Buffer.allocUnsafe(2);
|
||||
frame.frameData.writeUInt16BE(code, 0);
|
||||
} else if (code !== null && reason !== null) {
|
||||
// If reason is also present, then reasonBytes must be
|
||||
// provided in the Close message after the status code.
|
||||
frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason))
|
||||
frame.frameData.writeUInt16BE(code, 0)
|
||||
frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason));
|
||||
frame.frameData.writeUInt16BE(code, 0);
|
||||
// the body MAY contain UTF-8-encoded data with value /reason/
|
||||
frame.frameData.write(reason, 2, 'utf-8')
|
||||
frame.frameData.write(reason, 2, 'utf-8');
|
||||
} else {
|
||||
frame.frameData = emptyBuffer
|
||||
frame.frameData = emptyBuffer;
|
||||
}
|
||||
|
||||
object.socket.write(frame.createFrame(opcodes.CLOSE))
|
||||
object.socket.write(frame.createFrame(opcodes.CLOSE));
|
||||
|
||||
object.closeState.add(sentCloseFrameState.SENT)
|
||||
object.closeState.add(sentCloseFrameState.SENT);
|
||||
|
||||
// Upon either sending or receiving a Close control frame, it is said
|
||||
// that _The WebSocket Closing Handshake is Started_ and that the
|
||||
// WebSocket connection is in the CLOSING state.
|
||||
object.readyState = states.CLOSING
|
||||
object.readyState = states.CLOSING;
|
||||
} else {
|
||||
// Set object’s ready state to CLOSING (2).
|
||||
object.readyState = states.CLOSING
|
||||
object.readyState = states.CLOSING;
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,26 +360,26 @@ function closeWebSocketConnection (object, code, reason, validate = false) {
|
||||
* @param {string|undefined} reason
|
||||
* @returns {void}
|
||||
*/
|
||||
function failWebsocketConnection (handler, code, reason) {
|
||||
function failWebsocketConnection(handler, code, reason) {
|
||||
// If _The WebSocket Connection is Established_ prior to the point where
|
||||
// the endpoint is required to _Fail the WebSocket Connection_, the
|
||||
// endpoint SHOULD send a Close frame with an appropriate status code
|
||||
// (Section 7.4) before proceeding to _Close the WebSocket Connection_.
|
||||
if (isEstablished(handler.readyState)) {
|
||||
closeWebSocketConnection(handler, code, reason, false)
|
||||
closeWebSocketConnection(handler, code, reason, false);
|
||||
}
|
||||
|
||||
handler.controller.abort()
|
||||
handler.controller.abort();
|
||||
|
||||
if (handler.socket?.destroyed === false) {
|
||||
handler.socket.destroy()
|
||||
handler.socket.destroy();
|
||||
}
|
||||
|
||||
handler.onFail(code, reason)
|
||||
handler.onFail(code, reason);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
establishWebSocketConnection,
|
||||
failWebsocketConnection,
|
||||
closeWebSocketConnection
|
||||
}
|
||||
closeWebSocketConnection,
|
||||
};
|
||||
|
36
node_modules/undici/lib/web/websocket/constants.js
generated
vendored
36
node_modules/undici/lib/web/websocket/constants.js
generated
vendored
@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This is a Globally Unique Identifier unique used to validate that the
|
||||
@ -6,7 +6,7 @@
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3
|
||||
* @type {'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'}
|
||||
*/
|
||||
const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
/**
|
||||
* @type {PropertyDescriptor}
|
||||
@ -14,8 +14,8 @@ const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
const staticPropertyDescriptors = {
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
configurable: false
|
||||
}
|
||||
configurable: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* The states of the WebSocket connection.
|
||||
@ -31,8 +31,8 @@ const states = {
|
||||
CONNECTING: 0,
|
||||
OPEN: 1,
|
||||
CLOSING: 2,
|
||||
CLOSED: 3
|
||||
}
|
||||
CLOSED: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
@ -43,8 +43,8 @@ const states = {
|
||||
*/
|
||||
const sentCloseFrameState = {
|
||||
SENT: 1,
|
||||
RECEIVED: 2
|
||||
}
|
||||
RECEIVED: 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* The WebSocket opcodes.
|
||||
@ -65,15 +65,15 @@ const opcodes = {
|
||||
BINARY: 0x2,
|
||||
CLOSE: 0x8,
|
||||
PING: 0x9,
|
||||
PONG: 0xA
|
||||
}
|
||||
PONG: 0xa,
|
||||
};
|
||||
|
||||
/**
|
||||
* The maximum value for an unsigned 16-bit integer.
|
||||
*
|
||||
* @type {65535} 2 ** 16 - 1
|
||||
*/
|
||||
const maxUnsigned16Bit = 65535
|
||||
const maxUnsigned16Bit = 65535;
|
||||
|
||||
/**
|
||||
* The states of the parser.
|
||||
@ -89,15 +89,15 @@ const parserStates = {
|
||||
INFO: 0,
|
||||
PAYLOADLENGTH_16: 2,
|
||||
PAYLOADLENGTH_64: 3,
|
||||
READ_DATA: 4
|
||||
}
|
||||
READ_DATA: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* An empty buffer.
|
||||
*
|
||||
* @type {Buffer}
|
||||
*/
|
||||
const emptyBuffer = Buffer.allocUnsafe(0)
|
||||
const emptyBuffer = Buffer.allocUnsafe(0);
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
@ -110,8 +110,8 @@ const sendHints = {
|
||||
text: 1,
|
||||
typedArray: 2,
|
||||
arrayBuffer: 3,
|
||||
blob: 4
|
||||
}
|
||||
blob: 4,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
uid,
|
||||
@ -122,5 +122,5 @@ module.exports = {
|
||||
maxUnsigned16Bit,
|
||||
parserStates,
|
||||
emptyBuffer,
|
||||
sendHints
|
||||
}
|
||||
sendHints,
|
||||
};
|
||||
|
264
node_modules/undici/lib/web/websocket/events.js
generated
vendored
264
node_modules/undici/lib/web/websocket/events.js
generated
vendored
@ -1,69 +1,73 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { kConstruct } = require('../../core/symbols')
|
||||
const { webidl } = require('../fetch/webidl');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const { kConstruct } = require('../../core/symbols');
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/comms.html#messageevent
|
||||
*/
|
||||
class MessageEvent extends Event {
|
||||
#eventInit
|
||||
#eventInit;
|
||||
|
||||
constructor (type, eventInitDict = {}) {
|
||||
constructor(type, eventInitDict = {}) {
|
||||
if (type === kConstruct) {
|
||||
super(arguments[1], arguments[2])
|
||||
webidl.util.markAsUncloneable(this)
|
||||
return
|
||||
super(arguments[1], arguments[2]);
|
||||
webidl.util.markAsUncloneable(this);
|
||||
return;
|
||||
}
|
||||
|
||||
const prefix = 'MessageEvent constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'MessageEvent constructor';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
type = webidl.converters.DOMString(type, prefix, 'type')
|
||||
eventInitDict = webidl.converters.MessageEventInit(eventInitDict, prefix, 'eventInitDict')
|
||||
type = webidl.converters.DOMString(type, prefix, 'type');
|
||||
eventInitDict = webidl.converters.MessageEventInit(
|
||||
eventInitDict,
|
||||
prefix,
|
||||
'eventInitDict'
|
||||
);
|
||||
|
||||
super(type, eventInitDict)
|
||||
super(type, eventInitDict);
|
||||
|
||||
this.#eventInit = eventInitDict
|
||||
webidl.util.markAsUncloneable(this)
|
||||
this.#eventInit = eventInitDict;
|
||||
webidl.util.markAsUncloneable(this);
|
||||
}
|
||||
|
||||
get data () {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
get data() {
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
return this.#eventInit.data
|
||||
return this.#eventInit.data;
|
||||
}
|
||||
|
||||
get origin () {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
get origin() {
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
return this.#eventInit.origin
|
||||
return this.#eventInit.origin;
|
||||
}
|
||||
|
||||
get lastEventId () {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
get lastEventId() {
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
return this.#eventInit.lastEventId
|
||||
return this.#eventInit.lastEventId;
|
||||
}
|
||||
|
||||
get source () {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
get source() {
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
return this.#eventInit.source
|
||||
return this.#eventInit.source;
|
||||
}
|
||||
|
||||
get ports () {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
get ports() {
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
if (!Object.isFrozen(this.#eventInit.ports)) {
|
||||
Object.freeze(this.#eventInit.ports)
|
||||
Object.freeze(this.#eventInit.ports);
|
||||
}
|
||||
|
||||
return this.#eventInit.ports
|
||||
return this.#eventInit.ports;
|
||||
}
|
||||
|
||||
initMessageEvent (
|
||||
initMessageEvent(
|
||||
type,
|
||||
bubbles = false,
|
||||
cancelable = false,
|
||||
@ -73,259 +77,265 @@ class MessageEvent extends Event {
|
||||
source = null,
|
||||
ports = []
|
||||
) {
|
||||
webidl.brandCheck(this, MessageEvent)
|
||||
webidl.brandCheck(this, MessageEvent);
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, 'MessageEvent.initMessageEvent')
|
||||
webidl.argumentLengthCheck(arguments, 1, 'MessageEvent.initMessageEvent');
|
||||
|
||||
return new MessageEvent(type, {
|
||||
bubbles, cancelable, data, origin, lastEventId, source, ports
|
||||
})
|
||||
bubbles,
|
||||
cancelable,
|
||||
data,
|
||||
origin,
|
||||
lastEventId,
|
||||
source,
|
||||
ports,
|
||||
});
|
||||
}
|
||||
|
||||
static createFastMessageEvent (type, init) {
|
||||
const messageEvent = new MessageEvent(kConstruct, type, init)
|
||||
messageEvent.#eventInit = init
|
||||
messageEvent.#eventInit.data ??= null
|
||||
messageEvent.#eventInit.origin ??= ''
|
||||
messageEvent.#eventInit.lastEventId ??= ''
|
||||
messageEvent.#eventInit.source ??= null
|
||||
messageEvent.#eventInit.ports ??= []
|
||||
return messageEvent
|
||||
static createFastMessageEvent(type, init) {
|
||||
const messageEvent = new MessageEvent(kConstruct, type, init);
|
||||
messageEvent.#eventInit = init;
|
||||
messageEvent.#eventInit.data ??= null;
|
||||
messageEvent.#eventInit.origin ??= '';
|
||||
messageEvent.#eventInit.lastEventId ??= '';
|
||||
messageEvent.#eventInit.source ??= null;
|
||||
messageEvent.#eventInit.ports ??= [];
|
||||
return messageEvent;
|
||||
}
|
||||
}
|
||||
|
||||
const { createFastMessageEvent } = MessageEvent
|
||||
delete MessageEvent.createFastMessageEvent
|
||||
const { createFastMessageEvent } = MessageEvent;
|
||||
delete MessageEvent.createFastMessageEvent;
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#the-closeevent-interface
|
||||
*/
|
||||
class CloseEvent extends Event {
|
||||
#eventInit
|
||||
#eventInit;
|
||||
|
||||
constructor (type, eventInitDict = {}) {
|
||||
const prefix = 'CloseEvent constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
constructor(type, eventInitDict = {}) {
|
||||
const prefix = 'CloseEvent constructor';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
type = webidl.converters.DOMString(type, prefix, 'type')
|
||||
eventInitDict = webidl.converters.CloseEventInit(eventInitDict)
|
||||
type = webidl.converters.DOMString(type, prefix, 'type');
|
||||
eventInitDict = webidl.converters.CloseEventInit(eventInitDict);
|
||||
|
||||
super(type, eventInitDict)
|
||||
super(type, eventInitDict);
|
||||
|
||||
this.#eventInit = eventInitDict
|
||||
webidl.util.markAsUncloneable(this)
|
||||
this.#eventInit = eventInitDict;
|
||||
webidl.util.markAsUncloneable(this);
|
||||
}
|
||||
|
||||
get wasClean () {
|
||||
webidl.brandCheck(this, CloseEvent)
|
||||
get wasClean() {
|
||||
webidl.brandCheck(this, CloseEvent);
|
||||
|
||||
return this.#eventInit.wasClean
|
||||
return this.#eventInit.wasClean;
|
||||
}
|
||||
|
||||
get code () {
|
||||
webidl.brandCheck(this, CloseEvent)
|
||||
get code() {
|
||||
webidl.brandCheck(this, CloseEvent);
|
||||
|
||||
return this.#eventInit.code
|
||||
return this.#eventInit.code;
|
||||
}
|
||||
|
||||
get reason () {
|
||||
webidl.brandCheck(this, CloseEvent)
|
||||
get reason() {
|
||||
webidl.brandCheck(this, CloseEvent);
|
||||
|
||||
return this.#eventInit.reason
|
||||
return this.#eventInit.reason;
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#the-errorevent-interface
|
||||
class ErrorEvent extends Event {
|
||||
#eventInit
|
||||
#eventInit;
|
||||
|
||||
constructor (type, eventInitDict) {
|
||||
const prefix = 'ErrorEvent constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
constructor(type, eventInitDict) {
|
||||
const prefix = 'ErrorEvent constructor';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
super(type, eventInitDict)
|
||||
webidl.util.markAsUncloneable(this)
|
||||
super(type, eventInitDict);
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
type = webidl.converters.DOMString(type, prefix, 'type')
|
||||
eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {})
|
||||
type = webidl.converters.DOMString(type, prefix, 'type');
|
||||
eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {});
|
||||
|
||||
this.#eventInit = eventInitDict
|
||||
this.#eventInit = eventInitDict;
|
||||
}
|
||||
|
||||
get message () {
|
||||
webidl.brandCheck(this, ErrorEvent)
|
||||
get message() {
|
||||
webidl.brandCheck(this, ErrorEvent);
|
||||
|
||||
return this.#eventInit.message
|
||||
return this.#eventInit.message;
|
||||
}
|
||||
|
||||
get filename () {
|
||||
webidl.brandCheck(this, ErrorEvent)
|
||||
get filename() {
|
||||
webidl.brandCheck(this, ErrorEvent);
|
||||
|
||||
return this.#eventInit.filename
|
||||
return this.#eventInit.filename;
|
||||
}
|
||||
|
||||
get lineno () {
|
||||
webidl.brandCheck(this, ErrorEvent)
|
||||
get lineno() {
|
||||
webidl.brandCheck(this, ErrorEvent);
|
||||
|
||||
return this.#eventInit.lineno
|
||||
return this.#eventInit.lineno;
|
||||
}
|
||||
|
||||
get colno () {
|
||||
webidl.brandCheck(this, ErrorEvent)
|
||||
get colno() {
|
||||
webidl.brandCheck(this, ErrorEvent);
|
||||
|
||||
return this.#eventInit.colno
|
||||
return this.#eventInit.colno;
|
||||
}
|
||||
|
||||
get error () {
|
||||
webidl.brandCheck(this, ErrorEvent)
|
||||
get error() {
|
||||
webidl.brandCheck(this, ErrorEvent);
|
||||
|
||||
return this.#eventInit.error
|
||||
return this.#eventInit.error;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(MessageEvent.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'MessageEvent',
|
||||
configurable: true
|
||||
configurable: true,
|
||||
},
|
||||
data: kEnumerableProperty,
|
||||
origin: kEnumerableProperty,
|
||||
lastEventId: kEnumerableProperty,
|
||||
source: kEnumerableProperty,
|
||||
ports: kEnumerableProperty,
|
||||
initMessageEvent: kEnumerableProperty
|
||||
})
|
||||
initMessageEvent: kEnumerableProperty,
|
||||
});
|
||||
|
||||
Object.defineProperties(CloseEvent.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'CloseEvent',
|
||||
configurable: true
|
||||
configurable: true,
|
||||
},
|
||||
reason: kEnumerableProperty,
|
||||
code: kEnumerableProperty,
|
||||
wasClean: kEnumerableProperty
|
||||
})
|
||||
wasClean: kEnumerableProperty,
|
||||
});
|
||||
|
||||
Object.defineProperties(ErrorEvent.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'ErrorEvent',
|
||||
configurable: true
|
||||
configurable: true,
|
||||
},
|
||||
message: kEnumerableProperty,
|
||||
filename: kEnumerableProperty,
|
||||
lineno: kEnumerableProperty,
|
||||
colno: kEnumerableProperty,
|
||||
error: kEnumerableProperty
|
||||
})
|
||||
error: kEnumerableProperty,
|
||||
});
|
||||
|
||||
webidl.converters.MessagePort = webidl.interfaceConverter(
|
||||
webidl.is.MessagePort,
|
||||
'MessagePort'
|
||||
)
|
||||
);
|
||||
|
||||
webidl.converters['sequence<MessagePort>'] = webidl.sequenceConverter(
|
||||
webidl.converters.MessagePort
|
||||
)
|
||||
);
|
||||
|
||||
const eventInit = [
|
||||
{
|
||||
key: 'bubbles',
|
||||
converter: webidl.converters.boolean,
|
||||
defaultValue: () => false
|
||||
defaultValue: () => false,
|
||||
},
|
||||
{
|
||||
key: 'cancelable',
|
||||
converter: webidl.converters.boolean,
|
||||
defaultValue: () => false
|
||||
defaultValue: () => false,
|
||||
},
|
||||
{
|
||||
key: 'composed',
|
||||
converter: webidl.converters.boolean,
|
||||
defaultValue: () => false
|
||||
}
|
||||
]
|
||||
defaultValue: () => false,
|
||||
},
|
||||
];
|
||||
|
||||
webidl.converters.MessageEventInit = webidl.dictionaryConverter([
|
||||
...eventInit,
|
||||
{
|
||||
key: 'data',
|
||||
converter: webidl.converters.any,
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
key: 'origin',
|
||||
converter: webidl.converters.USVString,
|
||||
defaultValue: () => ''
|
||||
defaultValue: () => '',
|
||||
},
|
||||
{
|
||||
key: 'lastEventId',
|
||||
converter: webidl.converters.DOMString,
|
||||
defaultValue: () => ''
|
||||
defaultValue: () => '',
|
||||
},
|
||||
{
|
||||
key: 'source',
|
||||
// Node doesn't implement WindowProxy or ServiceWorker, so the only
|
||||
// valid value for source is a MessagePort.
|
||||
converter: webidl.nullableConverter(webidl.converters.MessagePort),
|
||||
defaultValue: () => null
|
||||
defaultValue: () => null,
|
||||
},
|
||||
{
|
||||
key: 'ports',
|
||||
converter: webidl.converters['sequence<MessagePort>'],
|
||||
defaultValue: () => new Array(0)
|
||||
}
|
||||
])
|
||||
defaultValue: () => new Array(0),
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.converters.CloseEventInit = webidl.dictionaryConverter([
|
||||
...eventInit,
|
||||
{
|
||||
key: 'wasClean',
|
||||
converter: webidl.converters.boolean,
|
||||
defaultValue: () => false
|
||||
defaultValue: () => false,
|
||||
},
|
||||
{
|
||||
key: 'code',
|
||||
converter: webidl.converters['unsigned short'],
|
||||
defaultValue: () => 0
|
||||
defaultValue: () => 0,
|
||||
},
|
||||
{
|
||||
key: 'reason',
|
||||
converter: webidl.converters.USVString,
|
||||
defaultValue: () => ''
|
||||
}
|
||||
])
|
||||
defaultValue: () => '',
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.converters.ErrorEventInit = webidl.dictionaryConverter([
|
||||
...eventInit,
|
||||
{
|
||||
key: 'message',
|
||||
converter: webidl.converters.DOMString,
|
||||
defaultValue: () => ''
|
||||
defaultValue: () => '',
|
||||
},
|
||||
{
|
||||
key: 'filename',
|
||||
converter: webidl.converters.USVString,
|
||||
defaultValue: () => ''
|
||||
defaultValue: () => '',
|
||||
},
|
||||
{
|
||||
key: 'lineno',
|
||||
converter: webidl.converters['unsigned long'],
|
||||
defaultValue: () => 0
|
||||
defaultValue: () => 0,
|
||||
},
|
||||
{
|
||||
key: 'colno',
|
||||
converter: webidl.converters['unsigned long'],
|
||||
defaultValue: () => 0
|
||||
defaultValue: () => 0,
|
||||
},
|
||||
{
|
||||
key: 'error',
|
||||
converter: webidl.converters.any
|
||||
}
|
||||
])
|
||||
converter: webidl.converters.any,
|
||||
},
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
MessageEvent,
|
||||
CloseEvent,
|
||||
ErrorEvent,
|
||||
createFastMessageEvent
|
||||
}
|
||||
createFastMessageEvent,
|
||||
};
|
||||
|
143
node_modules/undici/lib/web/websocket/frame.js
generated
vendored
143
node_modules/undici/lib/web/websocket/frame.js
generated
vendored
@ -1,138 +1,147 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { maxUnsigned16Bit, opcodes } = require('./constants')
|
||||
const { maxUnsigned16Bit, opcodes } = require('./constants');
|
||||
|
||||
const BUFFER_SIZE = 8 * 1024
|
||||
const BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
/** @type {import('crypto')} */
|
||||
let crypto
|
||||
let buffer = null
|
||||
let bufIdx = BUFFER_SIZE
|
||||
let crypto;
|
||||
let buffer = null;
|
||||
let bufIdx = BUFFER_SIZE;
|
||||
|
||||
try {
|
||||
crypto = require('node:crypto')
|
||||
/* c8 ignore next 3 */
|
||||
crypto = require('node:crypto');
|
||||
/* c8 ignore next 3 */
|
||||
} catch {
|
||||
crypto = {
|
||||
// not full compatibility, but minimum.
|
||||
randomFillSync: function randomFillSync (buffer, _offset, _size) {
|
||||
randomFillSync: function randomFillSync(buffer, _offset, _size) {
|
||||
for (let i = 0; i < buffer.length; ++i) {
|
||||
buffer[i] = Math.random() * 255 | 0
|
||||
buffer[i] = (Math.random() * 255) | 0;
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function generateMask () {
|
||||
function generateMask() {
|
||||
if (bufIdx === BUFFER_SIZE) {
|
||||
bufIdx = 0
|
||||
crypto.randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
||||
bufIdx = 0;
|
||||
crypto.randomFillSync(
|
||||
(buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)),
|
||||
0,
|
||||
BUFFER_SIZE
|
||||
);
|
||||
}
|
||||
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
||||
return [
|
||||
buffer[bufIdx++],
|
||||
buffer[bufIdx++],
|
||||
buffer[bufIdx++],
|
||||
buffer[bufIdx++],
|
||||
];
|
||||
}
|
||||
|
||||
class WebsocketFrameSend {
|
||||
/**
|
||||
* @param {Buffer|undefined} data
|
||||
*/
|
||||
constructor (data) {
|
||||
this.frameData = data
|
||||
constructor(data) {
|
||||
this.frameData = data;
|
||||
}
|
||||
|
||||
createFrame (opcode) {
|
||||
const frameData = this.frameData
|
||||
const maskKey = generateMask()
|
||||
const bodyLength = frameData?.byteLength ?? 0
|
||||
createFrame(opcode) {
|
||||
const frameData = this.frameData;
|
||||
const maskKey = generateMask();
|
||||
const bodyLength = frameData?.byteLength ?? 0;
|
||||
|
||||
/** @type {number} */
|
||||
let payloadLength = bodyLength // 0-125
|
||||
let offset = 6
|
||||
let payloadLength = bodyLength; // 0-125
|
||||
let offset = 6;
|
||||
|
||||
if (bodyLength > maxUnsigned16Bit) {
|
||||
offset += 8 // payload length is next 8 bytes
|
||||
payloadLength = 127
|
||||
offset += 8; // payload length is next 8 bytes
|
||||
payloadLength = 127;
|
||||
} else if (bodyLength > 125) {
|
||||
offset += 2 // payload length is next 2 bytes
|
||||
payloadLength = 126
|
||||
offset += 2; // payload length is next 2 bytes
|
||||
payloadLength = 126;
|
||||
}
|
||||
|
||||
const buffer = Buffer.allocUnsafe(bodyLength + offset)
|
||||
const buffer = Buffer.allocUnsafe(bodyLength + offset);
|
||||
|
||||
// Clear first 2 bytes, everything else is overwritten
|
||||
buffer[0] = buffer[1] = 0
|
||||
buffer[0] |= 0x80 // FIN
|
||||
buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
|
||||
buffer[0] = buffer[1] = 0;
|
||||
buffer[0] |= 0x80; // FIN
|
||||
buffer[0] = (buffer[0] & 0xf0) + opcode; // opcode
|
||||
|
||||
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
|
||||
buffer[offset - 4] = maskKey[0]
|
||||
buffer[offset - 3] = maskKey[1]
|
||||
buffer[offset - 2] = maskKey[2]
|
||||
buffer[offset - 1] = maskKey[3]
|
||||
buffer[offset - 4] = maskKey[0];
|
||||
buffer[offset - 3] = maskKey[1];
|
||||
buffer[offset - 2] = maskKey[2];
|
||||
buffer[offset - 1] = maskKey[3];
|
||||
|
||||
buffer[1] = payloadLength
|
||||
buffer[1] = payloadLength;
|
||||
|
||||
if (payloadLength === 126) {
|
||||
buffer.writeUInt16BE(bodyLength, 2)
|
||||
buffer.writeUInt16BE(bodyLength, 2);
|
||||
} else if (payloadLength === 127) {
|
||||
// Clear extended payload length
|
||||
buffer[2] = buffer[3] = 0
|
||||
buffer.writeUIntBE(bodyLength, 4, 6)
|
||||
buffer[2] = buffer[3] = 0;
|
||||
buffer.writeUIntBE(bodyLength, 4, 6);
|
||||
}
|
||||
|
||||
buffer[1] |= 0x80 // MASK
|
||||
buffer[1] |= 0x80; // MASK
|
||||
|
||||
// mask body
|
||||
for (let i = 0; i < bodyLength; ++i) {
|
||||
buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
|
||||
buffer[offset + i] = frameData[i] ^ maskKey[i & 3];
|
||||
}
|
||||
|
||||
return buffer
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} buffer
|
||||
*/
|
||||
static createFastTextFrame (buffer) {
|
||||
const maskKey = generateMask()
|
||||
static createFastTextFrame(buffer) {
|
||||
const maskKey = generateMask();
|
||||
|
||||
const bodyLength = buffer.length
|
||||
const bodyLength = buffer.length;
|
||||
|
||||
// mask body
|
||||
for (let i = 0; i < bodyLength; ++i) {
|
||||
buffer[i] ^= maskKey[i & 3]
|
||||
buffer[i] ^= maskKey[i & 3];
|
||||
}
|
||||
|
||||
let payloadLength = bodyLength
|
||||
let offset = 6
|
||||
let payloadLength = bodyLength;
|
||||
let offset = 6;
|
||||
|
||||
if (bodyLength > maxUnsigned16Bit) {
|
||||
offset += 8 // payload length is next 8 bytes
|
||||
payloadLength = 127
|
||||
offset += 8; // payload length is next 8 bytes
|
||||
payloadLength = 127;
|
||||
} else if (bodyLength > 125) {
|
||||
offset += 2 // payload length is next 2 bytes
|
||||
payloadLength = 126
|
||||
offset += 2; // payload length is next 2 bytes
|
||||
payloadLength = 126;
|
||||
}
|
||||
const head = Buffer.allocUnsafeSlow(offset)
|
||||
const head = Buffer.allocUnsafeSlow(offset);
|
||||
|
||||
head[0] = 0x80 /* FIN */ | opcodes.TEXT /* opcode TEXT */
|
||||
head[1] = payloadLength | 0x80 /* MASK */
|
||||
head[offset - 4] = maskKey[0]
|
||||
head[offset - 3] = maskKey[1]
|
||||
head[offset - 2] = maskKey[2]
|
||||
head[offset - 1] = maskKey[3]
|
||||
head[0] = 0x80 /* FIN */ | opcodes.TEXT; /* opcode TEXT */
|
||||
head[1] = payloadLength | 0x80; /* MASK */
|
||||
head[offset - 4] = maskKey[0];
|
||||
head[offset - 3] = maskKey[1];
|
||||
head[offset - 2] = maskKey[2];
|
||||
head[offset - 1] = maskKey[3];
|
||||
|
||||
if (payloadLength === 126) {
|
||||
head.writeUInt16BE(bodyLength, 2)
|
||||
head.writeUInt16BE(bodyLength, 2);
|
||||
} else if (payloadLength === 127) {
|
||||
head[2] = head[3] = 0
|
||||
head.writeUIntBE(bodyLength, 4, 6)
|
||||
head[2] = head[3] = 0;
|
||||
head.writeUIntBE(bodyLength, 4, 6);
|
||||
}
|
||||
|
||||
return [head, buffer]
|
||||
return [head, buffer];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
WebsocketFrameSend
|
||||
}
|
||||
WebsocketFrameSend,
|
||||
};
|
||||
|
76
node_modules/undici/lib/web/websocket/permessage-deflate.js
generated
vendored
76
node_modules/undici/lib/web/websocket/permessage-deflate.js
generated
vendored
@ -1,70 +1,78 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib')
|
||||
const { isValidClientWindowBits } = require('./util')
|
||||
const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib');
|
||||
const { isValidClientWindowBits } = require('./util');
|
||||
|
||||
const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
||||
const kBuffer = Symbol('kBuffer')
|
||||
const kLength = Symbol('kLength')
|
||||
const tail = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||||
const kBuffer = Symbol('kBuffer');
|
||||
const kLength = Symbol('kLength');
|
||||
|
||||
class PerMessageDeflate {
|
||||
/** @type {import('node:zlib').InflateRaw} */
|
||||
#inflate
|
||||
#inflate;
|
||||
|
||||
#options = {}
|
||||
#options = {};
|
||||
|
||||
constructor (extensions) {
|
||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||
constructor(extensions) {
|
||||
this.#options.serverNoContextTakeover = extensions.has(
|
||||
'server_no_context_takeover'
|
||||
);
|
||||
this.#options.serverMaxWindowBits = extensions.get(
|
||||
'server_max_window_bits'
|
||||
);
|
||||
}
|
||||
|
||||
decompress (chunk, fin, callback) {
|
||||
decompress(chunk, fin, callback) {
|
||||
// An endpoint uses the following algorithm to decompress a message.
|
||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
// payload of the message.
|
||||
// 2. Decompress the resulting data using DEFLATE.
|
||||
|
||||
if (!this.#inflate) {
|
||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||
let windowBits = Z_DEFAULT_WINDOWBITS;
|
||||
|
||||
if (this.#options.serverMaxWindowBits) { // empty values default to Z_DEFAULT_WINDOWBITS
|
||||
if (this.#options.serverMaxWindowBits) {
|
||||
// empty values default to Z_DEFAULT_WINDOWBITS
|
||||
if (!isValidClientWindowBits(this.#options.serverMaxWindowBits)) {
|
||||
callback(new Error('Invalid server_max_window_bits'))
|
||||
return
|
||||
callback(new Error('Invalid server_max_window_bits'));
|
||||
return;
|
||||
}
|
||||
|
||||
windowBits = Number.parseInt(this.#options.serverMaxWindowBits)
|
||||
windowBits = Number.parseInt(this.#options.serverMaxWindowBits);
|
||||
}
|
||||
|
||||
this.#inflate = createInflateRaw({ windowBits })
|
||||
this.#inflate[kBuffer] = []
|
||||
this.#inflate[kLength] = 0
|
||||
this.#inflate = createInflateRaw({ windowBits });
|
||||
this.#inflate[kBuffer] = [];
|
||||
this.#inflate[kLength] = 0;
|
||||
|
||||
this.#inflate.on('data', (data) => {
|
||||
this.#inflate[kBuffer].push(data)
|
||||
this.#inflate[kLength] += data.length
|
||||
})
|
||||
this.#inflate[kBuffer].push(data);
|
||||
this.#inflate[kLength] += data.length;
|
||||
});
|
||||
|
||||
this.#inflate.on('error', (err) => {
|
||||
this.#inflate = null
|
||||
callback(err)
|
||||
})
|
||||
this.#inflate = null;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
this.#inflate.write(chunk)
|
||||
this.#inflate.write(chunk);
|
||||
if (fin) {
|
||||
this.#inflate.write(tail)
|
||||
this.#inflate.write(tail);
|
||||
}
|
||||
|
||||
this.#inflate.flush(() => {
|
||||
const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength])
|
||||
const full = Buffer.concat(
|
||||
this.#inflate[kBuffer],
|
||||
this.#inflate[kLength]
|
||||
);
|
||||
|
||||
this.#inflate[kBuffer].length = 0
|
||||
this.#inflate[kLength] = 0
|
||||
this.#inflate[kBuffer].length = 0;
|
||||
this.#inflate[kLength] = 0;
|
||||
|
||||
callback(null, full)
|
||||
})
|
||||
callback(null, full);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { PerMessageDeflate }
|
||||
module.exports = { PerMessageDeflate };
|
||||
|
434
node_modules/undici/lib/web/websocket/receiver.js
generated
vendored
434
node_modules/undici/lib/web/websocket/receiver.js
generated
vendored
@ -1,9 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { Writable } = require('node:stream')
|
||||
const assert = require('node:assert')
|
||||
const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
|
||||
const { channels } = require('../../core/diagnostics')
|
||||
const { Writable } = require('node:stream');
|
||||
const assert = require('node:assert');
|
||||
const {
|
||||
parserStates,
|
||||
opcodes,
|
||||
states,
|
||||
emptyBuffer,
|
||||
sentCloseFrameState,
|
||||
} = require('./constants');
|
||||
const { channels } = require('../../core/diagnostics');
|
||||
const {
|
||||
isValidStatusCode,
|
||||
isValidOpcode,
|
||||
@ -11,11 +17,11 @@ const {
|
||||
utf8Decode,
|
||||
isControlFrame,
|
||||
isTextBinaryFrame,
|
||||
isContinuationFrame
|
||||
} = require('./util')
|
||||
const { failWebsocketConnection } = require('./connection')
|
||||
const { WebsocketFrameSend } = require('./frame')
|
||||
const { PerMessageDeflate } = require('./permessage-deflate')
|
||||
isContinuationFrame,
|
||||
} = require('./util');
|
||||
const { failWebsocketConnection } = require('./connection');
|
||||
const { WebsocketFrameSend } = require('./frame');
|
||||
const { PerMessageDeflate } = require('./permessage-deflate');
|
||||
|
||||
// This code was influenced by ws released under the MIT license.
|
||||
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
@ -23,30 +29,33 @@ const { PerMessageDeflate } = require('./permessage-deflate')
|
||||
// Copyright (c) 2016 Luigi Pinca and contributors
|
||||
|
||||
class ByteParser extends Writable {
|
||||
#buffers = []
|
||||
#fragmentsBytes = 0
|
||||
#byteOffset = 0
|
||||
#loop = false
|
||||
#buffers = [];
|
||||
#fragmentsBytes = 0;
|
||||
#byteOffset = 0;
|
||||
#loop = false;
|
||||
|
||||
#state = parserStates.INFO
|
||||
#state = parserStates.INFO;
|
||||
|
||||
#info = {}
|
||||
#fragments = []
|
||||
#info = {};
|
||||
#fragments = [];
|
||||
|
||||
/** @type {Map<string, PerMessageDeflate>} */
|
||||
#extensions
|
||||
#extensions;
|
||||
|
||||
/** @type {import('./websocket').Handler} */
|
||||
#handler
|
||||
#handler;
|
||||
|
||||
constructor (handler, extensions) {
|
||||
super()
|
||||
constructor(handler, extensions) {
|
||||
super();
|
||||
|
||||
this.#handler = handler
|
||||
this.#extensions = extensions == null ? new Map() : extensions
|
||||
this.#handler = handler;
|
||||
this.#extensions = extensions == null ? new Map() : extensions;
|
||||
|
||||
if (this.#extensions.has('permessage-deflate')) {
|
||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
||||
this.#extensions.set(
|
||||
'permessage-deflate',
|
||||
new PerMessageDeflate(extensions)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,12 +63,12 @@ class ByteParser extends Writable {
|
||||
* @param {Buffer} chunk
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
_write (chunk, _, callback) {
|
||||
this.#buffers.push(chunk)
|
||||
this.#byteOffset += chunk.length
|
||||
this.#loop = true
|
||||
_write(chunk, _, callback) {
|
||||
this.#buffers.push(chunk);
|
||||
this.#byteOffset += chunk.length;
|
||||
this.#loop = true;
|
||||
|
||||
this.run(callback)
|
||||
this.run(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,34 +76,42 @@ class ByteParser extends Writable {
|
||||
* Callback is called whenever there are no more chunks buffering,
|
||||
* or not enough bytes are buffered to parse.
|
||||
*/
|
||||
run (callback) {
|
||||
run(callback) {
|
||||
while (this.#loop) {
|
||||
if (this.#state === parserStates.INFO) {
|
||||
// If there aren't enough bytes to parse the payload length, etc.
|
||||
if (this.#byteOffset < 2) {
|
||||
return callback()
|
||||
return callback();
|
||||
}
|
||||
|
||||
const buffer = this.consume(2)
|
||||
const fin = (buffer[0] & 0x80) !== 0
|
||||
const opcode = buffer[0] & 0x0F
|
||||
const masked = (buffer[1] & 0x80) === 0x80
|
||||
const buffer = this.consume(2);
|
||||
const fin = (buffer[0] & 0x80) !== 0;
|
||||
const opcode = buffer[0] & 0x0f;
|
||||
const masked = (buffer[1] & 0x80) === 0x80;
|
||||
|
||||
const fragmented = !fin && opcode !== opcodes.CONTINUATION
|
||||
const payloadLength = buffer[1] & 0x7F
|
||||
const fragmented = !fin && opcode !== opcodes.CONTINUATION;
|
||||
const payloadLength = buffer[1] & 0x7f;
|
||||
|
||||
const rsv1 = buffer[0] & 0x40
|
||||
const rsv2 = buffer[0] & 0x20
|
||||
const rsv3 = buffer[0] & 0x10
|
||||
const rsv1 = buffer[0] & 0x40;
|
||||
const rsv2 = buffer[0] & 0x20;
|
||||
const rsv3 = buffer[0] & 0x10;
|
||||
|
||||
if (!isValidOpcode(opcode)) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Invalid opcode received')
|
||||
return callback()
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Invalid opcode received'
|
||||
);
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (masked) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Frame cannot be masked')
|
||||
return callback()
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Frame cannot be masked'
|
||||
);
|
||||
return callback();
|
||||
}
|
||||
|
||||
// MUST be 0 unless an extension is negotiated that defines meanings
|
||||
@ -107,80 +124,112 @@ class ByteParser extends Writable {
|
||||
// WebSocket connection where a PMCE is in use, this bit indicates
|
||||
// whether a message is compressed or not.
|
||||
if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Expected RSV1 to be clear.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Expected RSV1 to be clear.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rsv2 !== 0 || rsv3 !== 0) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'RSV1, RSV2, RSV3 must be clear')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'RSV1, RSV2, RSV3 must be clear'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragmented && !isTextBinaryFrame(opcode)) {
|
||||
// Only text and binary frames can be fragmented
|
||||
failWebsocketConnection(this.#handler, 1002, 'Invalid frame type was fragmented.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Invalid frame type was fragmented.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are already parsing a text/binary frame and do not receive either
|
||||
// a continuation frame or close frame, fail the connection.
|
||||
if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Expected continuation frame')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Expected continuation frame'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#info.fragmented && fragmented) {
|
||||
// A fragmented frame can't be fragmented itself
|
||||
failWebsocketConnection(this.#handler, 1002, 'Fragmented frame exceeded 125 bytes.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Fragmented frame exceeded 125 bytes.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// "All control frames MUST have a payload length of 125 bytes or less
|
||||
// and MUST NOT be fragmented."
|
||||
if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Control frame either too large or fragmented')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Control frame either too large or fragmented'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Unexpected continuation frame')
|
||||
return
|
||||
if (
|
||||
isContinuationFrame(opcode) &&
|
||||
this.#fragments.length === 0 &&
|
||||
!this.#info.compressed
|
||||
) {
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Unexpected continuation frame'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payloadLength <= 125) {
|
||||
this.#info.payloadLength = payloadLength
|
||||
this.#state = parserStates.READ_DATA
|
||||
this.#info.payloadLength = payloadLength;
|
||||
this.#state = parserStates.READ_DATA;
|
||||
} else if (payloadLength === 126) {
|
||||
this.#state = parserStates.PAYLOADLENGTH_16
|
||||
this.#state = parserStates.PAYLOADLENGTH_16;
|
||||
} else if (payloadLength === 127) {
|
||||
this.#state = parserStates.PAYLOADLENGTH_64
|
||||
this.#state = parserStates.PAYLOADLENGTH_64;
|
||||
}
|
||||
|
||||
if (isTextBinaryFrame(opcode)) {
|
||||
this.#info.binaryType = opcode
|
||||
this.#info.compressed = rsv1 !== 0
|
||||
this.#info.binaryType = opcode;
|
||||
this.#info.compressed = rsv1 !== 0;
|
||||
}
|
||||
|
||||
this.#info.opcode = opcode
|
||||
this.#info.masked = masked
|
||||
this.#info.fin = fin
|
||||
this.#info.fragmented = fragmented
|
||||
this.#info.opcode = opcode;
|
||||
this.#info.masked = masked;
|
||||
this.#info.fin = fin;
|
||||
this.#info.fragmented = fragmented;
|
||||
} else if (this.#state === parserStates.PAYLOADLENGTH_16) {
|
||||
if (this.#byteOffset < 2) {
|
||||
return callback()
|
||||
return callback();
|
||||
}
|
||||
|
||||
const buffer = this.consume(2)
|
||||
const buffer = this.consume(2);
|
||||
|
||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||
this.#state = parserStates.READ_DATA
|
||||
this.#info.payloadLength = buffer.readUInt16BE(0);
|
||||
this.#state = parserStates.READ_DATA;
|
||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||
if (this.#byteOffset < 8) {
|
||||
return callback()
|
||||
return callback();
|
||||
}
|
||||
|
||||
const buffer = this.consume(8)
|
||||
const upper = buffer.readUInt32BE(0)
|
||||
const buffer = this.consume(8);
|
||||
const upper = buffer.readUInt32BE(0);
|
||||
|
||||
// 2^31 is the maximum bytes an arraybuffer can contain
|
||||
// on 32-bit systems. Although, on 64-bit systems, this is
|
||||
@ -189,62 +238,76 @@ class ByteParser extends Writable {
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
|
||||
if (upper > 2 ** 31 - 1) {
|
||||
failWebsocketConnection(this.#handler, 1009, 'Received payload length > 2^31 bytes.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1009,
|
||||
'Received payload length > 2^31 bytes.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const lower = buffer.readUInt32BE(4)
|
||||
const lower = buffer.readUInt32BE(4);
|
||||
|
||||
this.#info.payloadLength = (upper << 8) + lower
|
||||
this.#state = parserStates.READ_DATA
|
||||
this.#info.payloadLength = (upper << 8) + lower;
|
||||
this.#state = parserStates.READ_DATA;
|
||||
} else if (this.#state === parserStates.READ_DATA) {
|
||||
if (this.#byteOffset < this.#info.payloadLength) {
|
||||
return callback()
|
||||
return callback();
|
||||
}
|
||||
|
||||
const body = this.consume(this.#info.payloadLength)
|
||||
const body = this.consume(this.#info.payloadLength);
|
||||
|
||||
if (isControlFrame(this.#info.opcode)) {
|
||||
this.#loop = this.parseControlFrame(body)
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = this.parseControlFrame(body);
|
||||
this.#state = parserStates.INFO;
|
||||
} else {
|
||||
if (!this.#info.compressed) {
|
||||
this.writeFragments(body)
|
||||
this.writeFragments(body);
|
||||
|
||||
// If the frame is not fragmented, a message has been received.
|
||||
// If the frame is fragmented, it will terminate with a fin bit set
|
||||
// and an opcode of 0 (continuation), therefore we handle that when
|
||||
// parsing continuation frames, not here.
|
||||
if (!this.#info.fragmented && this.#info.fin) {
|
||||
websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
|
||||
websocketMessageReceived(
|
||||
this.#handler,
|
||||
this.#info.binaryType,
|
||||
this.consumeFragments()
|
||||
);
|
||||
}
|
||||
|
||||
this.#state = parserStates.INFO
|
||||
this.#state = parserStates.INFO;
|
||||
} else {
|
||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
||||
if (error) {
|
||||
failWebsocketConnection(this.#handler, 1007, error.message)
|
||||
return
|
||||
}
|
||||
this.#extensions
|
||||
.get('permessage-deflate')
|
||||
.decompress(body, this.#info.fin, (error, data) => {
|
||||
if (error) {
|
||||
failWebsocketConnection(this.#handler, 1007, error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.writeFragments(data)
|
||||
this.writeFragments(data);
|
||||
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = true
|
||||
this.run(callback)
|
||||
return
|
||||
}
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO;
|
||||
this.#loop = true;
|
||||
this.run(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
|
||||
websocketMessageReceived(
|
||||
this.#handler,
|
||||
this.#info.binaryType,
|
||||
this.consumeFragments()
|
||||
);
|
||||
|
||||
this.#loop = true
|
||||
this.#state = parserStates.INFO
|
||||
this.run(callback)
|
||||
})
|
||||
this.#loop = true;
|
||||
this.#state = parserStates.INFO;
|
||||
this.run(callback);
|
||||
});
|
||||
|
||||
this.#loop = false
|
||||
break
|
||||
this.#loop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -256,162 +319,169 @@ class ByteParser extends Writable {
|
||||
* @param {number} n
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
consume (n) {
|
||||
consume(n) {
|
||||
if (n > this.#byteOffset) {
|
||||
throw new Error('Called consume() before buffers satiated.')
|
||||
throw new Error('Called consume() before buffers satiated.');
|
||||
} else if (n === 0) {
|
||||
return emptyBuffer
|
||||
return emptyBuffer;
|
||||
}
|
||||
|
||||
this.#byteOffset -= n
|
||||
this.#byteOffset -= n;
|
||||
|
||||
const first = this.#buffers[0]
|
||||
const first = this.#buffers[0];
|
||||
|
||||
if (first.length > n) {
|
||||
// replace with remaining buffer
|
||||
this.#buffers[0] = first.subarray(n, first.length)
|
||||
return first.subarray(0, n)
|
||||
this.#buffers[0] = first.subarray(n, first.length);
|
||||
return first.subarray(0, n);
|
||||
} else if (first.length === n) {
|
||||
// prefect match
|
||||
return this.#buffers.shift()
|
||||
return this.#buffers.shift();
|
||||
} else {
|
||||
let offset = 0
|
||||
let offset = 0;
|
||||
// If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
|
||||
const buffer = Buffer.allocUnsafeSlow(n)
|
||||
const buffer = Buffer.allocUnsafeSlow(n);
|
||||
while (offset !== n) {
|
||||
const next = this.#buffers[0]
|
||||
const length = next.length
|
||||
const next = this.#buffers[0];
|
||||
const length = next.length;
|
||||
|
||||
if (length + offset === n) {
|
||||
buffer.set(this.#buffers.shift(), offset)
|
||||
break
|
||||
buffer.set(this.#buffers.shift(), offset);
|
||||
break;
|
||||
} else if (length + offset > n) {
|
||||
buffer.set(next.subarray(0, n - offset), offset)
|
||||
this.#buffers[0] = next.subarray(n - offset)
|
||||
break
|
||||
buffer.set(next.subarray(0, n - offset), offset);
|
||||
this.#buffers[0] = next.subarray(n - offset);
|
||||
break;
|
||||
} else {
|
||||
buffer.set(this.#buffers.shift(), offset)
|
||||
offset += length
|
||||
buffer.set(this.#buffers.shift(), offset);
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
writeFragments (fragment) {
|
||||
this.#fragmentsBytes += fragment.length
|
||||
this.#fragments.push(fragment)
|
||||
writeFragments(fragment) {
|
||||
this.#fragmentsBytes += fragment.length;
|
||||
this.#fragments.push(fragment);
|
||||
}
|
||||
|
||||
consumeFragments () {
|
||||
const fragments = this.#fragments
|
||||
consumeFragments() {
|
||||
const fragments = this.#fragments;
|
||||
|
||||
if (fragments.length === 1) {
|
||||
// single fragment
|
||||
this.#fragmentsBytes = 0
|
||||
return fragments.shift()
|
||||
this.#fragmentsBytes = 0;
|
||||
return fragments.shift();
|
||||
}
|
||||
|
||||
let offset = 0
|
||||
let offset = 0;
|
||||
// If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
|
||||
const output = Buffer.allocUnsafeSlow(this.#fragmentsBytes)
|
||||
const output = Buffer.allocUnsafeSlow(this.#fragmentsBytes);
|
||||
|
||||
for (let i = 0; i < fragments.length; ++i) {
|
||||
const buffer = fragments[i]
|
||||
output.set(buffer, offset)
|
||||
offset += buffer.length
|
||||
const buffer = fragments[i];
|
||||
output.set(buffer, offset);
|
||||
offset += buffer.length;
|
||||
}
|
||||
|
||||
this.#fragments = []
|
||||
this.#fragmentsBytes = 0
|
||||
this.#fragments = [];
|
||||
this.#fragmentsBytes = 0;
|
||||
|
||||
return output
|
||||
return output;
|
||||
}
|
||||
|
||||
parseCloseBody (data) {
|
||||
assert(data.length !== 1)
|
||||
parseCloseBody(data) {
|
||||
assert(data.length !== 1);
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
|
||||
/** @type {number|undefined} */
|
||||
let code
|
||||
let code;
|
||||
|
||||
if (data.length >= 2) {
|
||||
// _The WebSocket Connection Close Code_ is
|
||||
// defined as the status code (Section 7.4) contained in the first Close
|
||||
// control frame received by the application
|
||||
code = data.readUInt16BE(0)
|
||||
code = data.readUInt16BE(0);
|
||||
}
|
||||
|
||||
if (code !== undefined && !isValidStatusCode(code)) {
|
||||
return { code: 1002, reason: 'Invalid status code', error: true }
|
||||
return { code: 1002, reason: 'Invalid status code', error: true };
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
|
||||
/** @type {Buffer} */
|
||||
let reason = data.subarray(2)
|
||||
let reason = data.subarray(2);
|
||||
|
||||
// Remove BOM
|
||||
if (reason[0] === 0xEF && reason[1] === 0xBB && reason[2] === 0xBF) {
|
||||
reason = reason.subarray(3)
|
||||
if (reason[0] === 0xef && reason[1] === 0xbb && reason[2] === 0xbf) {
|
||||
reason = reason.subarray(3);
|
||||
}
|
||||
|
||||
try {
|
||||
reason = utf8Decode(reason)
|
||||
reason = utf8Decode(reason);
|
||||
} catch {
|
||||
return { code: 1007, reason: 'Invalid UTF-8', error: true }
|
||||
return { code: 1007, reason: 'Invalid UTF-8', error: true };
|
||||
}
|
||||
|
||||
return { code, reason, error: false }
|
||||
return { code, reason, error: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses control frames.
|
||||
* @param {Buffer} body
|
||||
*/
|
||||
parseControlFrame (body) {
|
||||
const { opcode, payloadLength } = this.#info
|
||||
parseControlFrame(body) {
|
||||
const { opcode, payloadLength } = this.#info;
|
||||
|
||||
if (opcode === opcodes.CLOSE) {
|
||||
if (payloadLength === 1) {
|
||||
failWebsocketConnection(this.#handler, 1002, 'Received close frame with a 1-byte body.')
|
||||
return false
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1002,
|
||||
'Received close frame with a 1-byte body.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.#info.closeInfo = this.parseCloseBody(body)
|
||||
this.#info.closeInfo = this.parseCloseBody(body);
|
||||
|
||||
if (this.#info.closeInfo.error) {
|
||||
const { code, reason } = this.#info.closeInfo
|
||||
const { code, reason } = this.#info.closeInfo;
|
||||
|
||||
failWebsocketConnection(this.#handler, code, reason)
|
||||
return false
|
||||
failWebsocketConnection(this.#handler, code, reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upon receiving such a frame, the other peer sends a
|
||||
// Close frame in response, if it hasn't already sent one.
|
||||
if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
if (
|
||||
!this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
) {
|
||||
// If an endpoint receives a Close frame and did not previously send a
|
||||
// Close frame, the endpoint MUST send a Close frame in response. (When
|
||||
// sending a Close frame in response, the endpoint typically echos the
|
||||
// status code it received.)
|
||||
let body = emptyBuffer
|
||||
let body = emptyBuffer;
|
||||
if (this.#info.closeInfo.code) {
|
||||
body = Buffer.allocUnsafe(2)
|
||||
body.writeUInt16BE(this.#info.closeInfo.code, 0)
|
||||
body = Buffer.allocUnsafe(2);
|
||||
body.writeUInt16BE(this.#info.closeInfo.code, 0);
|
||||
}
|
||||
const closeFrame = new WebsocketFrameSend(body)
|
||||
const closeFrame = new WebsocketFrameSend(body);
|
||||
|
||||
this.#handler.socket.write(closeFrame.createFrame(opcodes.CLOSE))
|
||||
this.#handler.closeState.add(sentCloseFrameState.SENT)
|
||||
this.#handler.socket.write(closeFrame.createFrame(opcodes.CLOSE));
|
||||
this.#handler.closeState.add(sentCloseFrameState.SENT);
|
||||
}
|
||||
|
||||
// Upon either sending or receiving a Close control frame, it is said
|
||||
// that _The WebSocket Closing Handshake is Started_ and that the
|
||||
// WebSocket connection is in the CLOSING state.
|
||||
this.#handler.readyState = states.CLOSING
|
||||
this.#handler.closeState.add(sentCloseFrameState.RECEIVED)
|
||||
this.#handler.readyState = states.CLOSING;
|
||||
this.#handler.closeState.add(sentCloseFrameState.RECEIVED);
|
||||
|
||||
return false
|
||||
return false;
|
||||
} else if (opcode === opcodes.PING) {
|
||||
// Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
|
||||
// response, unless it already received a Close frame.
|
||||
@ -419,14 +489,14 @@ class ByteParser extends Writable {
|
||||
// "Application data"
|
||||
|
||||
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
const frame = new WebsocketFrameSend(body)
|
||||
const frame = new WebsocketFrameSend(body);
|
||||
|
||||
this.#handler.socket.write(frame.createFrame(opcodes.PONG))
|
||||
this.#handler.socket.write(frame.createFrame(opcodes.PONG));
|
||||
|
||||
if (channels.ping.hasSubscribers) {
|
||||
channels.ping.publish({
|
||||
payload: body
|
||||
})
|
||||
payload: body,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (opcode === opcodes.PONG) {
|
||||
@ -436,19 +506,19 @@ class ByteParser extends Writable {
|
||||
|
||||
if (channels.pong.hasSubscribers) {
|
||||
channels.pong.publish({
|
||||
payload: body
|
||||
})
|
||||
payload: body,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
get closingInfo () {
|
||||
return this.#info.closeInfo
|
||||
get closingInfo() {
|
||||
return this.#info.closeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ByteParser
|
||||
}
|
||||
ByteParser,
|
||||
};
|
||||
|
83
node_modules/undici/lib/web/websocket/sender.js
generated
vendored
83
node_modules/undici/lib/web/websocket/sender.js
generated
vendored
@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { WebsocketFrameSend } = require('./frame')
|
||||
const { opcodes, sendHints } = require('./constants')
|
||||
const FixedQueue = require('../../dispatcher/fixed-queue')
|
||||
const { WebsocketFrameSend } = require('./frame');
|
||||
const { opcodes, sendHints } = require('./constants');
|
||||
const FixedQueue = require('../../dispatcher/fixed-queue');
|
||||
|
||||
/**
|
||||
* @typedef {object} SendQueueNode
|
||||
@ -15,95 +15,98 @@ class SendQueue {
|
||||
/**
|
||||
* @type {FixedQueue}
|
||||
*/
|
||||
#queue = new FixedQueue()
|
||||
#queue = new FixedQueue();
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
#running = false
|
||||
#running = false;
|
||||
|
||||
/** @type {import('node:net').Socket} */
|
||||
#socket
|
||||
#socket;
|
||||
|
||||
constructor (socket) {
|
||||
this.#socket = socket
|
||||
constructor(socket) {
|
||||
this.#socket = socket;
|
||||
}
|
||||
|
||||
add (item, cb, hint) {
|
||||
add(item, cb, hint) {
|
||||
if (hint !== sendHints.blob) {
|
||||
if (!this.#running) {
|
||||
// TODO(@tsctx): support fast-path for string on running
|
||||
if (hint === sendHints.text) {
|
||||
// special fast-path for string
|
||||
const { 0: head, 1: body } = WebsocketFrameSend.createFastTextFrame(item)
|
||||
this.#socket.cork()
|
||||
this.#socket.write(head)
|
||||
this.#socket.write(body, cb)
|
||||
this.#socket.uncork()
|
||||
const { 0: head, 1: body } =
|
||||
WebsocketFrameSend.createFastTextFrame(item);
|
||||
this.#socket.cork();
|
||||
this.#socket.write(head);
|
||||
this.#socket.write(body, cb);
|
||||
this.#socket.uncork();
|
||||
} else {
|
||||
// direct writing
|
||||
this.#socket.write(createFrame(item, hint), cb)
|
||||
this.#socket.write(createFrame(item, hint), cb);
|
||||
}
|
||||
} else {
|
||||
/** @type {SendQueueNode} */
|
||||
const node = {
|
||||
promise: null,
|
||||
callback: cb,
|
||||
frame: createFrame(item, hint)
|
||||
}
|
||||
this.#queue.push(node)
|
||||
frame: createFrame(item, hint),
|
||||
};
|
||||
this.#queue.push(node);
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {SendQueueNode} */
|
||||
const node = {
|
||||
promise: item.arrayBuffer().then((ab) => {
|
||||
node.promise = null
|
||||
node.frame = createFrame(ab, hint)
|
||||
node.promise = null;
|
||||
node.frame = createFrame(ab, hint);
|
||||
}),
|
||||
callback: cb,
|
||||
frame: null
|
||||
}
|
||||
frame: null,
|
||||
};
|
||||
|
||||
this.#queue.push(node)
|
||||
this.#queue.push(node);
|
||||
|
||||
if (!this.#running) {
|
||||
this.#run()
|
||||
this.#run();
|
||||
}
|
||||
}
|
||||
|
||||
async #run () {
|
||||
this.#running = true
|
||||
const queue = this.#queue
|
||||
async #run() {
|
||||
this.#running = true;
|
||||
const queue = this.#queue;
|
||||
while (!queue.isEmpty()) {
|
||||
const node = queue.shift()
|
||||
const node = queue.shift();
|
||||
// wait pending promise
|
||||
if (node.promise !== null) {
|
||||
await node.promise
|
||||
await node.promise;
|
||||
}
|
||||
// write
|
||||
this.#socket.write(node.frame, node.callback)
|
||||
this.#socket.write(node.frame, node.callback);
|
||||
// cleanup
|
||||
node.callback = node.frame = null
|
||||
node.callback = node.frame = null;
|
||||
}
|
||||
this.#running = false
|
||||
this.#running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function createFrame (data, hint) {
|
||||
return new WebsocketFrameSend(toBuffer(data, hint)).createFrame(hint === sendHints.text ? opcodes.TEXT : opcodes.BINARY)
|
||||
function createFrame(data, hint) {
|
||||
return new WebsocketFrameSend(toBuffer(data, hint)).createFrame(
|
||||
hint === sendHints.text ? opcodes.TEXT : opcodes.BINARY
|
||||
);
|
||||
}
|
||||
|
||||
function toBuffer (data, hint) {
|
||||
function toBuffer(data, hint) {
|
||||
switch (hint) {
|
||||
case sendHints.text:
|
||||
case sendHints.typedArray:
|
||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
case sendHints.arrayBuffer:
|
||||
case sendHints.blob:
|
||||
return new Uint8Array(data)
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SendQueue }
|
||||
module.exports = { SendQueue };
|
||||
|
68
node_modules/undici/lib/web/websocket/stream/websocketerror.js
generated
vendored
68
node_modules/undici/lib/web/websocket/stream/websocketerror.js
generated
vendored
@ -1,54 +1,54 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { webidl } = require('../../fetch/webidl')
|
||||
const { validateCloseCodeAndReason } = require('../util')
|
||||
const { kConstruct } = require('../../../core/symbols')
|
||||
const { kEnumerableProperty } = require('../../../core/util')
|
||||
const { webidl } = require('../../fetch/webidl');
|
||||
const { validateCloseCodeAndReason } = require('../util');
|
||||
const { kConstruct } = require('../../../core/symbols');
|
||||
const { kEnumerableProperty } = require('../../../core/util');
|
||||
|
||||
class WebSocketError extends DOMException {
|
||||
#closeCode
|
||||
#reason
|
||||
#closeCode;
|
||||
#reason;
|
||||
|
||||
constructor (message = '', init = undefined) {
|
||||
message = webidl.converters.DOMString(message, 'WebSocketError', 'message')
|
||||
constructor(message = '', init = undefined) {
|
||||
message = webidl.converters.DOMString(message, 'WebSocketError', 'message');
|
||||
|
||||
// 1. Set this 's name to " WebSocketError ".
|
||||
// 2. Set this 's message to message .
|
||||
super(message, 'WebSocketError')
|
||||
super(message, 'WebSocketError');
|
||||
|
||||
if (init === kConstruct) {
|
||||
return
|
||||
return;
|
||||
} else if (init !== null) {
|
||||
init = webidl.converters.WebSocketCloseInfo(init)
|
||||
init = webidl.converters.WebSocketCloseInfo(init);
|
||||
}
|
||||
|
||||
// 3. Let code be init [" closeCode "] if it exists , or null otherwise.
|
||||
let code = init.closeCode ?? null
|
||||
let code = init.closeCode ?? null;
|
||||
|
||||
// 4. Let reason be init [" reason "] if it exists , or the empty string otherwise.
|
||||
const reason = init.reason ?? ''
|
||||
const reason = init.reason ?? '';
|
||||
|
||||
// 5. Validate close code and reason with code and reason .
|
||||
validateCloseCodeAndReason(code, reason)
|
||||
validateCloseCodeAndReason(code, reason);
|
||||
|
||||
// 6. If reason is non-empty, but code is not set, then set code to 1000 ("Normal Closure").
|
||||
if (reason.length !== 0 && code === null) {
|
||||
code = 1000
|
||||
code = 1000;
|
||||
}
|
||||
|
||||
// 7. Set this 's closeCode to code .
|
||||
this.#closeCode = code
|
||||
this.#closeCode = code;
|
||||
|
||||
// 8. Set this 's reason to reason .
|
||||
this.#reason = reason
|
||||
this.#reason = reason;
|
||||
}
|
||||
|
||||
get closeCode () {
|
||||
return this.#closeCode
|
||||
get closeCode() {
|
||||
return this.#closeCode;
|
||||
}
|
||||
|
||||
get reason () {
|
||||
return this.#reason
|
||||
get reason() {
|
||||
return this.#reason;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,16 +56,16 @@ class WebSocketError extends DOMException {
|
||||
* @param {number|null} code
|
||||
* @param {string} reason
|
||||
*/
|
||||
static createUnvalidatedWebSocketError (message, code, reason) {
|
||||
const error = new WebSocketError(message, kConstruct)
|
||||
error.#closeCode = code
|
||||
error.#reason = reason
|
||||
return error
|
||||
static createUnvalidatedWebSocketError(message, code, reason) {
|
||||
const error = new WebSocketError(message, kConstruct);
|
||||
error.#closeCode = code;
|
||||
error.#reason = reason;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
const { createUnvalidatedWebSocketError } = WebSocketError
|
||||
delete WebSocketError.createUnvalidatedWebSocketError
|
||||
const { createUnvalidatedWebSocketError } = WebSocketError;
|
||||
delete WebSocketError.createUnvalidatedWebSocketError;
|
||||
|
||||
Object.defineProperties(WebSocketError.prototype, {
|
||||
closeCode: kEnumerableProperty,
|
||||
@ -74,10 +74,10 @@ Object.defineProperties(WebSocketError.prototype, {
|
||||
value: 'WebSocketError',
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
webidl.is.WebSocketError = webidl.util.MakeTypeAssertion(WebSocketError)
|
||||
webidl.is.WebSocketError = webidl.util.MakeTypeAssertion(WebSocketError);
|
||||
|
||||
module.exports = { WebSocketError, createUnvalidatedWebSocketError }
|
||||
module.exports = { WebSocketError, createUnvalidatedWebSocketError };
|
||||
|
390
node_modules/undici/lib/web/websocket/stream/websocketstream.js
generated
vendored
390
node_modules/undici/lib/web/websocket/stream/websocketstream.js
generated
vendored
@ -1,67 +1,84 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { createDeferredPromise, environmentSettingsObject } = require('../../fetch/util')
|
||||
const { states, opcodes, sentCloseFrameState } = require('../constants')
|
||||
const { webidl } = require('../../fetch/webidl')
|
||||
const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util')
|
||||
const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection')
|
||||
const { types } = require('node:util')
|
||||
const { channels } = require('../../../core/diagnostics')
|
||||
const { WebsocketFrameSend } = require('../frame')
|
||||
const { ByteParser } = require('../receiver')
|
||||
const { WebSocketError, createUnvalidatedWebSocketError } = require('./websocketerror')
|
||||
const { utf8DecodeBytes } = require('../../fetch/util')
|
||||
const { kEnumerableProperty } = require('../../../core/util')
|
||||
const {
|
||||
createDeferredPromise,
|
||||
environmentSettingsObject,
|
||||
} = require('../../fetch/util');
|
||||
const { states, opcodes, sentCloseFrameState } = require('../constants');
|
||||
const { webidl } = require('../../fetch/webidl');
|
||||
const {
|
||||
getURLRecord,
|
||||
isValidSubprotocol,
|
||||
isEstablished,
|
||||
utf8Decode,
|
||||
} = require('../util');
|
||||
const {
|
||||
establishWebSocketConnection,
|
||||
failWebsocketConnection,
|
||||
closeWebSocketConnection,
|
||||
} = require('../connection');
|
||||
const { types } = require('node:util');
|
||||
const { channels } = require('../../../core/diagnostics');
|
||||
const { WebsocketFrameSend } = require('../frame');
|
||||
const { ByteParser } = require('../receiver');
|
||||
const {
|
||||
WebSocketError,
|
||||
createUnvalidatedWebSocketError,
|
||||
} = require('./websocketerror');
|
||||
const { utf8DecodeBytes } = require('../../fetch/util');
|
||||
const { kEnumerableProperty } = require('../../../core/util');
|
||||
|
||||
let emittedExperimentalWarning = false
|
||||
let emittedExperimentalWarning = false;
|
||||
|
||||
class WebSocketStream {
|
||||
// Each WebSocketStream object has an associated url , which is a URL record .
|
||||
/** @type {URL} */
|
||||
#url
|
||||
#url;
|
||||
|
||||
// Each WebSocketStream object has an associated opened promise , which is a promise.
|
||||
/** @type {ReturnType<typeof createDeferredPromise>} */
|
||||
#openedPromise
|
||||
#openedPromise;
|
||||
|
||||
// Each WebSocketStream object has an associated closed promise , which is a promise.
|
||||
/** @type {ReturnType<typeof createDeferredPromise>} */
|
||||
#closedPromise
|
||||
#closedPromise;
|
||||
|
||||
// Each WebSocketStream object has an associated readable stream , which is a ReadableStream .
|
||||
/** @type {ReadableStream} */
|
||||
#readableStream
|
||||
#readableStream;
|
||||
/** @type {ReadableStreamDefaultController} */
|
||||
#readableStreamController
|
||||
#readableStreamController;
|
||||
|
||||
// Each WebSocketStream object has an associated writable stream , which is a WritableStream .
|
||||
/** @type {WritableStream} */
|
||||
#writableStream
|
||||
#writableStream;
|
||||
|
||||
// Each WebSocketStream object has an associated boolean handshake aborted , which is initially false.
|
||||
#handshakeAborted = false
|
||||
#handshakeAborted = false;
|
||||
|
||||
/** @type {import('../websocket').Handler} */
|
||||
#handler = {
|
||||
// https://whatpr.org/websockets/48/7b748d3...d5570f3.html#feedback-to-websocket-stream-from-the-protocol
|
||||
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
||||
onConnectionEstablished: (response, extensions) =>
|
||||
this.#onConnectionEstablished(response, extensions),
|
||||
onFail: (_code, _reason) => {},
|
||||
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
||||
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
||||
onParserError: (err) =>
|
||||
failWebsocketConnection(this.#handler, null, err.message),
|
||||
onParserDrain: () => this.#handler.socket.resume(),
|
||||
onSocketData: (chunk) => {
|
||||
if (!this.#parser.write(chunk)) {
|
||||
this.#handler.socket.pause()
|
||||
this.#handler.socket.pause();
|
||||
}
|
||||
},
|
||||
onSocketError: (err) => {
|
||||
this.#handler.readyState = states.CLOSING
|
||||
this.#handler.readyState = states.CLOSING;
|
||||
|
||||
if (channels.socketError.hasSubscribers) {
|
||||
channels.socketError.publish(err)
|
||||
channels.socketError.publish(err);
|
||||
}
|
||||
|
||||
this.#handler.socket.destroy()
|
||||
this.#handler.socket.destroy();
|
||||
},
|
||||
onSocketClose: () => this.#onSocketClose(),
|
||||
|
||||
@ -69,51 +86,65 @@ class WebSocketStream {
|
||||
socket: null,
|
||||
closeState: new Set(),
|
||||
controller: null,
|
||||
wasEverConnected: false
|
||||
}
|
||||
wasEverConnected: false,
|
||||
};
|
||||
|
||||
/** @type {import('../receiver').ByteParser} */
|
||||
#parser
|
||||
#parser;
|
||||
|
||||
constructor (url, options = undefined) {
|
||||
constructor(url, options = undefined) {
|
||||
if (!emittedExperimentalWarning) {
|
||||
process.emitWarning('WebSocketStream is experimental! Expect it to change at any time.', {
|
||||
code: 'UNDICI-WSS'
|
||||
})
|
||||
emittedExperimentalWarning = true
|
||||
process.emitWarning(
|
||||
'WebSocketStream is experimental! Expect it to change at any time.',
|
||||
{
|
||||
code: 'UNDICI-WSS',
|
||||
}
|
||||
);
|
||||
emittedExperimentalWarning = true;
|
||||
}
|
||||
|
||||
webidl.argumentLengthCheck(arguments, 1, 'WebSocket')
|
||||
webidl.argumentLengthCheck(arguments, 1, 'WebSocket');
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
url = webidl.converters.USVString(url);
|
||||
if (options !== null) {
|
||||
options = webidl.converters.WebSocketStreamOptions(options)
|
||||
options = webidl.converters.WebSocketStreamOptions(options);
|
||||
}
|
||||
|
||||
// 1. Let baseURL be this 's relevant settings object 's API base URL .
|
||||
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
||||
const baseURL = environmentSettingsObject.settingsObject.baseUrl;
|
||||
|
||||
// 2. Let urlRecord be the result of getting a URL record given url and baseURL .
|
||||
const urlRecord = getURLRecord(url, baseURL)
|
||||
const urlRecord = getURLRecord(url, baseURL);
|
||||
|
||||
// 3. Let protocols be options [" protocols "] if it exists , otherwise an empty sequence.
|
||||
const protocols = options.protocols
|
||||
const protocols = options.protocols;
|
||||
|
||||
// 4. If any of the values in protocols occur more than once or otherwise fail to match the requirements for elements that comprise the value of ` Sec-WebSocket-Protocol ` fields as defined by The WebSocket Protocol , then throw a " SyntaxError " DOMException . [WSP]
|
||||
if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
if (
|
||||
protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size
|
||||
) {
|
||||
throw new DOMException(
|
||||
'Invalid Sec-WebSocket-Protocol value',
|
||||
'SyntaxError'
|
||||
);
|
||||
}
|
||||
|
||||
if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
if (
|
||||
protocols.length > 0 &&
|
||||
!protocols.every((p) => isValidSubprotocol(p))
|
||||
) {
|
||||
throw new DOMException(
|
||||
'Invalid Sec-WebSocket-Protocol value',
|
||||
'SyntaxError'
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Set this 's url to urlRecord .
|
||||
this.#url = urlRecord.toString()
|
||||
this.#url = urlRecord.toString();
|
||||
|
||||
// 6. Set this 's opened promise and closed promise to new promises.
|
||||
this.#openedPromise = createDeferredPromise()
|
||||
this.#closedPromise = createDeferredPromise()
|
||||
this.#openedPromise = createDeferredPromise();
|
||||
this.#closedPromise = createDeferredPromise();
|
||||
|
||||
// 7. Apply backpressure to the WebSocket.
|
||||
// TODO
|
||||
@ -121,38 +152,42 @@ class WebSocketStream {
|
||||
// 8. If options [" signal "] exists ,
|
||||
if (options.signal != null) {
|
||||
// 8.1. Let signal be options [" signal "].
|
||||
const signal = options.signal
|
||||
const signal = options.signal;
|
||||
|
||||
// 8.2. If signal is aborted , then reject this 's opened promise and closed promise with signal ’s abort reason
|
||||
// and return.
|
||||
if (signal.aborted) {
|
||||
this.#openedPromise.reject(signal.reason)
|
||||
this.#closedPromise.reject(signal.reason)
|
||||
return
|
||||
this.#openedPromise.reject(signal.reason);
|
||||
this.#closedPromise.reject(signal.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
// 8.3. Add the following abort steps to signal :
|
||||
signal.addEventListener('abort', () => {
|
||||
// 8.3.1. If the WebSocket connection is not yet established : [WSP]
|
||||
if (!isEstablished(this.#handler.readyState)) {
|
||||
// 8.3.1.1. Fail the WebSocket connection .
|
||||
failWebsocketConnection(this.#handler)
|
||||
signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
// 8.3.1. If the WebSocket connection is not yet established : [WSP]
|
||||
if (!isEstablished(this.#handler.readyState)) {
|
||||
// 8.3.1.1. Fail the WebSocket connection .
|
||||
failWebsocketConnection(this.#handler);
|
||||
|
||||
// Set this 's ready state to CLOSING .
|
||||
this.#handler.readyState = states.CLOSING
|
||||
// Set this 's ready state to CLOSING .
|
||||
this.#handler.readyState = states.CLOSING;
|
||||
|
||||
// Reject this 's opened promise and closed promise with signal ’s abort reason .
|
||||
this.#openedPromise.reject(signal.reason)
|
||||
this.#closedPromise.reject(signal.reason)
|
||||
// Reject this 's opened promise and closed promise with signal ’s abort reason .
|
||||
this.#openedPromise.reject(signal.reason);
|
||||
this.#closedPromise.reject(signal.reason);
|
||||
|
||||
// Set this 's handshake aborted to true.
|
||||
this.#handshakeAborted = true
|
||||
}
|
||||
}, { once: true })
|
||||
// Set this 's handshake aborted to true.
|
||||
this.#handshakeAborted = true;
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
|
||||
// 9. Let client be this 's relevant settings object .
|
||||
const client = environmentSettingsObject.settingsObject
|
||||
const client = environmentSettingsObject.settingsObject;
|
||||
|
||||
// 10. Run this step in parallel :
|
||||
// 10.1. Establish a WebSocket connection given urlRecord , protocols , and client . [FETCH]
|
||||
@ -162,115 +197,122 @@ class WebSocketStream {
|
||||
client,
|
||||
this.#handler,
|
||||
options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// The url getter steps are to return this 's url , serialized .
|
||||
get url () {
|
||||
return this.#url.toString()
|
||||
get url() {
|
||||
return this.#url.toString();
|
||||
}
|
||||
|
||||
// The opened getter steps are to return this 's opened promise .
|
||||
get opened () {
|
||||
return this.#openedPromise.promise
|
||||
get opened() {
|
||||
return this.#openedPromise.promise;
|
||||
}
|
||||
|
||||
// The closed getter steps are to return this 's closed promise .
|
||||
get closed () {
|
||||
return this.#closedPromise.promise
|
||||
get closed() {
|
||||
return this.#closedPromise.promise;
|
||||
}
|
||||
|
||||
// The close( closeInfo ) method steps are:
|
||||
close (closeInfo = undefined) {
|
||||
close(closeInfo = undefined) {
|
||||
if (closeInfo !== null) {
|
||||
closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo)
|
||||
closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo);
|
||||
}
|
||||
|
||||
// 1. Let code be closeInfo [" closeCode "] if present, or null otherwise.
|
||||
const code = closeInfo.closeCode ?? null
|
||||
const code = closeInfo.closeCode ?? null;
|
||||
|
||||
// 2. Let reason be closeInfo [" reason "].
|
||||
const reason = closeInfo.reason
|
||||
const reason = closeInfo.reason;
|
||||
|
||||
// 3. Close the WebSocket with this , code , and reason .
|
||||
closeWebSocketConnection(this.#handler, code, reason, true)
|
||||
closeWebSocketConnection(this.#handler, code, reason, true);
|
||||
}
|
||||
|
||||
#write (chunk) {
|
||||
#write(chunk) {
|
||||
// 1. Let promise be a new promise created in stream ’s relevant realm .
|
||||
const promise = createDeferredPromise()
|
||||
const promise = createDeferredPromise();
|
||||
|
||||
// 2. Let data be null.
|
||||
let data = null
|
||||
let data = null;
|
||||
|
||||
// 3. Let opcode be null.
|
||||
let opcode = null
|
||||
let opcode = null;
|
||||
|
||||
// 4. If chunk is a BufferSource ,
|
||||
if (ArrayBuffer.isView(chunk) || types.isArrayBuffer(chunk)) {
|
||||
// 4.1. Set data to a copy of the bytes given chunk .
|
||||
data = new Uint8Array(ArrayBuffer.isView(chunk) ? new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength) : chunk)
|
||||
data = new Uint8Array(
|
||||
ArrayBuffer.isView(chunk) ?
|
||||
new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)
|
||||
: chunk
|
||||
);
|
||||
|
||||
// 4.2. Set opcode to a binary frame opcode.
|
||||
opcode = opcodes.BINARY
|
||||
opcode = opcodes.BINARY;
|
||||
} else {
|
||||
// 5. Otherwise,
|
||||
|
||||
// 5.1. Let string be the result of converting chunk to an IDL USVString .
|
||||
// If this throws an exception, return a promise rejected with the exception.
|
||||
let string
|
||||
let string;
|
||||
|
||||
try {
|
||||
string = webidl.converters.DOMString(chunk)
|
||||
string = webidl.converters.DOMString(chunk);
|
||||
} catch (e) {
|
||||
promise.reject(e)
|
||||
return
|
||||
promise.reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5.2. Set data to the result of UTF-8 encoding string .
|
||||
data = new TextEncoder().encode(string)
|
||||
data = new TextEncoder().encode(string);
|
||||
|
||||
// 5.3. Set opcode to a text frame opcode.
|
||||
opcode = opcodes.TEXT
|
||||
opcode = opcodes.TEXT;
|
||||
}
|
||||
|
||||
// 6. In parallel,
|
||||
// 6.1. Wait until there is sufficient buffer space in stream to send the message.
|
||||
|
||||
// 6.2. If the closing handshake has not yet started , Send a WebSocket Message to stream comprised of data using opcode .
|
||||
if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
const frame = new WebsocketFrameSend(data)
|
||||
if (
|
||||
!this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
) {
|
||||
const frame = new WebsocketFrameSend(data);
|
||||
|
||||
this.#handler.socket.write(frame.createFrame(opcode), () => {
|
||||
promise.resolve(undefined)
|
||||
})
|
||||
promise.resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
// 6.3. Queue a global task on the WebSocket task source given stream ’s relevant global object to resolve promise with undefined.
|
||||
return promise
|
||||
return promise;
|
||||
}
|
||||
|
||||
/** @type {import('../websocket').Handler['onConnectionEstablished']} */
|
||||
#onConnectionEstablished (response, parsedExtensions) {
|
||||
this.#handler.socket = response.socket
|
||||
#onConnectionEstablished(response, parsedExtensions) {
|
||||
this.#handler.socket = response.socket;
|
||||
|
||||
const parser = new ByteParser(this.#handler, parsedExtensions)
|
||||
parser.on('drain', () => this.#handler.onParserDrain())
|
||||
parser.on('error', (err) => this.#handler.onParserError(err))
|
||||
const parser = new ByteParser(this.#handler, parsedExtensions);
|
||||
parser.on('drain', () => this.#handler.onParserDrain());
|
||||
parser.on('error', (err) => this.#handler.onParserError(err));
|
||||
|
||||
this.#parser = parser
|
||||
this.#parser = parser;
|
||||
|
||||
// 1. Change stream ’s ready state to OPEN (1).
|
||||
this.#handler.readyState = states.OPEN
|
||||
this.#handler.readyState = states.OPEN;
|
||||
|
||||
// 2. Set stream ’s was ever connected to true.
|
||||
// This is done in the opening handshake.
|
||||
|
||||
// 3. Let extensions be the extensions in use .
|
||||
const extensions = parsedExtensions ?? ''
|
||||
const extensions = parsedExtensions ?? '';
|
||||
|
||||
// 4. Let protocol be the subprotocol in use .
|
||||
const protocol = response.headersList.get('sec-websocket-protocol') ?? ''
|
||||
const protocol = response.headersList.get('sec-websocket-protocol') ?? '';
|
||||
|
||||
// 5. Let pullAlgorithm be an action that pulls bytes from stream .
|
||||
// 6. Let cancelAlgorithm be an action that cancels stream with reason , given reason .
|
||||
@ -278,16 +320,19 @@ class WebSocketStream {
|
||||
// 8. Set up readable with pullAlgorithm and cancelAlgorithm .
|
||||
const readable = new ReadableStream({
|
||||
start: (controller) => {
|
||||
this.#readableStreamController = controller
|
||||
this.#readableStreamController = controller;
|
||||
},
|
||||
pull (controller) {
|
||||
let chunk
|
||||
while (controller.desiredSize > 0 && (chunk = response.socket.read()) !== null) {
|
||||
controller.enqueue(chunk)
|
||||
pull(controller) {
|
||||
let chunk;
|
||||
while (
|
||||
controller.desiredSize > 0 &&
|
||||
(chunk = response.socket.read()) !== null
|
||||
) {
|
||||
controller.enqueue(chunk);
|
||||
}
|
||||
},
|
||||
cancel: (reason) => this.#cancel(reason)
|
||||
})
|
||||
cancel: (reason) => this.#cancel(reason),
|
||||
});
|
||||
|
||||
// 9. Let writeAlgorithm be an action that writes chunk to stream , given chunk .
|
||||
// 10. Let closeAlgorithm be an action that closes stream .
|
||||
@ -297,29 +342,29 @@ class WebSocketStream {
|
||||
const writable = new WritableStream({
|
||||
write: (chunk) => this.#write(chunk),
|
||||
close: () => closeWebSocketConnection(this.#handler, null, null),
|
||||
abort: (reason) => this.#closeUsingReason(reason)
|
||||
})
|
||||
abort: (reason) => this.#closeUsingReason(reason),
|
||||
});
|
||||
|
||||
// Set stream ’s readable stream to readable .
|
||||
this.#readableStream = readable
|
||||
this.#readableStream = readable;
|
||||
|
||||
// Set stream ’s writable stream to writable .
|
||||
this.#writableStream = writable
|
||||
this.#writableStream = writable;
|
||||
|
||||
// Resolve stream ’s opened promise with WebSocketOpenInfo «[ " extensions " → extensions , " protocol " → protocol , " readable " → readable , " writable " → writable ]».
|
||||
this.#openedPromise.resolve({
|
||||
extensions,
|
||||
protocol,
|
||||
readable,
|
||||
writable
|
||||
})
|
||||
writable,
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {import('../websocket').Handler['onMessage']} */
|
||||
#onMessage (type, data) {
|
||||
#onMessage(type, data) {
|
||||
// 1. If stream’s ready state is not OPEN (1), then return.
|
||||
if (this.#handler.readyState !== states.OPEN) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Let chunk be determined by switching on type:
|
||||
@ -328,45 +373,48 @@ class WebSocketStream {
|
||||
// - type indicates that the data is Binary
|
||||
// a new Uint8Array object, created in the relevant Realm of the
|
||||
// WebSocketStream object, whose contents are data
|
||||
let chunk
|
||||
let chunk;
|
||||
|
||||
if (type === opcodes.TEXT) {
|
||||
try {
|
||||
chunk = utf8Decode(data)
|
||||
chunk = utf8Decode(data);
|
||||
} catch {
|
||||
failWebsocketConnection(this.#handler, 'Received invalid UTF-8 in text frame.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
'Received invalid UTF-8 in text frame.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (type === opcodes.BINARY) {
|
||||
chunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
||||
chunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
|
||||
// 3. Enqueue chunk into stream’s readable stream.
|
||||
this.#readableStreamController.enqueue(chunk)
|
||||
this.#readableStreamController.enqueue(chunk);
|
||||
|
||||
// 4. Apply backpressure to the WebSocket.
|
||||
}
|
||||
|
||||
/** @type {import('../websocket').Handler['onSocketClose']} */
|
||||
#onSocketClose () {
|
||||
#onSocketClose() {
|
||||
const wasClean =
|
||||
this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
this.#handler.closeState.has(sentCloseFrameState.RECEIVED);
|
||||
|
||||
// 1. Change the ready state to CLOSED (3).
|
||||
this.#handler.readyState = states.CLOSED
|
||||
this.#handler.readyState = states.CLOSED;
|
||||
|
||||
// 2. If stream ’s handshake aborted is true, then return.
|
||||
if (this.#handshakeAborted) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. If stream ’s was ever connected is false, then reject stream ’s opened promise with a new WebSocketError.
|
||||
if (!this.#handler.wasEverConnected) {
|
||||
this.#openedPromise.reject(new WebSocketError('Socket never opened'))
|
||||
this.#openedPromise.reject(new WebSocketError('Socket never opened'));
|
||||
}
|
||||
|
||||
const result = this.#parser.closingInfo
|
||||
const result = this.#parser.closingInfo;
|
||||
|
||||
// 4. Let code be the WebSocket connection close code .
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
|
||||
@ -376,71 +424,84 @@ class WebSocketStream {
|
||||
// endpoint (such as could occur if the underlying transport connection
|
||||
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
||||
// 1006.
|
||||
let code = result?.code ?? 1005
|
||||
let code = result?.code ?? 1005;
|
||||
|
||||
if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
code = 1006
|
||||
if (
|
||||
!this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
) {
|
||||
code = 1006;
|
||||
}
|
||||
|
||||
// 5. Let reason be the result of applying UTF-8 decode without BOM to the WebSocket connection close reason .
|
||||
const reason = result?.reason == null ? '' : utf8DecodeBytes(Buffer.from(result.reason))
|
||||
const reason =
|
||||
result?.reason == null ? '' : utf8DecodeBytes(Buffer.from(result.reason));
|
||||
|
||||
// 6. If the connection was closed cleanly ,
|
||||
if (wasClean) {
|
||||
// 6.1. Close stream ’s readable stream .
|
||||
this.#readableStream.cancel().catch(() => {})
|
||||
this.#readableStream.cancel().catch(() => {});
|
||||
|
||||
// 6.2. Error stream ’s writable stream with an " InvalidStateError " DOMException indicating that a closed WebSocketStream cannot be written to.
|
||||
if (!this.#writableStream.locked) {
|
||||
this.#writableStream.abort(new DOMException('A closed WebSocketStream cannot be written to', 'InvalidStateError'))
|
||||
this.#writableStream.abort(
|
||||
new DOMException(
|
||||
'A closed WebSocketStream cannot be written to',
|
||||
'InvalidStateError'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 6.3. Resolve stream ’s closed promise with WebSocketCloseInfo «[ " closeCode " → code , " reason " → reason ]».
|
||||
this.#closedPromise.resolve({
|
||||
closeCode: code,
|
||||
reason
|
||||
})
|
||||
reason,
|
||||
});
|
||||
} else {
|
||||
// 7. Otherwise,
|
||||
|
||||
// 7.1. Let error be a new WebSocketError whose closeCode is code and reason is reason .
|
||||
const error = createUnvalidatedWebSocketError('unclean close', code, reason)
|
||||
const error = createUnvalidatedWebSocketError(
|
||||
'unclean close',
|
||||
code,
|
||||
reason
|
||||
);
|
||||
|
||||
// 7.2. Error stream ’s readable stream with error .
|
||||
this.#readableStreamController.error(error)
|
||||
this.#readableStreamController.error(error);
|
||||
|
||||
// 7.3. Error stream ’s writable stream with error .
|
||||
this.#writableStream.abort(error)
|
||||
this.#writableStream.abort(error);
|
||||
|
||||
// 7.4. Reject stream ’s closed promise with error .
|
||||
this.#closedPromise.reject(error)
|
||||
this.#closedPromise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
#closeUsingReason (reason) {
|
||||
#closeUsingReason(reason) {
|
||||
// 1. Let code be null.
|
||||
let code = null
|
||||
let code = null;
|
||||
|
||||
// 2. Let reasonString be the empty string.
|
||||
let reasonString = ''
|
||||
let reasonString = '';
|
||||
|
||||
// 3. If reason implements WebSocketError ,
|
||||
if (webidl.is.WebSocketError(reason)) {
|
||||
// 3.1. Set code to reason ’s closeCode .
|
||||
code = reason.closeCode
|
||||
code = reason.closeCode;
|
||||
|
||||
// 3.2. Set reasonString to reason ’s reason .
|
||||
reasonString = reason.reason
|
||||
reasonString = reason.reason;
|
||||
}
|
||||
|
||||
// 4. Close the WebSocket with stream , code , and reasonString . If this throws an exception,
|
||||
// discard code and reasonString and close the WebSocket with stream .
|
||||
closeWebSocketConnection(this.#handler, code, reasonString)
|
||||
closeWebSocketConnection(this.#handler, code, reasonString);
|
||||
}
|
||||
|
||||
// To cancel a WebSocketStream stream given reason , close using reason giving stream and reason .
|
||||
#cancel (reason) {
|
||||
this.#closeUsingReason(reason)
|
||||
#cancel(reason) {
|
||||
this.#closeUsingReason(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,33 +514,34 @@ Object.defineProperties(WebSocketStream.prototype, {
|
||||
value: 'WebSocketStream',
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
webidl.converters.WebSocketStreamOptions = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'protocols',
|
||||
converter: webidl.sequenceConverter(webidl.converters.USVString),
|
||||
defaultValue: () => []
|
||||
defaultValue: () => [],
|
||||
},
|
||||
{
|
||||
key: 'signal',
|
||||
converter: webidl.nullableConverter(webidl.converters.AbortSignal),
|
||||
defaultValue: () => null
|
||||
}
|
||||
])
|
||||
defaultValue: () => null,
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.converters.WebSocketCloseInfo = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'closeCode',
|
||||
converter: (V) => webidl.converters['unsigned short'](V, { enforceRange: true })
|
||||
converter: (V) =>
|
||||
webidl.converters['unsigned short'](V, { enforceRange: true }),
|
||||
},
|
||||
{
|
||||
key: 'reason',
|
||||
converter: webidl.converters.USVString,
|
||||
defaultValue: () => ''
|
||||
}
|
||||
])
|
||||
defaultValue: () => '',
|
||||
},
|
||||
]);
|
||||
|
||||
module.exports = { WebSocketStream }
|
||||
module.exports = { WebSocketStream };
|
||||
|
181
node_modules/undici/lib/web/websocket/util.js
generated
vendored
181
node_modules/undici/lib/web/websocket/util.js
generated
vendored
@ -1,47 +1,50 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { states, opcodes } = require('./constants')
|
||||
const { isUtf8 } = require('node:buffer')
|
||||
const { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require('../fetch/data-url')
|
||||
const { states, opcodes } = require('./constants');
|
||||
const { isUtf8 } = require('node:buffer');
|
||||
const {
|
||||
collectASequenceOfCodePointsFast,
|
||||
removeHTTPWhitespace,
|
||||
} = require('../fetch/data-url');
|
||||
|
||||
/**
|
||||
* @param {number} readyState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isConnecting (readyState) {
|
||||
function isConnecting(readyState) {
|
||||
// If the WebSocket connection is not yet established, and the connection
|
||||
// is not yet closed, then the WebSocket connection is in the CONNECTING state.
|
||||
return readyState === states.CONNECTING
|
||||
return readyState === states.CONNECTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} readyState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isEstablished (readyState) {
|
||||
function isEstablished(readyState) {
|
||||
// If the server's response is validated as provided for above, it is
|
||||
// said that _The WebSocket Connection is Established_ and that the
|
||||
// WebSocket Connection is in the OPEN state.
|
||||
return readyState === states.OPEN
|
||||
return readyState === states.OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} readyState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isClosing (readyState) {
|
||||
function isClosing(readyState) {
|
||||
// Upon either sending or receiving a Close control frame, it is said
|
||||
// that _The WebSocket Closing Handshake is Started_ and that the
|
||||
// WebSocket connection is in the CLOSING state.
|
||||
return readyState === states.CLOSING
|
||||
return readyState === states.CLOSING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} readyState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isClosed (readyState) {
|
||||
return readyState === states.CLOSED
|
||||
function isClosed(readyState) {
|
||||
return readyState === states.CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,20 +55,25 @@ function isClosed (readyState) {
|
||||
* @param {EventInit | undefined} eventInitDict
|
||||
* @returns {void}
|
||||
*/
|
||||
function fireEvent (e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) {
|
||||
function fireEvent(
|
||||
e,
|
||||
target,
|
||||
eventFactory = (type, init) => new Event(type, init),
|
||||
eventInitDict = {}
|
||||
) {
|
||||
// 1. If eventConstructor is not given, then let eventConstructor be Event.
|
||||
|
||||
// 2. Let event be the result of creating an event given eventConstructor,
|
||||
// in the relevant realm of target.
|
||||
// 3. Initialize event’s type attribute to e.
|
||||
const event = eventFactory(e, eventInitDict)
|
||||
const event = eventFactory(e, eventInitDict);
|
||||
|
||||
// 4. Initialize any other IDL attributes of event as described in the
|
||||
// invocation of this algorithm.
|
||||
|
||||
// 5. Return the result of dispatching event at target, with legacy target
|
||||
// override flag set if set.
|
||||
target.dispatchEvent(event)
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,19 +83,19 @@ function fireEvent (e, target, eventFactory = (type, init) => new Event(type, in
|
||||
* @param {Buffer} data application data
|
||||
* @returns {void}
|
||||
*/
|
||||
function websocketMessageReceived (handler, type, data) {
|
||||
handler.onMessage(type, data)
|
||||
function websocketMessageReceived(handler, type, data) {
|
||||
handler.onMessage(type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} buffer
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
function toArrayBuffer (buffer) {
|
||||
function toArrayBuffer(buffer) {
|
||||
if (buffer.byteLength === buffer.buffer.byteLength) {
|
||||
return buffer.buffer
|
||||
return buffer.buffer;
|
||||
}
|
||||
return new Uint8Array(buffer).buffer
|
||||
return new Uint8Array(buffer).buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +105,7 @@ function toArrayBuffer (buffer) {
|
||||
* @param {string} protocol
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidSubprotocol (protocol) {
|
||||
function isValidSubprotocol(protocol) {
|
||||
// If present, this value indicates one
|
||||
// or more comma-separated subprotocol the client wishes to speak,
|
||||
// ordered by preference. The elements that comprise this value
|
||||
@ -105,38 +113,38 @@ function isValidSubprotocol (protocol) {
|
||||
// U+007E not including separator characters as defined in
|
||||
// [RFC2616] and MUST all be unique strings.
|
||||
if (protocol.length === 0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < protocol.length; ++i) {
|
||||
const code = protocol.charCodeAt(i)
|
||||
const code = protocol.charCodeAt(i);
|
||||
|
||||
if (
|
||||
code < 0x21 || // CTL, contains SP (0x20) and HT (0x09)
|
||||
code > 0x7E ||
|
||||
code > 0x7e ||
|
||||
code === 0x22 || // "
|
||||
code === 0x28 || // (
|
||||
code === 0x29 || // )
|
||||
code === 0x2C || // ,
|
||||
code === 0x2F || // /
|
||||
code === 0x3A || // :
|
||||
code === 0x3B || // ;
|
||||
code === 0x3C || // <
|
||||
code === 0x3D || // =
|
||||
code === 0x3E || // >
|
||||
code === 0x3F || // ?
|
||||
code === 0x2c || // ,
|
||||
code === 0x2f || // /
|
||||
code === 0x3a || // :
|
||||
code === 0x3b || // ;
|
||||
code === 0x3c || // <
|
||||
code === 0x3d || // =
|
||||
code === 0x3e || // >
|
||||
code === 0x3f || // ?
|
||||
code === 0x40 || // @
|
||||
code === 0x5B || // [
|
||||
code === 0x5C || // \
|
||||
code === 0x5D || // ]
|
||||
code === 0x7B || // {
|
||||
code === 0x7D // }
|
||||
code === 0x5b || // [
|
||||
code === 0x5c || // \
|
||||
code === 0x5d || // ]
|
||||
code === 0x7b || // {
|
||||
code === 0x7d // }
|
||||
) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,16 +152,16 @@ function isValidSubprotocol (protocol) {
|
||||
* @param {number} code
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidStatusCode (code) {
|
||||
function isValidStatusCode(code) {
|
||||
if (code >= 1000 && code < 1015) {
|
||||
return (
|
||||
code !== 1004 && // reserved
|
||||
code !== 1005 && // "MUST NOT be set as a status code"
|
||||
code !== 1006 // "MUST NOT be set as a status code"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return code >= 3000 && code <= 4999
|
||||
return code >= 3000 && code <= 4999;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,28 +169,28 @@ function isValidStatusCode (code) {
|
||||
* @param {number} opcode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isControlFrame (opcode) {
|
||||
function isControlFrame(opcode) {
|
||||
return (
|
||||
opcode === opcodes.CLOSE ||
|
||||
opcode === opcodes.PING ||
|
||||
opcode === opcodes.PONG
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} opcode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isContinuationFrame (opcode) {
|
||||
return opcode === opcodes.CONTINUATION
|
||||
function isContinuationFrame(opcode) {
|
||||
return opcode === opcodes.CONTINUATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} opcode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTextBinaryFrame (opcode) {
|
||||
return opcode === opcodes.TEXT || opcode === opcodes.BINARY
|
||||
function isTextBinaryFrame(opcode) {
|
||||
return opcode === opcodes.TEXT || opcode === opcodes.BINARY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,8 +198,12 @@ function isTextBinaryFrame (opcode) {
|
||||
* @param {number} opcode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidOpcode (opcode) {
|
||||
return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
|
||||
function isValidOpcode(opcode) {
|
||||
return (
|
||||
isTextBinaryFrame(opcode) ||
|
||||
isContinuationFrame(opcode) ||
|
||||
isControlFrame(opcode)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,23 +212,23 @@ function isValidOpcode (opcode) {
|
||||
* @returns {Map<string, string>}
|
||||
*/
|
||||
// TODO(@Uzlopak, @KhafraDev): make compliant https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
|
||||
function parseExtensions (extensions) {
|
||||
const position = { position: 0 }
|
||||
const extensionList = new Map()
|
||||
function parseExtensions(extensions) {
|
||||
const position = { position: 0 };
|
||||
const extensionList = new Map();
|
||||
|
||||
while (position.position < extensions.length) {
|
||||
const pair = collectASequenceOfCodePointsFast(';', extensions, position)
|
||||
const [name, value = ''] = pair.split('=', 2)
|
||||
const pair = collectASequenceOfCodePointsFast(';', extensions, position);
|
||||
const [name, value = ''] = pair.split('=', 2);
|
||||
|
||||
extensionList.set(
|
||||
removeHTTPWhitespace(name, true, false),
|
||||
removeHTTPWhitespace(value, false, true)
|
||||
)
|
||||
);
|
||||
|
||||
position.position++
|
||||
position.position++;
|
||||
}
|
||||
|
||||
return extensionList
|
||||
return extensionList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,16 +237,16 @@ function parseExtensions (extensions) {
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidClientWindowBits (value) {
|
||||
function isValidClientWindowBits(value) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const byte = value.charCodeAt(i)
|
||||
const byte = value.charCodeAt(i);
|
||||
|
||||
if (byte < 0x30 || byte > 0x39) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,47 +254,47 @@ function isValidClientWindowBits (value) {
|
||||
* @param {string} url
|
||||
* @param {string} [baseURL]
|
||||
*/
|
||||
function getURLRecord (url, baseURL) {
|
||||
function getURLRecord(url, baseURL) {
|
||||
// 1. Let urlRecord be the result of applying the URL parser to url with baseURL .
|
||||
// 2. If urlRecord is failure, then throw a " SyntaxError " DOMException .
|
||||
let urlRecord
|
||||
let urlRecord;
|
||||
|
||||
try {
|
||||
urlRecord = new URL(url, baseURL)
|
||||
urlRecord = new URL(url, baseURL);
|
||||
} catch (e) {
|
||||
throw new DOMException(e, 'SyntaxError')
|
||||
throw new DOMException(e, 'SyntaxError');
|
||||
}
|
||||
|
||||
// 3. If urlRecord ’s scheme is " http ", then set urlRecord ’s scheme to " ws ".
|
||||
// 4. Otherwise, if urlRecord ’s scheme is " https ", set urlRecord ’s scheme to " wss ".
|
||||
if (urlRecord.protocol === 'http:') {
|
||||
urlRecord.protocol = 'ws:'
|
||||
urlRecord.protocol = 'ws:';
|
||||
} else if (urlRecord.protocol === 'https:') {
|
||||
urlRecord.protocol = 'wss:'
|
||||
urlRecord.protocol = 'wss:';
|
||||
}
|
||||
|
||||
// 5. If urlRecord ’s scheme is not " ws " or " wss ", then throw a " SyntaxError " DOMException .
|
||||
if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
|
||||
throw new DOMException('expected a ws: or wss: url', 'SyntaxError')
|
||||
throw new DOMException('expected a ws: or wss: url', 'SyntaxError');
|
||||
}
|
||||
|
||||
// If urlRecord ’s fragment is non-null, then throw a " SyntaxError " DOMException .
|
||||
if (urlRecord.hash.length || urlRecord.href.endsWith('#')) {
|
||||
throw new DOMException('hash', 'SyntaxError')
|
||||
throw new DOMException('hash', 'SyntaxError');
|
||||
}
|
||||
|
||||
// Return urlRecord .
|
||||
return urlRecord
|
||||
return urlRecord;
|
||||
}
|
||||
|
||||
// https://whatpr.org/websockets/48.html#validate-close-code-and-reason
|
||||
function validateCloseCodeAndReason (code, reason) {
|
||||
function validateCloseCodeAndReason(code, reason) {
|
||||
// 1. If code is not null, but is neither an integer equal to
|
||||
// 1000 nor an integer in the range 3000 to 4999, inclusive,
|
||||
// throw an "InvalidAccessError" DOMException.
|
||||
if (code !== null) {
|
||||
if (code !== 1000 && (code < 3000 || code > 4999)) {
|
||||
throw new DOMException('invalid code', 'InvalidAccessError')
|
||||
throw new DOMException('invalid code', 'InvalidAccessError');
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,10 +303,13 @@ function validateCloseCodeAndReason (code, reason) {
|
||||
// 2.1. Let reasonBytes be the result of UTF-8 encoding reason.
|
||||
// 2.2. If reasonBytes is longer than 123 bytes, then throw a
|
||||
// "SyntaxError" DOMException.
|
||||
const reasonBytesLength = Buffer.byteLength(reason)
|
||||
const reasonBytesLength = Buffer.byteLength(reason);
|
||||
|
||||
if (reasonBytesLength > 123) {
|
||||
throw new DOMException(`Reason must be less than 123 bytes; received ${reasonBytesLength}`, 'SyntaxError')
|
||||
throw new DOMException(
|
||||
`Reason must be less than 123 bytes; received ${reasonBytesLength}`,
|
||||
'SyntaxError'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,16 +320,16 @@ function validateCloseCodeAndReason (code, reason) {
|
||||
*/
|
||||
const utf8Decode = (() => {
|
||||
if (typeof process.versions.icu === 'string') {
|
||||
const fatalDecoder = new TextDecoder('utf-8', { fatal: true })
|
||||
return fatalDecoder.decode.bind(fatalDecoder)
|
||||
const fatalDecoder = new TextDecoder('utf-8', { fatal: true });
|
||||
return fatalDecoder.decode.bind(fatalDecoder);
|
||||
}
|
||||
return function (buffer) {
|
||||
if (isUtf8(buffer)) {
|
||||
return buffer.toString('utf-8')
|
||||
return buffer.toString('utf-8');
|
||||
}
|
||||
throw new TypeError('Invalid utf-8 received.')
|
||||
}
|
||||
})()
|
||||
throw new TypeError('Invalid utf-8 received.');
|
||||
};
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
isConnecting,
|
||||
@ -334,5 +349,5 @@ module.exports = {
|
||||
isValidClientWindowBits,
|
||||
toArrayBuffer,
|
||||
getURLRecord,
|
||||
validateCloseCodeAndReason
|
||||
}
|
||||
validateCloseCodeAndReason,
|
||||
};
|
||||
|
505
node_modules/undici/lib/web/websocket/websocket.js
generated
vendored
505
node_modules/undici/lib/web/websocket/websocket.js
generated
vendored
@ -1,9 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { URLSerializer } = require('../fetch/data-url')
|
||||
const { environmentSettingsObject } = require('../fetch/util')
|
||||
const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')
|
||||
const { webidl } = require('../fetch/webidl');
|
||||
const { URLSerializer } = require('../fetch/data-url');
|
||||
const { environmentSettingsObject } = require('../fetch/util');
|
||||
const {
|
||||
staticPropertyDescriptors,
|
||||
states,
|
||||
sentCloseFrameState,
|
||||
sendHints,
|
||||
opcodes,
|
||||
} = require('./constants');
|
||||
const {
|
||||
isConnecting,
|
||||
isEstablished,
|
||||
@ -12,16 +18,20 @@ const {
|
||||
fireEvent,
|
||||
utf8Decode,
|
||||
toArrayBuffer,
|
||||
getURLRecord
|
||||
} = require('./util')
|
||||
const { establishWebSocketConnection, closeWebSocketConnection, failWebsocketConnection } = require('./connection')
|
||||
const { ByteParser } = require('./receiver')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { getGlobalDispatcher } = require('../../global')
|
||||
const { types } = require('node:util')
|
||||
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
|
||||
const { SendQueue } = require('./sender')
|
||||
const { channels } = require('../../core/diagnostics')
|
||||
getURLRecord,
|
||||
} = require('./util');
|
||||
const {
|
||||
establishWebSocketConnection,
|
||||
closeWebSocketConnection,
|
||||
failWebsocketConnection,
|
||||
} = require('./connection');
|
||||
const { ByteParser } = require('./receiver');
|
||||
const { kEnumerableProperty } = require('../../core/util');
|
||||
const { getGlobalDispatcher } = require('../../global');
|
||||
const { types } = require('node:util');
|
||||
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events');
|
||||
const { SendQueue } = require('./sender');
|
||||
const { channels } = require('../../core/diagnostics');
|
||||
|
||||
/**
|
||||
* @typedef {object} Handler
|
||||
@ -47,36 +57,38 @@ class WebSocket extends EventTarget {
|
||||
open: null,
|
||||
error: null,
|
||||
close: null,
|
||||
message: null
|
||||
}
|
||||
message: null,
|
||||
};
|
||||
|
||||
#bufferedAmount = 0
|
||||
#protocol = ''
|
||||
#extensions = ''
|
||||
#bufferedAmount = 0;
|
||||
#protocol = '';
|
||||
#extensions = '';
|
||||
|
||||
/** @type {SendQueue} */
|
||||
#sendQueue
|
||||
#sendQueue;
|
||||
|
||||
/** @type {Handler} */
|
||||
#handler = {
|
||||
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
||||
onConnectionEstablished: (response, extensions) =>
|
||||
this.#onConnectionEstablished(response, extensions),
|
||||
onFail: (code, reason) => this.#onFail(code, reason),
|
||||
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
||||
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
||||
onParserError: (err) =>
|
||||
failWebsocketConnection(this.#handler, null, err.message),
|
||||
onParserDrain: () => this.#onParserDrain(),
|
||||
onSocketData: (chunk) => {
|
||||
if (!this.#parser.write(chunk)) {
|
||||
this.#handler.socket.pause()
|
||||
this.#handler.socket.pause();
|
||||
}
|
||||
},
|
||||
onSocketError: (err) => {
|
||||
this.#handler.readyState = states.CLOSING
|
||||
this.#handler.readyState = states.CLOSING;
|
||||
|
||||
if (channels.socketError.hasSubscribers) {
|
||||
channels.socketError.publish(err)
|
||||
channels.socketError.publish(err);
|
||||
}
|
||||
|
||||
this.#handler.socket.destroy()
|
||||
this.#handler.socket.destroy();
|
||||
},
|
||||
onSocketClose: () => this.#onSocketClose(),
|
||||
|
||||
@ -84,60 +96,73 @@ class WebSocket extends EventTarget {
|
||||
socket: null,
|
||||
closeState: new Set(),
|
||||
controller: null,
|
||||
wasEverConnected: false
|
||||
}
|
||||
wasEverConnected: false,
|
||||
};
|
||||
|
||||
#url
|
||||
#binaryType
|
||||
#url;
|
||||
#binaryType;
|
||||
/** @type {import('./receiver').ByteParser} */
|
||||
#parser
|
||||
#parser;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string|string[]} protocols
|
||||
*/
|
||||
constructor (url, protocols = []) {
|
||||
super()
|
||||
constructor(url, protocols = []) {
|
||||
super();
|
||||
|
||||
webidl.util.markAsUncloneable(this)
|
||||
webidl.util.markAsUncloneable(this);
|
||||
|
||||
const prefix = 'WebSocket constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'WebSocket constructor';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
||||
const options = webidl.converters[
|
||||
'DOMString or sequence<DOMString> or WebSocketInit'
|
||||
](protocols, prefix, 'options');
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
protocols = options.protocols
|
||||
url = webidl.converters.USVString(url);
|
||||
protocols = options.protocols;
|
||||
|
||||
// 1. Let baseURL be this's relevant settings object's API base URL.
|
||||
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
||||
const baseURL = environmentSettingsObject.settingsObject.baseUrl;
|
||||
|
||||
// 2. Let urlRecord be the result of getting a URL record given url and baseURL.
|
||||
const urlRecord = getURLRecord(url, baseURL)
|
||||
const urlRecord = getURLRecord(url, baseURL);
|
||||
|
||||
// 3. If protocols is a string, set protocols to a sequence consisting
|
||||
// of just that string.
|
||||
if (typeof protocols === 'string') {
|
||||
protocols = [protocols]
|
||||
protocols = [protocols];
|
||||
}
|
||||
|
||||
// 4. If any of the values in protocols occur more than once or otherwise
|
||||
// fail to match the requirements for elements that comprise the value
|
||||
// of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
|
||||
// protocol, then throw a "SyntaxError" DOMException.
|
||||
if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
if (
|
||||
protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size
|
||||
) {
|
||||
throw new DOMException(
|
||||
'Invalid Sec-WebSocket-Protocol value',
|
||||
'SyntaxError'
|
||||
);
|
||||
}
|
||||
|
||||
if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
if (
|
||||
protocols.length > 0 &&
|
||||
!protocols.every((p) => isValidSubprotocol(p))
|
||||
) {
|
||||
throw new DOMException(
|
||||
'Invalid Sec-WebSocket-Protocol value',
|
||||
'SyntaxError'
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Set this's url to urlRecord.
|
||||
this.#url = new URL(urlRecord.href)
|
||||
this.#url = new URL(urlRecord.href);
|
||||
|
||||
// 6. Let client be this's relevant settings object.
|
||||
const client = environmentSettingsObject.settingsObject
|
||||
const client = environmentSettingsObject.settingsObject;
|
||||
|
||||
// 7. Run this step in parallel:
|
||||
// 7.1. Establish a WebSocket connection given urlRecord, protocols,
|
||||
@ -148,12 +173,12 @@ class WebSocket extends EventTarget {
|
||||
client,
|
||||
this.#handler,
|
||||
options
|
||||
)
|
||||
);
|
||||
|
||||
// Each WebSocket object has an associated ready state, which is a
|
||||
// number representing the state of the connection. Initially it must
|
||||
// be CONNECTING (0).
|
||||
this.#handler.readyState = WebSocket.CONNECTING
|
||||
this.#handler.readyState = WebSocket.CONNECTING;
|
||||
|
||||
// The extensions attribute must initially return the empty string.
|
||||
|
||||
@ -161,7 +186,7 @@ class WebSocket extends EventTarget {
|
||||
|
||||
// Each WebSocket object has an associated binary type, which is a
|
||||
// BinaryType. Initially it must be "blob".
|
||||
this.#binaryType = 'blob'
|
||||
this.#binaryType = 'blob';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,53 +194,58 @@ class WebSocket extends EventTarget {
|
||||
* @param {number|undefined} code
|
||||
* @param {string|undefined} reason
|
||||
*/
|
||||
close (code = undefined, reason = undefined) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
close(code = undefined, reason = undefined) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
const prefix = 'WebSocket.close'
|
||||
const prefix = 'WebSocket.close';
|
||||
|
||||
if (code !== undefined) {
|
||||
code = webidl.converters['unsigned short'](code, prefix, 'code', { clamp: true })
|
||||
code = webidl.converters['unsigned short'](code, prefix, 'code', {
|
||||
clamp: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (reason !== undefined) {
|
||||
reason = webidl.converters.USVString(reason)
|
||||
reason = webidl.converters.USVString(reason);
|
||||
}
|
||||
|
||||
// 1. If code is the special value "missing", then set code to null.
|
||||
code ??= null
|
||||
code ??= null;
|
||||
|
||||
// 2. If reason is the special value "missing", then set reason to the empty string.
|
||||
reason ??= ''
|
||||
reason ??= '';
|
||||
|
||||
// 3. Close the WebSocket with this, code, and reason.
|
||||
closeWebSocketConnection(this.#handler, code, reason, true)
|
||||
closeWebSocketConnection(this.#handler, code, reason, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#dom-websocket-send
|
||||
* @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data
|
||||
*/
|
||||
send (data) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
send(data) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
const prefix = 'WebSocket.send'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
const prefix = 'WebSocket.send';
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix);
|
||||
|
||||
data = webidl.converters.WebSocketSendData(data, prefix, 'data')
|
||||
data = webidl.converters.WebSocketSendData(data, prefix, 'data');
|
||||
|
||||
// 1. If this's ready state is CONNECTING, then throw an
|
||||
// "InvalidStateError" DOMException.
|
||||
if (isConnecting(this.#handler.readyState)) {
|
||||
throw new DOMException('Sent before connected.', 'InvalidStateError')
|
||||
throw new DOMException('Sent before connected.', 'InvalidStateError');
|
||||
}
|
||||
|
||||
// 2. Run the appropriate set of steps from the following list:
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||
|
||||
if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
|
||||
return
|
||||
if (
|
||||
!isEstablished(this.#handler.readyState) ||
|
||||
isClosing(this.#handler.readyState)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If data is a string
|
||||
@ -231,12 +261,16 @@ class WebSocket extends EventTarget {
|
||||
// the bufferedAmount attribute by the number of bytes needed to
|
||||
// express the argument as UTF-8.
|
||||
|
||||
const buffer = Buffer.from(data)
|
||||
const buffer = Buffer.from(data);
|
||||
|
||||
this.#bufferedAmount += buffer.byteLength
|
||||
this.#sendQueue.add(buffer, () => {
|
||||
this.#bufferedAmount -= buffer.byteLength
|
||||
}, sendHints.text)
|
||||
this.#bufferedAmount += buffer.byteLength;
|
||||
this.#sendQueue.add(
|
||||
buffer,
|
||||
() => {
|
||||
this.#bufferedAmount -= buffer.byteLength;
|
||||
},
|
||||
sendHints.text
|
||||
);
|
||||
} else if (types.isArrayBuffer(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
@ -250,10 +284,14 @@ class WebSocket extends EventTarget {
|
||||
// increase the bufferedAmount attribute by the length of the
|
||||
// ArrayBuffer in bytes.
|
||||
|
||||
this.#bufferedAmount += data.byteLength
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.byteLength
|
||||
}, sendHints.arrayBuffer)
|
||||
this.#bufferedAmount += data.byteLength;
|
||||
this.#sendQueue.add(
|
||||
data,
|
||||
() => {
|
||||
this.#bufferedAmount -= data.byteLength;
|
||||
},
|
||||
sendHints.arrayBuffer
|
||||
);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
@ -267,10 +305,14 @@ class WebSocket extends EventTarget {
|
||||
// not throw an exception must increase the bufferedAmount attribute
|
||||
// by the length of data’s buffer in bytes.
|
||||
|
||||
this.#bufferedAmount += data.byteLength
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.byteLength
|
||||
}, sendHints.typedArray)
|
||||
this.#bufferedAmount += data.byteLength;
|
||||
this.#sendQueue.add(
|
||||
data,
|
||||
() => {
|
||||
this.#bufferedAmount -= data.byteLength;
|
||||
},
|
||||
sendHints.typedArray
|
||||
);
|
||||
} else if (webidl.is.Blob(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
@ -283,234 +325,244 @@ class WebSocket extends EventTarget {
|
||||
// an exception must increase the bufferedAmount attribute by the size
|
||||
// of the Blob object’s raw data, in bytes.
|
||||
|
||||
this.#bufferedAmount += data.size
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.size
|
||||
}, sendHints.blob)
|
||||
this.#bufferedAmount += data.size;
|
||||
this.#sendQueue.add(
|
||||
data,
|
||||
() => {
|
||||
this.#bufferedAmount -= data.size;
|
||||
},
|
||||
sendHints.blob
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get readyState () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get readyState() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
// The readyState getter steps are to return this's ready state.
|
||||
return this.#handler.readyState
|
||||
return this.#handler.readyState;
|
||||
}
|
||||
|
||||
get bufferedAmount () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get bufferedAmount() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#bufferedAmount
|
||||
return this.#bufferedAmount;
|
||||
}
|
||||
|
||||
get url () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get url() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
// The url getter steps are to return this's url, serialized.
|
||||
return URLSerializer(this.#url)
|
||||
return URLSerializer(this.#url);
|
||||
}
|
||||
|
||||
get extensions () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get extensions() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#extensions
|
||||
return this.#extensions;
|
||||
}
|
||||
|
||||
get protocol () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get protocol() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#protocol
|
||||
return this.#protocol;
|
||||
}
|
||||
|
||||
get onopen () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get onopen() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#events.open
|
||||
return this.#events.open;
|
||||
}
|
||||
|
||||
set onopen (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
set onopen(fn) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
if (this.#events.open) {
|
||||
this.removeEventListener('open', this.#events.open)
|
||||
this.removeEventListener('open', this.#events.open);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.open = fn
|
||||
this.addEventListener('open', fn)
|
||||
this.#events.open = fn;
|
||||
this.addEventListener('open', fn);
|
||||
} else {
|
||||
this.#events.open = null
|
||||
this.#events.open = null;
|
||||
}
|
||||
}
|
||||
|
||||
get onerror () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get onerror() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#events.error
|
||||
return this.#events.error;
|
||||
}
|
||||
|
||||
set onerror (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
set onerror(fn) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
if (this.#events.error) {
|
||||
this.removeEventListener('error', this.#events.error)
|
||||
this.removeEventListener('error', this.#events.error);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.error = fn
|
||||
this.addEventListener('error', fn)
|
||||
this.#events.error = fn;
|
||||
this.addEventListener('error', fn);
|
||||
} else {
|
||||
this.#events.error = null
|
||||
this.#events.error = null;
|
||||
}
|
||||
}
|
||||
|
||||
get onclose () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get onclose() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#events.close
|
||||
return this.#events.close;
|
||||
}
|
||||
|
||||
set onclose (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
set onclose(fn) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
if (this.#events.close) {
|
||||
this.removeEventListener('close', this.#events.close)
|
||||
this.removeEventListener('close', this.#events.close);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.close = fn
|
||||
this.addEventListener('close', fn)
|
||||
this.#events.close = fn;
|
||||
this.addEventListener('close', fn);
|
||||
} else {
|
||||
this.#events.close = null
|
||||
this.#events.close = null;
|
||||
}
|
||||
}
|
||||
|
||||
get onmessage () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get onmessage() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#events.message
|
||||
return this.#events.message;
|
||||
}
|
||||
|
||||
set onmessage (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
set onmessage(fn) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
if (this.#events.message) {
|
||||
this.removeEventListener('message', this.#events.message)
|
||||
this.removeEventListener('message', this.#events.message);
|
||||
}
|
||||
|
||||
if (typeof fn === 'function') {
|
||||
this.#events.message = fn
|
||||
this.addEventListener('message', fn)
|
||||
this.#events.message = fn;
|
||||
this.addEventListener('message', fn);
|
||||
} else {
|
||||
this.#events.message = null
|
||||
this.#events.message = null;
|
||||
}
|
||||
}
|
||||
|
||||
get binaryType () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
get binaryType() {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
return this.#binaryType
|
||||
return this.#binaryType;
|
||||
}
|
||||
|
||||
set binaryType (type) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
set binaryType(type) {
|
||||
webidl.brandCheck(this, WebSocket);
|
||||
|
||||
if (type !== 'blob' && type !== 'arraybuffer') {
|
||||
this.#binaryType = 'blob'
|
||||
this.#binaryType = 'blob';
|
||||
} else {
|
||||
this.#binaryType = type
|
||||
this.#binaryType = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
||||
*/
|
||||
#onConnectionEstablished (response, parsedExtensions) {
|
||||
#onConnectionEstablished(response, parsedExtensions) {
|
||||
// processResponse is called when the "response’s header list has been received and initialized."
|
||||
// once this happens, the connection is open
|
||||
this.#handler.socket = response.socket
|
||||
this.#handler.socket = response.socket;
|
||||
|
||||
const parser = new ByteParser(this.#handler, parsedExtensions)
|
||||
parser.on('drain', () => this.#handler.onParserDrain())
|
||||
parser.on('error', (err) => this.#handler.onParserError(err))
|
||||
const parser = new ByteParser(this.#handler, parsedExtensions);
|
||||
parser.on('drain', () => this.#handler.onParserDrain());
|
||||
parser.on('error', (err) => this.#handler.onParserError(err));
|
||||
|
||||
this.#parser = parser
|
||||
this.#sendQueue = new SendQueue(response.socket)
|
||||
this.#parser = parser;
|
||||
this.#sendQueue = new SendQueue(response.socket);
|
||||
|
||||
// 1. Change the ready state to OPEN (1).
|
||||
this.#handler.readyState = states.OPEN
|
||||
this.#handler.readyState = states.OPEN;
|
||||
|
||||
// 2. Change the extensions attribute’s value to the extensions in use, if
|
||||
// it is not the null value.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
|
||||
const extensions = response.headersList.get('sec-websocket-extensions')
|
||||
const extensions = response.headersList.get('sec-websocket-extensions');
|
||||
|
||||
if (extensions !== null) {
|
||||
this.#extensions = extensions
|
||||
this.#extensions = extensions;
|
||||
}
|
||||
|
||||
// 3. Change the protocol attribute’s value to the subprotocol in use, if
|
||||
// it is not the null value.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-1.9
|
||||
const protocol = response.headersList.get('sec-websocket-protocol')
|
||||
const protocol = response.headersList.get('sec-websocket-protocol');
|
||||
|
||||
if (protocol !== null) {
|
||||
this.#protocol = protocol
|
||||
this.#protocol = protocol;
|
||||
}
|
||||
|
||||
// 4. Fire an event named open at the WebSocket object.
|
||||
fireEvent('open', this)
|
||||
fireEvent('open', this);
|
||||
}
|
||||
|
||||
#onFail (code, reason) {
|
||||
#onFail(code, reason) {
|
||||
if (reason) {
|
||||
// TODO: process.nextTick
|
||||
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
||||
error: new Error(reason),
|
||||
message: reason
|
||||
})
|
||||
message: reason,
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.#handler.wasEverConnected) {
|
||||
this.#handler.readyState = states.CLOSED
|
||||
this.#handler.readyState = states.CLOSED;
|
||||
|
||||
// If the WebSocket connection could not be established, it is also said
|
||||
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
||||
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
||||
wasClean: false, code, reason
|
||||
})
|
||||
wasClean: false,
|
||||
code,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#onMessage (type, data) {
|
||||
#onMessage(type, data) {
|
||||
// 1. If ready state is not OPEN (1), then return.
|
||||
if (this.#handler.readyState !== states.OPEN) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Let dataForEvent be determined by switching on type and binary type:
|
||||
let dataForEvent
|
||||
let dataForEvent;
|
||||
|
||||
if (type === opcodes.TEXT) {
|
||||
// -> type indicates that the data is Text
|
||||
// a new DOMString containing data
|
||||
try {
|
||||
dataForEvent = utf8Decode(data)
|
||||
dataForEvent = utf8Decode(data);
|
||||
} catch {
|
||||
failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
|
||||
return
|
||||
failWebsocketConnection(
|
||||
this.#handler,
|
||||
1007,
|
||||
'Received invalid UTF-8 in text frame.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (type === opcodes.BINARY) {
|
||||
if (this.#binaryType === 'blob') {
|
||||
// -> type indicates that the data is Binary and binary type is "blob"
|
||||
// a new Blob object, created in the relevant Realm of the WebSocket
|
||||
// object, that represents data as its raw data
|
||||
dataForEvent = new Blob([data])
|
||||
dataForEvent = new Blob([data]);
|
||||
} else {
|
||||
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
||||
// a new ArrayBuffer object, created in the relevant Realm of the
|
||||
// WebSocket object, whose contents are data
|
||||
dataForEvent = toArrayBuffer(data)
|
||||
dataForEvent = toArrayBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,45 +571,45 @@ class WebSocket extends EventTarget {
|
||||
// object’s url's origin, and the data attribute initialized to dataForEvent.
|
||||
fireEvent('message', this, createFastMessageEvent, {
|
||||
origin: this.#url.origin,
|
||||
data: dataForEvent
|
||||
})
|
||||
data: dataForEvent,
|
||||
});
|
||||
}
|
||||
|
||||
#onParserDrain () {
|
||||
this.#handler.socket.resume()
|
||||
#onParserDrain() {
|
||||
this.#handler.socket.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
||||
*/
|
||||
#onSocketClose () {
|
||||
#onSocketClose() {
|
||||
// If the TCP connection was closed after the
|
||||
// WebSocket closing handshake was completed, the WebSocket connection
|
||||
// is said to have been closed _cleanly_.
|
||||
const wasClean =
|
||||
this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
this.#handler.closeState.has(sentCloseFrameState.RECEIVED);
|
||||
|
||||
let code = 1005
|
||||
let reason = ''
|
||||
let code = 1005;
|
||||
let reason = '';
|
||||
|
||||
const result = this.#parser.closingInfo
|
||||
const result = this.#parser.closingInfo;
|
||||
|
||||
if (result && !result.error) {
|
||||
code = result.code ?? 1005
|
||||
reason = result.reason
|
||||
code = result.code ?? 1005;
|
||||
reason = result.reason;
|
||||
} else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
// If _The WebSocket
|
||||
// Connection is Closed_ and no Close control frame was received by the
|
||||
// endpoint (such as could occur if the underlying transport connection
|
||||
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
||||
// 1006.
|
||||
code = 1006
|
||||
code = 1006;
|
||||
}
|
||||
|
||||
// 1. Change the ready state to CLOSED (3).
|
||||
this.#handler.readyState = states.CLOSED
|
||||
this.#handler.readyState = states.CLOSED;
|
||||
|
||||
// 2. If the user agent was required to fail the WebSocket
|
||||
// connection, or if the WebSocket connection was closed
|
||||
@ -575,27 +627,29 @@ class WebSocket extends EventTarget {
|
||||
// reason.
|
||||
// TODO: process.nextTick
|
||||
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
||||
wasClean, code, reason
|
||||
})
|
||||
wasClean,
|
||||
code,
|
||||
reason,
|
||||
});
|
||||
|
||||
if (channels.close.hasSubscribers) {
|
||||
channels.close.publish({
|
||||
websocket: this,
|
||||
code,
|
||||
reason
|
||||
})
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
|
||||
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING
|
||||
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING;
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-open
|
||||
WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN
|
||||
WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN;
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-closing
|
||||
WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING
|
||||
WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING;
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-closed
|
||||
WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED
|
||||
WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED;
|
||||
|
||||
Object.defineProperties(WebSocket.prototype, {
|
||||
CONNECTING: staticPropertyDescriptors,
|
||||
@ -618,69 +672,82 @@ Object.defineProperties(WebSocket.prototype, {
|
||||
value: 'WebSocket',
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperties(WebSocket, {
|
||||
CONNECTING: staticPropertyDescriptors,
|
||||
OPEN: staticPropertyDescriptors,
|
||||
CLOSING: staticPropertyDescriptors,
|
||||
CLOSED: staticPropertyDescriptors
|
||||
})
|
||||
CLOSED: staticPropertyDescriptors,
|
||||
});
|
||||
|
||||
webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
|
||||
webidl.converters.DOMString
|
||||
)
|
||||
);
|
||||
|
||||
webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
|
||||
return webidl.converters['sequence<DOMString>'](V)
|
||||
webidl.converters['DOMString or sequence<DOMString>'] = function (
|
||||
V,
|
||||
prefix,
|
||||
argument
|
||||
) {
|
||||
if (
|
||||
webidl.util.Type(V) === webidl.util.Types.OBJECT &&
|
||||
Symbol.iterator in V
|
||||
) {
|
||||
return webidl.converters['sequence<DOMString>'](V);
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V, prefix, argument)
|
||||
}
|
||||
return webidl.converters.DOMString(V, prefix, argument);
|
||||
};
|
||||
|
||||
// This implements the proposal made in https://github.com/whatwg/websockets/issues/42
|
||||
webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'protocols',
|
||||
converter: webidl.converters['DOMString or sequence<DOMString>'],
|
||||
defaultValue: () => new Array(0)
|
||||
defaultValue: () => new Array(0),
|
||||
},
|
||||
{
|
||||
key: 'dispatcher',
|
||||
converter: webidl.converters.any,
|
||||
defaultValue: () => getGlobalDispatcher()
|
||||
defaultValue: () => getGlobalDispatcher(),
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
||||
}
|
||||
])
|
||||
converter: webidl.nullableConverter(webidl.converters.HeadersInit),
|
||||
},
|
||||
]);
|
||||
|
||||
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
|
||||
return webidl.converters.WebSocketInit(V)
|
||||
}
|
||||
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] =
|
||||
function (V) {
|
||||
if (
|
||||
webidl.util.Type(V) === webidl.util.Types.OBJECT &&
|
||||
!(Symbol.iterator in V)
|
||||
) {
|
||||
return webidl.converters.WebSocketInit(V);
|
||||
}
|
||||
|
||||
return { protocols: webidl.converters['DOMString or sequence<DOMString>'](V) }
|
||||
}
|
||||
return {
|
||||
protocols: webidl.converters['DOMString or sequence<DOMString>'](V),
|
||||
};
|
||||
};
|
||||
|
||||
webidl.converters.WebSocketSendData = function (V) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
||||
if (webidl.is.Blob(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
||||
return V
|
||||
return V;
|
||||
}
|
||||
}
|
||||
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
return webidl.converters.USVString(V);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
WebSocket
|
||||
}
|
||||
WebSocket,
|
||||
};
|
||||
|
Reference in New Issue
Block a user