summaryrefslogtreecommitdiff
path: root/deps/undici/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'deps/undici/src/lib')
-rw-r--r--deps/undici/src/lib/cache/cache.js842
-rw-r--r--deps/undici/src/lib/cache/cachestorage.js144
-rw-r--r--deps/undici/src/lib/cache/symbols.js5
-rw-r--r--deps/undici/src/lib/cache/util.js49
-rw-r--r--deps/undici/src/lib/client.js15
-rw-r--r--deps/undici/src/lib/fetch/body.js10
-rw-r--r--deps/undici/src/lib/fetch/dataURL.js95
-rw-r--r--deps/undici/src/lib/fetch/index.js2
-rw-r--r--deps/undici/src/lib/fetch/response.js5
-rw-r--r--deps/undici/src/lib/fetch/util.js3
-rw-r--r--deps/undici/src/lib/fetch/webidl.js7
-rw-r--r--deps/undici/src/lib/websocket/connection.js14
-rw-r--r--deps/undici/src/lib/websocket/frame.js2
-rw-r--r--deps/undici/src/lib/websocket/websocket.js38
14 files changed, 1198 insertions, 33 deletions
diff --git a/deps/undici/src/lib/cache/cache.js b/deps/undici/src/lib/cache/cache.js
new file mode 100644
index 0000000000..18f06a348a
--- /dev/null
+++ b/deps/undici/src/lib/cache/cache.js
@@ -0,0 +1,842 @@
+'use strict'
+
+const { kConstruct } = require('./symbols')
+const { urlEquals, fieldValues: getFieldValues } = require('./util')
+const { kEnumerableProperty, isDisturbed } = require('../core/util')
+const { kHeadersList } = require('../core/symbols')
+const { webidl } = require('../fetch/webidl')
+const { Response, cloneResponse } = require('../fetch/response')
+const { Request } = require('../fetch/request')
+const { kState, kHeaders, kGuard, kRealm } = require('../fetch/symbols')
+const { fetching } = require('../fetch/index')
+const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
+const assert = require('assert')
+const { getGlobalDispatcher } = require('../global')
+
+/**
+ * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation
+ * @typedef {Object} CacheBatchOperation
+ * @property {'delete' | 'put'} type
+ * @property {any} request
+ * @property {any} response
+ * @property {import('../../types/cache').CacheQueryOptions} options
+ */
+
+/**
+ * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list
+ * @typedef {[any, any][]} requestResponseList
+ */
+
+class Cache {
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list
+ * @type {requestResponseList}
+ */
+ #relevantRequestResponseList
+
+ constructor () {
+ if (arguments[0] !== kConstruct) {
+ webidl.illegalConstructor()
+ }
+
+ this.#relevantRequestResponseList = arguments[1]
+ }
+
+ async match (request, options = {}) {
+ webidl.brandCheck(this, Cache)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.match' })
+
+ request = webidl.converters.RequestInfo(request)
+ options = webidl.converters.CacheQueryOptions(options)
+
+ const p = await this.matchAll(request, options)
+
+ if (p.length === 0) {
+ return
+ }
+
+ return p[0]
+ }
+
+ async matchAll (request = undefined, options = {}) {
+ webidl.brandCheck(this, Cache)
+
+ if (request !== undefined) request = webidl.converters.RequestInfo(request)
+ options = webidl.converters.CacheQueryOptions(options)
+
+ // 1.
+ let r = null
+
+ // 2.
+ if (request !== undefined) {
+ if (request instanceof Request) {
+ // 2.1.1
+ r = request[kState]
+
+ // 2.1.2
+ if (r.method !== 'GET' && !options.ignoreMethod) {
+ return []
+ }
+ } else if (typeof request === 'string') {
+ // 2.2.1
+ r = new Request(request)[kState]
+ }
+ }
+
+ // 5.
+ // 5.1
+ const responses = []
+
+ // 5.2
+ if (request === undefined) {
+ // 5.2.1
+ for (const requestResponse of this.#relevantRequestResponseList) {
+ responses.push(requestResponse[1])
+ }
+ } else { // 5.3
+ // 5.3.1
+ const requestResponses = this.#queryCache(r, options)
+
+ // 5.3.2
+ for (const requestResponse of requestResponses) {
+ responses.push(requestResponse[1])
+ }
+ }
+
+ // 5.4
+ // We don't implement CORs so we don't need to loop over the responses, yay!
+
+ // 5.5.1
+ const responseList = []
+
+ // 5.5.2
+ for (const response of responses) {
+ // 5.5.2.1
+ const responseObject = new Response(response.body?.source ?? null)
+ const body = responseObject[kState].body
+ responseObject[kState] = response
+ responseObject[kState].body = body
+ responseObject[kHeaders][kHeadersList] = response.headersList
+ responseObject[kHeaders][kGuard] = 'immutable'
+
+ responseList.push(responseObject)
+ }
+
+ // 6.
+ return Object.freeze(responseList)
+ }
+
+ async add (request) {
+ webidl.brandCheck(this, Cache)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.add' })
+
+ request = webidl.converters.RequestInfo(request)
+
+ // 1.
+ const requests = [request]
+
+ // 2.
+ const responseArrayPromise = this.addAll(requests)
+
+ // 3.
+ return await responseArrayPromise
+ }
+
+ async addAll (requests) {
+ webidl.brandCheck(this, Cache)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.addAll' })
+
+ requests = webidl.converters['sequence<RequestInfo>'](requests)
+
+ // 1.
+ const responsePromises = []
+
+ // 2.
+ const requestList = []
+
+ // 3.
+ for (const request of requests) {
+ if (typeof request === 'string') {
+ continue
+ }
+
+ // 3.1
+ const r = request[kState]
+
+ // 3.2
+ if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') {
+ throw webidl.errors.exception({
+ header: 'Cache.addAll',
+ message: 'Expected http/s scheme when method is not GET.'
+ })
+ }
+ }
+
+ // 4.
+ /** @type {ReturnType<typeof fetching>[]} */
+ const fetchControllers = []
+
+ // 5.
+ for (const request of requests) {
+ // 5.1
+ const r = new Request(request)[kState]
+
+ // 5.2
+ if (!urlIsHttpHttpsScheme(r.url)) {
+ throw webidl.errors.exception({
+ header: 'Cache.addAll',
+ message: 'Expected http/s scheme.'
+ })
+ }
+
+ // 5.4
+ r.initiator = 'fetch'
+ r.destination = 'subresource'
+
+ // 5.5
+ requestList.push(r)
+
+ // 5.6
+ const responsePromise = createDeferredPromise()
+
+ // 5.7
+ fetchControllers.push(fetching({
+ request: r,
+ dispatcher: getGlobalDispatcher(),
+ processResponse (response) {
+ // 1.
+ if (response.type === 'error' || response.status === 206 || response.status < 200 || response.status > 299) {
+ responsePromise.reject(webidl.errors.exception({
+ header: 'Cache.addAll',
+ message: 'Received an invalid status code or the request failed.'
+ }))
+ } else if (response.headersList.contains('vary')) { // 2.
+ // 2.1
+ const fieldValues = getFieldValues(response.headersList.get('vary'))
+
+ // 2.2
+ for (const fieldValue of fieldValues) {
+ // 2.2.1
+ if (fieldValue === '*') {
+ responsePromise.reject(webidl.errors.exception({
+ header: 'Cache.addAll',
+ message: 'invalid vary field value'
+ }))
+
+ for (const controller of fetchControllers) {
+ controller.abort()
+ }
+
+ return
+ }
+ }
+ }
+ },
+ processResponseEndOfBody (response) {
+ // 1.
+ if (response.aborted) {
+ responsePromise.reject(new DOMException('aborted', 'AbortError'))
+ return
+ }
+
+ // 2.
+ responsePromise.resolve(response)
+ }
+ }))
+
+ // 5.8
+ responsePromises.push(responsePromise.promise)
+ }
+
+ // 6.
+ const p = Promise.all(responsePromises)
+
+ // 7.
+ const responses = await p
+
+ // 7.1
+ const operations = []
+
+ // 7.2
+ let index = 0
+
+ // 7.3
+ for (const response of responses) {
+ // 7.3.1
+ /** @type {CacheBatchOperation} */
+ const operation = {
+ type: 'put', // 7.3.2
+ request: requestList[index], // 7.3.3
+ response // 7.3.4
+ }
+
+ operations.push(operation) // 7.3.5
+
+ index++ // 7.3.6
+ }
+
+ // 7.5
+ const cacheJobPromise = createDeferredPromise()
+
+ // 7.6.1
+ let errorData = null
+
+ // 7.6.2
+ try {
+ this.#batchCacheOperations(operations)
+ } catch (e) {
+ errorData = e
+ }
+
+ // 7.6.3
+ queueMicrotask(() => {
+ // 7.6.3.1
+ if (errorData === null) {
+ cacheJobPromise.resolve(undefined)
+ } else {
+ // 7.6.3.2
+ cacheJobPromise.reject(errorData)
+ }
+ })
+
+ // 7.7
+ return cacheJobPromise.promise
+ }
+
+ async put (request, response) {
+ webidl.brandCheck(this, Cache)
+ webidl.argumentLengthCheck(arguments, 2, { header: 'Cache.put' })
+
+ request = webidl.converters.RequestInfo(request)
+ response = webidl.converters.Response(response)
+
+ // 1.
+ let innerRequest = null
+
+ // 2.
+ if (request instanceof Request) {
+ innerRequest = request[kState]
+ } else { // 3.
+ innerRequest = new Request(request)[kState]
+ }
+
+ // 4.
+ if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== 'GET') {
+ throw webidl.errors.exception({
+ header: 'Cache.put',
+ message: 'Expected an http/s scheme when method is not GET'
+ })
+ }
+
+ // 5.
+ const innerResponse = response[kState]
+
+ // 6.
+ if (innerResponse.status === 206) {
+ throw webidl.errors.exception({
+ header: 'Cache.put',
+ message: 'Got 206 status'
+ })
+ }
+
+ // 7.
+ if (innerResponse.headersList.contains('vary')) {
+ // 7.1.
+ const fieldValues = getFieldValues(innerResponse.headersList.get('vary'))
+
+ // 7.2.
+ for (const fieldValue of fieldValues) {
+ // 7.2.1
+ if (fieldValue === '*') {
+ throw webidl.errors.exception({
+ header: 'Cache.put',
+ message: 'Got * vary field value'
+ })
+ }
+ }
+ }
+
+ // 8.
+ if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) {
+ throw webidl.errors.exception({
+ header: 'Cache.put',
+ message: 'Response body is locked or disturbed'
+ })
+ }
+
+ // 9.
+ const clonedResponse = cloneResponse(innerResponse)
+
+ // 10.
+ const bodyReadPromise = createDeferredPromise()
+
+ // 11.
+ if (innerResponse.body != null) {
+ // 11.1
+ const stream = innerResponse.body.stream
+
+ // 11.2
+ const reader = stream.getReader()
+
+ // 11.3
+ readAllBytes(
+ reader,
+ (bytes) => bodyReadPromise.resolve(bytes),
+ (error) => bodyReadPromise.reject(error)
+ )
+ } else {
+ bodyReadPromise.resolve(undefined)
+ }
+
+ // 12.
+ /** @type {CacheBatchOperation[]} */
+ const operations = []
+
+ // 13.
+ /** @type {CacheBatchOperation} */
+ const operation = {
+ type: 'put', // 14.
+ request: innerRequest, // 15.
+ response: clonedResponse // 16.
+ }
+
+ // 17.
+ operations.push(operation)
+
+ // 19.
+ const bytes = await bodyReadPromise.promise
+
+ if (clonedResponse.body != null) {
+ clonedResponse.body.source = bytes
+ }
+
+ // 19.1
+ const cacheJobPromise = createDeferredPromise()
+
+ // 19.2.1
+ let errorData = null
+
+ // 19.2.2
+ try {
+ this.#batchCacheOperations(operations)
+ } catch (e) {
+ errorData = e
+ }
+
+ // 19.2.3
+ queueMicrotask(() => {
+ // 19.2.3.1
+ if (errorData === null) {
+ cacheJobPromise.resolve()
+ } else { // 19.2.3.2
+ cacheJobPromise.reject(errorData)
+ }
+ })
+
+ return cacheJobPromise.promise
+ }
+
+ async delete (request, options = {}) {
+ webidl.brandCheck(this, Cache)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.delete' })
+
+ request = webidl.converters.RequestInfo(request)
+ options = webidl.converters.CacheQueryOptions(options)
+
+ /**
+ * @type {Request}
+ */
+ let r = null
+
+ if (request instanceof Request) {
+ r = request[kState]
+
+ if (r.method !== 'GET' && !options.ignoreMethod) {
+ return false
+ }
+ } else {
+ assert(typeof request === 'string')
+
+ r = new Request(request)[kState]
+ }
+
+ /** @type {CacheBatchOperation[]} */
+ const operations = []
+
+ /** @type {CacheBatchOperation} */
+ const operation = {
+ type: 'delete',
+ request: r,
+ options
+ }
+
+ operations.push(operation)
+
+ const cacheJobPromise = createDeferredPromise()
+
+ let errorData = null
+ let requestResponses
+
+ try {
+ requestResponses = this.#batchCacheOperations(operations)
+ } catch (e) {
+ errorData = e
+ }
+
+ queueMicrotask(() => {
+ if (errorData === null) {
+ cacheJobPromise.resolve(!!requestResponses?.length)
+ } else {
+ cacheJobPromise.reject(errorData)
+ }
+ })
+
+ return cacheJobPromise.promise
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys
+ * @param {any} request
+ * @param {import('../../types/cache').CacheQueryOptions} options
+ * @returns {readonly Request[]}
+ */
+ async keys (request = undefined, options = {}) {
+ webidl.brandCheck(this, Cache)
+
+ if (request !== undefined) request = webidl.converters.RequestInfo(request)
+ options = webidl.converters.CacheQueryOptions(options)
+
+ // 1.
+ let r = null
+
+ // 2.
+ if (request !== undefined) {
+ // 2.1
+ if (request instanceof Request) {
+ // 2.1.1
+ r = request[kState]
+
+ // 2.1.2
+ if (r.method !== 'GET' && !options.ignoreMethod) {
+ return []
+ }
+ } else if (typeof request === 'string') { // 2.2
+ r = new Request(request)[kState]
+ }
+ }
+
+ // 4.
+ const promise = createDeferredPromise()
+
+ // 5.
+ // 5.1
+ const requests = []
+
+ // 5.2
+ if (request === undefined) {
+ // 5.2.1
+ for (const requestResponse of this.#relevantRequestResponseList) {
+ // 5.2.1.1
+ requests.push(requestResponse[0])
+ }
+ } else { // 5.3
+ // 5.3.1
+ const requestResponses = this.#queryCache(r, options)
+
+ // 5.3.2
+ for (const requestResponse of requestResponses) {
+ // 5.3.2.1
+ requests.push(requestResponse[0])
+ }
+ }
+
+ // 5.4
+ queueMicrotask(() => {
+ // 5.4.1
+ const requestList = []
+
+ // 5.4.2
+ for (const request of requests) {
+ const requestObject = new Request('https://a')
+ requestObject[kState] = request
+ requestObject[kHeaders][kHeadersList] = request.headersList
+ requestObject[kHeaders][kGuard] = 'immutable'
+ requestObject[kRealm] = request.client
+
+ // 5.4.2.1
+ requestList.push(requestObject)
+ }
+
+ // 5.4.3
+ promise.resolve(Object.freeze(requestList))
+ })
+
+ return promise.promise
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm
+ * @param {CacheBatchOperation[]} operations
+ * @returns {requestResponseList}
+ */
+ #batchCacheOperations (operations) {
+ // 1.
+ const cache = this.#relevantRequestResponseList
+
+ // 2.
+ const backupCache = [...cache]
+
+ // 3.
+ const addedItems = []
+
+ // 4.1
+ const resultList = []
+
+ try {
+ // 4.2
+ for (const operation of operations) {
+ // 4.2.1
+ if (operation.type !== 'delete' && operation.type !== 'put') {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'operation type does not match "delete" or "put"'
+ })
+ }
+
+ // 4.2.2
+ if (operation.type === 'delete' && operation.response != null) {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'delete operation should not have an associated response'
+ })
+ }
+
+ // 4.2.3
+ if (this.#queryCache(operation.request, operation.options, addedItems).length) {
+ throw new DOMException('???', 'InvalidStateError')
+ }
+
+ // 4.2.4
+ let requestResponses
+
+ // 4.2.5
+ if (operation.type === 'delete') {
+ // 4.2.5.1
+ requestResponses = this.#queryCache(operation.request, operation.options)
+
+ // TODO: the spec is wrong, this is needed to pass WPTs
+ if (requestResponses.length === 0) {
+ return []
+ }
+
+ // 4.2.5.2
+ for (const requestResponse of requestResponses) {
+ const idx = cache.indexOf(requestResponse)
+ assert(idx !== -1)
+
+ // 4.2.5.2.1
+ cache.splice(idx, 1)
+ }
+ } else if (operation.type === 'put') { // 4.2.6
+ // 4.2.6.1
+ if (operation.response == null) {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'put operation should have an associated response'
+ })
+ }
+
+ // 4.2.6.2
+ const r = operation.request
+
+ // 4.2.6.3
+ if (!urlIsHttpHttpsScheme(r.url)) {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'expected http or https scheme'
+ })
+ }
+
+ // 4.2.6.4
+ if (r.method !== 'GET') {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'not get method'
+ })
+ }
+
+ // 4.2.6.5
+ if (operation.options != null) {
+ throw webidl.errors.exception({
+ header: 'Cache.#batchCacheOperations',
+ message: 'options must not be defined'
+ })
+ }
+
+ // 4.2.6.6
+ requestResponses = this.#queryCache(operation.request)
+
+ // 4.2.6.7
+ for (const requestResponse of requestResponses) {
+ const idx = cache.indexOf(requestResponse)
+ assert(idx !== -1)
+
+ // 4.2.6.7.1
+ cache.splice(idx, 1)
+ }
+
+ // 4.2.6.8
+ cache.push([operation.request, operation.response])
+
+ // 4.2.6.10
+ addedItems.push([operation.request, operation.response])
+ }
+
+ // 4.2.7
+ resultList.push([operation.request, operation.response])
+ }
+
+ // 4.3
+ return resultList
+ } catch (e) { // 5.
+ // 5.1
+ this.#relevantRequestResponseList.length = 0
+
+ // 5.2
+ this.#relevantRequestResponseList = backupCache
+
+ // 5.3
+ throw e
+ }
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#query-cache
+ * @param {any} requestQuery
+ * @param {import('../../types/cache').CacheQueryOptions} options
+ * @param {requestResponseList} targetStorage
+ * @returns {requestResponseList}
+ */
+ #queryCache (requestQuery, options, targetStorage) {
+ /** @type {requestResponseList} */
+ const resultList = []
+
+ const storage = targetStorage ?? this.#relevantRequestResponseList
+
+ for (const requestResponse of storage) {
+ const [cachedRequest, cachedResponse] = requestResponse
+ if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) {
+ resultList.push(requestResponse)
+ }
+ }
+
+ return resultList
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm
+ * @param {any} requestQuery
+ * @param {any} request
+ * @param {any | null} response
+ * @param {import('../../types/cache').CacheQueryOptions | undefined} options
+ * @returns {boolean}
+ */
+ #requestMatchesCachedItem (requestQuery, request, response = null, options) {
+ // if (options?.ignoreMethod === false && request.method === 'GET') {
+ // return false
+ // }
+
+ const queryURL = new URL(requestQuery.url)
+
+ const cachedURL = new URL(request.url)
+
+ if (options?.ignoreSearch) {
+ cachedURL.search = ''
+
+ queryURL.search = ''
+ }
+
+ if (!urlEquals(queryURL, cachedURL, true)) {
+ return false
+ }
+
+ if (
+ response == null ||
+ options?.ignoreVary ||
+ !response.headersList.contains('vary')
+ ) {
+ return true
+ }
+
+ const fieldValues = getFieldValues(response.headersList.get('vary'))
+
+ for (const fieldValue of fieldValues) {
+ if (fieldValue === '*') {
+ return false
+ }
+
+ const requestValue = request.headersList.get(fieldValue)
+ const queryValue = requestQuery.headersList.get(fieldValue)
+
+ // If one has the header and the other doesn't, or one has
+ // a different value than the other, return false
+ if (requestValue !== queryValue) {
+ return false
+ }
+ }
+
+ return true
+ }
+}
+
+Object.defineProperties(Cache.prototype, {
+ [Symbol.toStringTag]: {
+ value: 'Cache',
+ configurable: true
+ },
+ match: kEnumerableProperty,
+ matchAll: kEnumerableProperty,
+ add: kEnumerableProperty,
+ addAll: kEnumerableProperty,
+ put: kEnumerableProperty,
+ delete: kEnumerableProperty,
+ keys: kEnumerableProperty
+})
+
+const cacheQueryOptionConverters = [
+ {
+ key: 'ignoreSearch',
+ converter: webidl.converters.boolean,
+ defaultValue: false
+ },
+ {
+ key: 'ignoreMethod',
+ converter: webidl.converters.boolean,
+ defaultValue: false
+ },
+ {
+ key: 'ignoreVary',
+ converter: webidl.converters.boolean,
+ defaultValue: false
+ }
+]
+
+webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters)
+
+webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([
+ ...cacheQueryOptionConverters,
+ {
+ key: 'cacheName',
+ converter: webidl.converters.DOMString
+ }
+])
+
+webidl.converters.Response = webidl.interfaceConverter(Response)
+
+webidl.converters['sequence<RequestInfo>'] = webidl.sequenceConverter(
+ webidl.converters.RequestInfo
+)
+
+module.exports = {
+ Cache
+}
diff --git a/deps/undici/src/lib/cache/cachestorage.js b/deps/undici/src/lib/cache/cachestorage.js
new file mode 100644
index 0000000000..7e7f0cff2b
--- /dev/null
+++ b/deps/undici/src/lib/cache/cachestorage.js
@@ -0,0 +1,144 @@
+'use strict'
+
+const { kConstruct } = require('./symbols')
+const { Cache } = require('./cache')
+const { webidl } = require('../fetch/webidl')
+const { kEnumerableProperty } = require('../core/util')
+
+class CacheStorage {
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map
+ * @type {Map<string, import('./cache').requestResponseList}
+ */
+ #caches = new Map()
+
+ constructor () {
+ if (arguments[0] !== kConstruct) {
+ webidl.illegalConstructor()
+ }
+ }
+
+ async match (request, options = {}) {
+ webidl.brandCheck(this, CacheStorage)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.match' })
+
+ 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)
+
+ return await cache.match(request, options)
+ }
+ } else { // 2.
+ // 2.2
+ for (const cacheList of this.#caches.values()) {
+ const cache = new Cache(kConstruct, cacheList)
+
+ // 2.2.1.2
+ const response = await cache.match(request, options)
+
+ if (response !== undefined) {
+ return response
+ }
+ }
+ }
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#cache-storage-has
+ * @param {string} cacheName
+ * @returns {Promise<boolean>}
+ */
+ async has (cacheName) {
+ webidl.brandCheck(this, CacheStorage)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.has' })
+
+ cacheName = webidl.converters.DOMString(cacheName)
+
+ // 2.1.1
+ // 2.2
+ return this.#caches.has(cacheName)
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open
+ * @param {string} cacheName
+ * @returns {Promise<Cache>}
+ */
+ async open (cacheName) {
+ webidl.brandCheck(this, CacheStorage)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.open' })
+
+ cacheName = webidl.converters.DOMString(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)
+
+ // 2.1.1.1
+ return new Cache(kConstruct, cache)
+ }
+
+ // 2.2
+ const cache = []
+
+ // 2.3
+ this.#caches.set(cacheName, cache)
+
+ // 2.4
+ return new Cache(kConstruct, cache)
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete
+ * @param {string} cacheName
+ * @returns {Promise<boolean>}
+ */
+ async delete (cacheName) {
+ webidl.brandCheck(this, CacheStorage)
+ webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.delete' })
+
+ cacheName = webidl.converters.DOMString(cacheName)
+
+ return this.#caches.delete(cacheName)
+ }
+
+ /**
+ * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys
+ * @returns {string[]}
+ */
+ async keys () {
+ webidl.brandCheck(this, CacheStorage)
+
+ // 2.1
+ const keys = this.#caches.keys()
+
+ // 2.2
+ return [...keys]
+ }
+}
+
+Object.defineProperties(CacheStorage.prototype, {
+ [Symbol.toStringTag]: {
+ value: 'CacheStorage',
+ configurable: true
+ },
+ match: kEnumerableProperty,
+ has: kEnumerableProperty,
+ open: kEnumerableProperty,
+ delete: kEnumerableProperty,
+ keys: kEnumerableProperty
+})
+
+module.exports = {
+ CacheStorage
+}
diff --git a/deps/undici/src/lib/cache/symbols.js b/deps/undici/src/lib/cache/symbols.js
new file mode 100644
index 0000000000..f9b19740af
--- /dev/null
+++ b/deps/undici/src/lib/cache/symbols.js
@@ -0,0 +1,5 @@
+'use strict'
+
+module.exports = {
+ kConstruct: Symbol('constructable')
+}
diff --git a/deps/undici/src/lib/cache/util.js b/deps/undici/src/lib/cache/util.js
new file mode 100644
index 0000000000..44d52b789e
--- /dev/null
+++ b/deps/undici/src/lib/cache/util.js
@@ -0,0 +1,49 @@
+'use strict'
+
+const assert = require('assert')
+const { URLSerializer } = require('../fetch/dataURL')
+const { isValidHeaderName } = require('../fetch/util')
+
+/**
+ * @see https://url.spec.whatwg.org/#concept-url-equals
+ * @param {URL} A
+ * @param {URL} B
+ * @param {boolean | undefined} excludeFragment
+ * @returns {boolean}
+ */
+function urlEquals (A, B, excludeFragment = false) {
+ const serializedA = URLSerializer(A, excludeFragment)
+
+ const serializedB = URLSerializer(B, excludeFragment)
+
+ 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 fieldValues (header) {
+ assert(header !== null)
+
+ const values = []
+
+ for (let value of header.split(',')) {
+ value = value.trim()
+
+ if (!value.length) {
+ continue
+ } else if (!isValidHeaderName(value)) {
+ continue
+ }
+
+ values.push(value)
+ }
+
+ return values
+}
+
+module.exports = {
+ urlEquals,
+ fieldValues
+}
diff --git a/deps/undici/src/lib/client.js b/deps/undici/src/lib/client.js
index 688df9e615..7d9ec8d7c2 100644
--- a/deps/undici/src/lib/client.js
+++ b/deps/undici/src/lib/client.js
@@ -569,7 +569,10 @@ class Parser {
/* istanbul ignore else: difficult to make a test case for */
if (ptr) {
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
- message = Buffer.from(llhttp.memory.buffer, ptr, len).toString()
+ message =
+ 'Response does not match the HTTP/1.1 protocol (' +
+ Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
+ ')'
}
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
}
@@ -1494,9 +1497,11 @@ function writeStream ({ body, client, request, socket, contentLength, header, ex
const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
const onData = function (chunk) {
- try {
- assert(!finished)
+ if (finished) {
+ return
+ }
+ try {
if (!writer.write(chunk) && this.pause) {
this.pause()
}
@@ -1505,7 +1510,9 @@ function writeStream ({ body, client, request, socket, contentLength, header, ex
}
}
const onDrain = function () {
- assert(!finished)
+ if (finished) {
+ return
+ }
if (body.resume) {
body.resume()
diff --git a/deps/undici/src/lib/fetch/body.js b/deps/undici/src/lib/fetch/body.js
index c291afa936..db450ee6bd 100644
--- a/deps/undici/src/lib/fetch/body.js
+++ b/deps/undici/src/lib/fetch/body.js
@@ -123,6 +123,7 @@ function extractBody (object, keepalive = 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') {
@@ -138,13 +139,20 @@ function extractBody (object, keepalive = false) {
value.type || 'application/octet-stream'
}\r\n\r\n`)
blobParts.push(chunk, value, rn)
- length += chunk.byteLength + value.size + rn.byteLength
+ if (typeof value.size === 'number') {
+ length += chunk.byteLength + value.size + rn.byteLength
+ } else {
+ hasUnknownSizeValue = true
+ }
}
}
const chunk = enc.encode(`--${boundary}--`)
blobParts.push(chunk)
length += chunk.byteLength
+ if (hasUnknownSizeValue) {
+ length = null
+ }
// Set source to object.
source = object
diff --git a/deps/undici/src/lib/fetch/dataURL.js b/deps/undici/src/lib/fetch/dataURL.js
index beefad1548..6df4fcc8cc 100644
--- a/deps/undici/src/lib/fetch/dataURL.js
+++ b/deps/undici/src/lib/fetch/dataURL.js
@@ -1,14 +1,18 @@
const assert = require('assert')
const { atob } = require('buffer')
-const { isValidHTTPToken, isomorphicDecode } = require('./util')
+const { isomorphicDecode } = require('./util')
const encoder = new TextEncoder()
-// Regex
-const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-z0-9]+$/
+/**
+ * @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
-// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
-const HTTP_QUOTED_STRING_TOKENS = /^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/ // eslint-disable-line
+/**
+ * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
+ */
+const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line
// https://fetch.spec.whatwg.org/#data-url-processor
/** @param {URL} dataURL */
@@ -38,14 +42,12 @@ function dataURLProcessor (dataURL) {
// 6. Strip leading and trailing ASCII whitespace
// from mimeType.
- // Note: This will only remove U+0020 SPACE code
- // points, if any.
// Undici implementation note: we need to store the
// length because if the mimetype has spaces removed,
// the wrong amount will be sliced from the input in
// step #9
const mimeTypeLength = mimeType.length
- mimeType = mimeType.replace(/^(\u0020)+|(\u0020)+$/g, '')
+ mimeType = removeASCIIWhitespace(mimeType, true, true)
// 7. If position is past the end of input, then
// return failure
@@ -233,7 +235,7 @@ function percentDecode (input) {
function parseMIMEType (input) {
// 1. Remove any leading and trailing HTTP whitespace
// from input.
- input = input.trim()
+ input = removeHTTPWhitespace(input, true, true)
// 2. Let position be a position variable for input,
// initially pointing at the start of input.
@@ -274,7 +276,7 @@ function parseMIMEType (input) {
)
// 8. Remove any trailing HTTP whitespace from subtype.
- subtype = subtype.trimEnd()
+ subtype = removeHTTPWhitespace(subtype, false, true)
// 9. If subtype is the empty string or does not solely
// contain HTTP token code points, then return failure.
@@ -282,17 +284,20 @@ function parseMIMEType (input) {
return 'failure'
}
+ const typeLowercase = type.toLowerCase()
+ const subtypeLowercase = subtype.toLowerCase()
+
// 10. Let mimeType be a new MIME type record whose type
// is type, in ASCII lowercase, and subtype is subtype,
// in ASCII lowercase.
// https://mimesniff.spec.whatwg.org/#mime-type
const mimeType = {
- type: type.toLowerCase(),
- subtype: subtype.toLowerCase(),
+ type: typeLowercase,
+ subtype: subtypeLowercase,
/** @type {Map<string, string>} */
parameters: new Map(),
// https://mimesniff.spec.whatwg.org/#mime-type-essence
- essence: `${type}/${subtype}`
+ essence: `${typeLowercase}/${subtypeLowercase}`
}
// 11. While position is not past the end of input:
@@ -370,8 +375,7 @@ function parseMIMEType (input) {
)
// 2. Remove any trailing HTTP whitespace from parameterValue.
- // Note: it says "trailing" whitespace; leading is fine.
- parameterValue = parameterValue.trimEnd()
+ parameterValue = removeHTTPWhitespace(parameterValue, false, true)
// 3. If parameterValue is the empty string, then continue.
if (parameterValue.length === 0) {
@@ -388,7 +392,7 @@ function parseMIMEType (input) {
if (
parameterName.length !== 0 &&
HTTP_TOKEN_CODEPOINTS.test(parameterName) &&
- !HTTP_QUOTED_STRING_TOKENS.test(parameterValue) &&
+ (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) &&
!mimeType.parameters.has(parameterName)
) {
mimeType.parameters.set(parameterName, parameterValue)
@@ -522,11 +526,11 @@ function collectAnHTTPQuotedString (input, position, extractValue) {
*/
function serializeAMimeType (mimeType) {
assert(mimeType !== 'failure')
- const { type, subtype, parameters } = mimeType
+ const { parameters, essence } = mimeType
// 1. Let serialization be the concatenation of mimeType’s
// type, U+002F (/), and mimeType’s subtype.
- let serialization = `${type}/${subtype}`
+ let serialization = essence
// 2. For each name → value of mimeType’s parameters:
for (let [name, value] of parameters.entries()) {
@@ -541,7 +545,7 @@ function serializeAMimeType (mimeType) {
// 4. If value does not solely contain HTTP token code
// points or value is the empty string, then:
- if (!isValidHTTPToken(value)) {
+ if (!HTTP_TOKEN_CODEPOINTS.test(value)) {
// 1. Precede each occurence of U+0022 (") or
// U+005C (\) in value with U+005C (\).
value = value.replace(/(\\|")/g, '\\$1')
@@ -561,6 +565,59 @@ function serializeAMimeType (mimeType) {
return serialization
}
+/**
+ * @see https://fetch.spec.whatwg.org/#http-whitespace
+ * @param {string} char
+ */
+function isHTTPWhiteSpace (char) {
+ return char === '\r' || char === '\n' || char === '\t' || char === ' '
+}
+
+/**
+ * @see https://fetch.spec.whatwg.org/#http-whitespace
+ * @param {string} str
+ */
+function removeHTTPWhitespace (str, leading = true, trailing = true) {
+ let lead = 0
+ let trail = str.length - 1
+
+ if (leading) {
+ for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++);
+ }
+
+ if (trailing) {
+ for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--);
+ }
+
+ return str.slice(lead, trail + 1)
+}
+
+/**
+ * @see https://infra.spec.whatwg.org/#ascii-whitespace
+ * @param {string} char
+ */
+function isASCIIWhitespace (char) {
+ return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' '
+}
+
+/**
+ * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
+ */
+function removeASCIIWhitespace (str, leading = true, trailing = true) {
+ let lead = 0
+ let trail = str.length - 1
+
+ if (leading) {
+ for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++);
+ }
+
+ if (trailing) {
+ for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--);
+ }
+
+ return str.slice(lead, trail + 1)
+}
+
module.exports = {
dataURLProcessor,
URLSerializer,
diff --git a/deps/undici/src/lib/fetch/index.js b/deps/undici/src/lib/fetch/index.js
index f3016c60dd..5199873242 100644
--- a/deps/undici/src/lib/fetch/index.js
+++ b/deps/undici/src/lib/fetch/index.js
@@ -318,7 +318,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
function markResourceTiming (timingInfo, originalURL, initiatorType, globalThis, cacheState) {
- if (nodeMajor >= 18 && nodeMinor >= 2) {
+ if (nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 2)) {
performance.markResourceTiming(timingInfo, originalURL, initiatorType, globalThis, cacheState)
}
}
diff --git a/deps/undici/src/lib/fetch/response.js b/deps/undici/src/lib/fetch/response.js
index ff06bfb47d..1029dbef53 100644
--- a/deps/undici/src/lib/fetch/response.js
+++ b/deps/undici/src/lib/fetch/response.js
@@ -467,7 +467,7 @@ function initializeResponse (response, init, body) {
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
if ('headers' in init && init.headers != null) {
- fill(response[kState].headersList, init.headers)
+ fill(response[kHeaders], init.headers)
}
// 6. If body was given, then:
@@ -569,5 +569,6 @@ module.exports = {
makeResponse,
makeAppropriateNetworkError,
filterResponse,
- Response
+ Response,
+ cloneResponse
}
diff --git a/deps/undici/src/lib/fetch/util.js b/deps/undici/src/lib/fetch/util.js
index 23023262d1..400687ba2e 100644
--- a/deps/undici/src/lib/fetch/util.js
+++ b/deps/undici/src/lib/fetch/util.js
@@ -1028,5 +1028,6 @@ module.exports = {
isomorphicDecode,
urlIsLocal,
urlHasHttpsScheme,
- urlIsHttpHttpsScheme
+ urlIsHttpHttpsScheme,
+ readAllBytes
}
diff --git a/deps/undici/src/lib/fetch/webidl.js b/deps/undici/src/lib/fetch/webidl.js
index e55de13950..38a05e6575 100644
--- a/deps/undici/src/lib/fetch/webidl.js
+++ b/deps/undici/src/lib/fetch/webidl.js
@@ -51,6 +51,13 @@ webidl.argumentLengthCheck = function ({ length }, min, ctx) {
}
}
+webidl.illegalConstructor = function () {
+ throw webidl.errors.exception({
+ header: 'TypeError',
+ message: 'Illegal constructor'
+ })
+}
+
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
webidl.util.Type = function (V) {
switch (typeof V) {
diff --git a/deps/undici/src/lib/websocket/connection.js b/deps/undici/src/lib/websocket/connection.js
index 09770247e3..8c821899f6 100644
--- a/deps/undici/src/lib/websocket/connection.js
+++ b/deps/undici/src/lib/websocket/connection.js
@@ -13,7 +13,9 @@ const { fireEvent, failWebsocketConnection } = require('./util')
const { CloseEvent } = require('./events')
const { makeRequest } = require('../fetch/request')
const { fetching } = require('../fetch/index')
+const { Headers } = require('../fetch/headers')
const { getGlobalDispatcher } = require('../global')
+const { kHeadersList } = require('../core/symbols')
const channels = {}
channels.open = diagnosticsChannel.channel('undici:websocket:open')
@@ -26,8 +28,9 @@ channels.socketError = diagnosticsChannel.channel('undici:websocket:socket_error
* @param {string|string[]} protocols
* @param {import('./websocket').WebSocket} ws
* @param {(response: any) => void} onEstablish
+ * @param {Partial<import('../../types/websocket').WebSocketInit>} options
*/
-function establishWebSocketConnection (url, protocols, ws, onEstablish) {
+function establishWebSocketConnection (url, protocols, ws, onEstablish, 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
@@ -48,6 +51,13 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish) {
redirect: 'error'
})
+ // Note: undici extension, allow setting custom headers.
+ if (options.headers) {
+ const headersList = new Headers(options.headers)[kHeadersList]
+
+ request.headersList = headersList
+ }
+
// 3. Append (`Upgrade`, `websocket`) to request’s header list.
// 4. Append (`Connection`, `Upgrade`) to request’s header list.
// Note: both of these are handled by undici currently.
@@ -88,7 +98,7 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish) {
const controller = fetching({
request,
useParallelQueue: true,
- dispatcher: getGlobalDispatcher(),
+ dispatcher: options.dispatcher ?? getGlobalDispatcher(),
processResponse (response) {
// 1. If response is a network error or its status is not 101,
// fail the WebSocket connection.
diff --git a/deps/undici/src/lib/websocket/frame.js b/deps/undici/src/lib/websocket/frame.js
index 1df5e16934..61bfd3915c 100644
--- a/deps/undici/src/lib/websocket/frame.js
+++ b/deps/undici/src/lib/websocket/frame.js
@@ -43,7 +43,7 @@ class WebsocketFrameSend {
buffer[1] = payloadLength
if (payloadLength === 126) {
- new DataView(buffer.buffer).setUint16(2, bodyLength)
+ buffer.writeUInt16BE(bodyLength, 2)
} else if (payloadLength === 127) {
// Clear extended payload length
buffer[2] = buffer[3] = 0
diff --git a/deps/undici/src/lib/websocket/websocket.js b/deps/undici/src/lib/websocket/websocket.js
index 164d24c6f8..22ad2fb11a 100644
--- a/deps/undici/src/lib/websocket/websocket.js
+++ b/deps/undici/src/lib/websocket/websocket.js
@@ -18,6 +18,7 @@ const { establishWebSocketConnection } = require('./connection')
const { WebsocketFrameSend } = require('./frame')
const { ByteParser } = require('./receiver')
const { kEnumerableProperty, isBlobLike } = require('../core/util')
+const { getGlobalDispatcher } = require('../global')
const { types } = require('util')
let experimentalWarned = false
@@ -51,8 +52,10 @@ class WebSocket extends EventTarget {
})
}
+ const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols)
+
url = webidl.converters.USVString(url)
- protocols = webidl.converters['DOMString or sequence<DOMString>'](protocols)
+ protocols = options.protocols
// 1. Let urlRecord be the result of applying the URL parser to url.
let urlRecord
@@ -110,7 +113,8 @@ class WebSocket extends EventTarget {
urlRecord,
protocols,
this,
- (response) => this.#onConnectionEstablished(response)
+ (response) => this.#onConnectionEstablished(response),
+ options
)
// Each WebSocket object has an associated ready state, which is a
@@ -577,6 +581,36 @@ webidl.converters['DOMString or sequence<DOMString>'] = function (V) {
return webidl.converters.DOMString(V)
}
+// This implements the propsal made in https://github.com/whatwg/websockets/issues/42
+webidl.converters.WebSocketInit = webidl.dictionaryConverter([
+ {
+ key: 'protocols',
+ converter: webidl.converters['DOMString or sequence<DOMString>'],
+ get defaultValue () {
+ return []
+ }
+ },
+ {
+ key: 'dispatcher',
+ converter: (V) => V,
+ get defaultValue () {
+ return getGlobalDispatcher()
+ }
+ },
+ {
+ key: 'headers',
+ converter: webidl.nullableConverter(webidl.converters.HeadersInit)
+ }
+])
+
+webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
+ if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) {
+ return webidl.converters.WebSocketInit(V)
+ }
+
+ return { protocols: webidl.converters['DOMString or sequence<DOMString>'](V) }
+}
+
webidl.converters.WebSocketSendData = function (V) {
if (webidl.util.Type(V) === 'Object') {
if (isBlobLike(V)) {