Skip to content

Commit

Permalink
deps: update undici to 5.4.0
Browse files Browse the repository at this point in the history
PR-URL: #43262
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
  • Loading branch information
nodejs-github-bot authored and bengl committed Jun 1, 2022
1 parent aefc9dd commit dc3b91f
Show file tree
Hide file tree
Showing 14 changed files with 79 additions and 202 deletions.
9 changes: 9 additions & 0 deletions deps/undici/src/README.md
Expand Up @@ -288,6 +288,15 @@ const headers = await fetch(url)
.then(res => res.headers)
```

##### Forbidden and Safelisted Header Names

* https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
* https://fetch.spec.whatwg.org/#forbidden-header-name
* https://fetch.spec.whatwg.org/#forbidden-response-header-name
* https://github.com/wintercg/fetch/issues/6

The [Fetch Standard](https://fetch.spec.whatwg.org) requires implementations to exclude certain headers from requests and responses. In browser environments, some headers are forbidden so the user agent remains in full control over them. In Undici, these constraints are removed to give more control to the user.

### `undici.upgrade([url, options]): Promise`

Upgrade to a different protocol. See [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details.
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/docs/api/MockPool.md
Expand Up @@ -57,6 +57,7 @@ Returns: `MockInterceptor` corresponding to the input options.
* **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method.
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.

### Return: `MockInterceptor`

Expand Down
5 changes: 5 additions & 0 deletions deps/undici/src/lib/client.js
Expand Up @@ -873,6 +873,11 @@ class Parser {
// have been queued since then.
util.destroy(socket, new InformationalError('reset'))
return constants.ERROR.PAUSED
} else if (client[kPipelining] === 1) {
// We must wait a full event loop cycle to reuse this socket to make sure
// that non-spec compliant servers are not closing the connection even if they
// said they won't.
setImmediate(resume, client)
} else {
resume(client)
}
Expand Down
31 changes: 0 additions & 31 deletions deps/undici/src/lib/fetch/constants.js
@@ -1,28 +1,5 @@
'use strict'

const forbiddenHeaderNames = [
'accept-charset',
'accept-encoding',
'access-control-request-headers',
'access-control-request-method',
'connection',
'content-length',
'cookie',
'cookie2',
'date',
'dnt',
'expect',
'host',
'keep-alive',
'origin',
'referer',
'te',
'trailer',
'transfer-encoding',
'upgrade',
'via'
]

const corsSafeListedMethods = ['GET', 'HEAD', 'POST']

const nullBodyStatus = [101, 204, 205, 304]
Expand Down Expand Up @@ -58,9 +35,6 @@ const requestCache = [
'only-if-cached'
]

// https://fetch.spec.whatwg.org/#forbidden-response-header-name
const forbiddenResponseHeaderNames = ['set-cookie', 'set-cookie2']

const requestBodyHeader = [
'content-encoding',
'content-language',
Expand All @@ -86,20 +60,15 @@ const subresource = [
''
]

const corsSafeListedResponseHeaderNames = [] // TODO

module.exports = {
subresource,
forbiddenResponseHeaderNames,
corsSafeListedResponseHeaderNames,
forbiddenMethods,
requestBodyHeader,
referrerPolicy,
requestRedirect,
requestMode,
requestCredentials,
requestCache,
forbiddenHeaderNames,
redirectStatus,
corsSafeListedMethods,
nullBodyStatus,
Expand Down
8 changes: 4 additions & 4 deletions deps/undici/src/lib/fetch/formdata.js
Expand Up @@ -243,8 +243,8 @@ function makeEntry (name, value, filename) {
// object, representing the same bytes, whose name attribute value is "blob".
if (isBlobLike(value) && !isFileLike(value)) {
value = value instanceof Blob
? new File([value], 'blob')
: new FileLike(value, 'blob')
? new File([value], 'blob', value)
: new FileLike(value, 'blob', value)
}

// 4. If value is (now) a File object and filename is given, then set value to a
Expand All @@ -256,8 +256,8 @@ function makeEntry (name, value, filename) {
// creating one more File instance doesn't make much sense....
if (isFileLike(value) && filename != null) {
value = value instanceof File
? new File([value], filename)
: new FileLike(value, filename)
? new File([value], filename, value)
: new FileLike(value, filename, value)
}

// 5. Set entry’s value to value.
Expand Down
46 changes: 8 additions & 38 deletions deps/undici/src/lib/fetch/headers.js
Expand Up @@ -6,10 +6,6 @@ const { validateHeaderName, validateHeaderValue } = require('http')
const { kHeadersList } = require('../core/symbols')
const { kGuard } = require('./symbols')
const { kEnumerableProperty } = require('../core/util')
const {
forbiddenHeaderNames,
forbiddenResponseHeaderNames
} = require('./constants')

const kHeadersMap = Symbol('headers map')
const kHeadersSortedMap = Symbol('headers map sorted')
Expand Down Expand Up @@ -115,6 +111,11 @@ class HeadersList {
}
}

clear () {
this[kHeadersMap].clear()
this[kHeadersSortedMap] = null
}

append (name, value) {
this[kHeadersSortedMap] = null

Expand Down Expand Up @@ -211,22 +212,11 @@ class Headers {
)
}

const normalizedName = normalizeAndValidateHeaderName(String(name))

// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (
this[kGuard] === 'request' &&
forbiddenHeaderNames.includes(normalizedName)
) {
return
} else if (this[kGuard] === 'request-no-cors') {
// TODO
} else if (
this[kGuard] === 'response' &&
forbiddenResponseHeaderNames.includes(normalizedName)
) {
return
}

return this[kHeadersList].append(String(name), String(value))
Expand All @@ -244,22 +234,11 @@ class Headers {
)
}

const normalizedName = normalizeAndValidateHeaderName(String(name))

// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (
this[kGuard] === 'request' &&
forbiddenHeaderNames.includes(normalizedName)
) {
return
} else if (this[kGuard] === 'request-no-cors') {
// TODO
} else if (
this[kGuard] === 'response' &&
forbiddenResponseHeaderNames.includes(normalizedName)
) {
return
}

return this[kHeadersList].delete(String(name))
Expand Down Expand Up @@ -307,20 +286,11 @@ class Headers {
)
}

// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
throw new TypeError('immutable')
} else if (
this[kGuard] === 'request' &&
forbiddenHeaderNames.includes(String(name).toLocaleLowerCase())
) {
return
} else if (this[kGuard] === 'request-no-cors') {
// TODO
} else if (
this[kGuard] === 'response' &&
forbiddenResponseHeaderNames.includes(String(name).toLocaleLowerCase())
) {
return
}

return this[kHeadersList].set(String(name), String(value))
Expand Down
11 changes: 5 additions & 6 deletions deps/undici/src/lib/fetch/request.js
Expand Up @@ -384,8 +384,8 @@ class Request {
// Realm, whose header list is request’s header list and guard is
// "request".
this[kHeaders] = new Headers()
this[kHeaders][kGuard] = 'request'
this[kHeaders][kHeadersList] = request.headersList
this[kHeaders][kGuard] = 'request'
this[kHeaders][kRealm] = this[kRealm]

// 31. If this’s request’s mode is "no-cors", then:
Expand All @@ -406,26 +406,25 @@ class Request {
if (Object.keys(init).length !== 0) {
// 1. Let headers be a copy of this’s headers and its associated header
// list.
let headers = new Headers(this.headers)
let headers = new Headers(this[kHeaders])

// 2. If init["headers"] exists, then set headers to init["headers"].
if (init.headers !== undefined) {
headers = init.headers
}

// 3. Empty this’s headers’s header list.
this[kState].headersList = new HeadersList()
this[kHeaders][kHeadersList] = this[kState].headersList
this[kHeaders][kHeadersList].clear()

// 4. If headers is a Headers object, then for each header in its header
// list, append header’s name/header’s value to this’s headers.
if (headers.constructor.name === 'Headers') {
for (const [key, val] of headers[kHeadersList] || headers) {
for (const [key, val] of headers) {
this[kHeaders].append(key, val)
}
} else {
// 5. Otherwise, fill this’s headers with headers.
fillHeaders(this[kState].headersList, headers)
fillHeaders(this[kHeaders], headers)
}
}

Expand Down
37 changes: 6 additions & 31 deletions deps/undici/src/lib/fetch/response.js
Expand Up @@ -8,9 +8,7 @@ const { kEnumerableProperty } = util
const { responseURL, isValidReasonPhrase, toUSVString, isCancelled, isAborted, serializeJavascriptValueToJSONString } = require('./util')
const {
redirectStatus,
nullBodyStatus,
forbiddenResponseHeaderNames,
corsSafeListedResponseHeaderNames
nullBodyStatus
} = require('./constants')
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
const { kHeadersList } = require('../core/symbols')
Expand Down Expand Up @@ -380,28 +378,6 @@ function makeFilteredResponse (response, state) {
})
}

function makeFilteredHeadersList (headersList, filter) {
return new Proxy(headersList, {
get (target, prop) {
// Override methods used by Headers class.
if (prop === 'get' || prop === 'has') {
const defaultReturn = prop === 'has' ? false : null
return (name) => filter(name) ? target[prop](name) : defaultReturn
} else if (prop === Symbol.iterator) {
return function * () {
for (const entry of target) {
if (filter(entry[0])) {
yield entry
}
}
}
} else {
return target[prop]
}
}
})
}

// https://fetch.spec.whatwg.org/#concept-filtered-response
function filterResponse (response, type) {
// Set response to the following filtered response with response as its
Expand All @@ -411,22 +387,21 @@ function filterResponse (response, type) {
// and header list excludes any headers in internal response’s header list
// whose name is a forbidden response-header name.

// Note: undici does not implement forbidden response-header names
return makeFilteredResponse(response, {
type: 'basic',
headersList: makeFilteredHeadersList(
response.headersList,
(name) => !forbiddenResponseHeaderNames.includes(name.toLowerCase())
)
headersList: response.headersList
})
} else if (type === 'cors') {
// A CORS filtered response is a filtered response whose type is "cors"
// and header list excludes any headers in internal response’s header
// list whose name is not a CORS-safelisted response-header name, given
// internal response’s CORS-exposed header-name list.

// Note: undici does not implement CORS-safelisted response-header names
return makeFilteredResponse(response, {
type: 'cors',
headersList: makeFilteredHeadersList(response.headersList, (name) => !corsSafeListedResponseHeaderNames.includes(name))
headersList: response.headersList
})
} else if (type === 'opaque') {
// An opaque filtered response is a filtered response whose type is
Expand All @@ -449,7 +424,7 @@ function filterResponse (response, type) {
type: 'opaqueredirect',
status: 0,
statusText: '',
headersList: makeFilteredHeadersList(response.headersList, () => false),
headersList: [],
body: null
})
} else {
Expand Down
11 changes: 8 additions & 3 deletions deps/undici/src/lib/mock/mock-interceptor.js
Expand Up @@ -10,6 +10,7 @@ const {
kMockDispatch
} = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')
const { buildURL } = require('../core/util')

/**
* Defines the scope API for an interceptor reply
Expand Down Expand Up @@ -70,9 +71,13 @@ class MockInterceptor {
// As per RFC 3986, clients are not supposed to send URI
// fragments to servers when they retrieve a document,
if (typeof opts.path === 'string') {
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
const parsedURL = new URL(opts.path, 'data://')
opts.path = parsedURL.pathname + parsedURL.search
if (opts.query) {
opts.path = buildURL(opts.path, opts.query)
} else {
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
const parsedURL = new URL(opts.path, 'data://')
opts.path = parsedURL.pathname + parsedURL.search
}
}
if (typeof opts.method === 'string') {
opts.method = opts.method.toUpperCase()
Expand Down
12 changes: 8 additions & 4 deletions deps/undici/src/lib/mock/mock-utils.js
Expand Up @@ -8,6 +8,7 @@ const {
kOrigin,
kGetNetConnect
} = require('./mock-symbols')
const { buildURL } = require('../core/util')

function matchValue (match, value) {
if (typeof match === 'string') {
Expand Down Expand Up @@ -98,10 +99,12 @@ function getResponseData (data) {
}

function getMockDispatch (mockDispatches, key) {
const resolvedPath = key.query ? buildURL(key.path, key.query) : key.path

// Match path
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, key.path))
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, resolvedPath))
if (matchedMockDispatches.length === 0) {
throw new MockNotMatchedError(`Mock dispatch not matched for path '${key.path}'`)
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)
}

// Match method
Expand Down Expand Up @@ -146,12 +149,13 @@ function deleteMockDispatch (mockDispatches, key) {
}

function buildKey (opts) {
const { path, method, body, headers } = opts
const { path, method, body, headers, query } = opts
return {
path,
method,
body,
headers
headers,
query
}
}

Expand Down

0 comments on commit dc3b91f

Please sign in to comment.