Skip to content

Commit 0354678

Browse files
nodejs-github-botRafaelGSS
authored andcommitted
deps: update undici to 8.9.0
PR-URL: #64712 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent b3fbb6e commit 0354678

38 files changed

Lines changed: 1617 additions & 390 deletions

deps/undici/src/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,6 @@ AGENTS.md
9393
.githuman
9494

9595
benchmarks/package-lock.json
96+
97+
# File generated by /scripts/platform-shell.js
98+
scripts/.npmrc

deps/undici/src/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ For more information about their behavior, please reference the body mixin from
408408

409409
This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site.
410410

411+
For the top-level APIs below, the `url` argument supplies the request origin and
412+
path. Do not pass `origin` or `path` in the second `options` argument. The linked
413+
`Dispatcher` option types include those fields because dispatcher methods are
414+
lower-level APIs that do not receive a separate `url` argument.
415+
411416
### `undici.request([url, options]): Promise`
412417

413418
Arguments:

deps/undici/src/SECURITY.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ meet the following criteria:
8787
* Dependencies installed by the application.
8888
* The DNS resolution results provided by the operating system or configured
8989
resolvers.
90+
* The network environment in which the process runs, including any private or
91+
internal networks it can reach, for the privacy of traffic on that network.
92+
Deployments that require stronger isolation, monitoring, or egress controls
93+
are responsible for providing them.
94+
* Any proxy server configured by the application, runtime, or environment.
95+
Proxies are trusted network intermediaries and must be authorized by the
96+
relevant network or deployment authority. Undici's proxy support is for
97+
routing traffic through such proxies, for example to reach external networks
98+
through a firewall; it is not intended to provide anonymity, hide traffic, or
99+
bypass organizational, regulatory, or legal controls. Untrusted proxies are
100+
outside the supported threat model.
90101

91102
In other words, if untrusted data passing through undici to the application
92103
can trigger actions other than those documented for the APIs, there is likely
@@ -172,6 +183,16 @@ lead to a loss of confidentiality, integrity, or availability.
172183
input to request options) are the application's responsibility, not
173184
vulnerabilities in undici.
174185

186+
#### Unauthorized or untrusted proxy use
187+
188+
* If an application configures undici to use a proxy that is untrusted,
189+
malicious, or not authorized by the relevant network or deployment authority,
190+
any resulting privacy loss, policy bypass, regulatory exposure, traffic
191+
manipulation, or unavailability is the deployment's responsibility. Undici
192+
cannot determine whether a proxy is authorized for a user's network or
193+
jurisdiction, and preventing use of proxies to hide traffic or evade controls
194+
is a non-goal.
195+
175196
## Receiving security updates
176197

177198
Security notifications will be distributed via

deps/undici/src/docs/docs/api/Client.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ added: v1.0.0
4949
* `headersTimeout` {number|null} The timeout, in milliseconds, the parser
5050
waits to receive the complete HTTP headers before the request times out. Use
5151
`0` to disable it entirely. **Default:** `300e3`.
52+
HTTP/1.1 headers/body parser timeouts are not guaranteed to fire with exact
53+
millisecond precision: delays up to 1000ms use native timers, while larger
54+
delays use undici's lower-overhead fast timers with a target resolution
55+
around 500ms.
5256
* `connectTimeout` {number|null} The timeout, in milliseconds, for
5357
establishing a socket connection. Use `0` to disable it entirely.
5458
**Default:** `10e3`.

deps/undici/src/docs/docs/api/Dispatcher.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ added: v4.0.0
228228
* `bodyTimeout` {number|null} The time, in milliseconds, after which the request
229229
times out while receiving body data. Monitors the time between body chunks.
230230
Use `0` to disable it entirely. Defaults to 300 seconds.
231+
HTTP/1.1 headers/body parser timeouts are not guaranteed to fire with exact
232+
millisecond precision: delays up to 1000ms use native timers, while larger
233+
delays use undici's lower-overhead fast timers with a target resolution
234+
around 500ms.
231235
* `reset` {boolean} Whether the request should establish a keep-alive
232236
connection. **Default:** `false`.
233237
* `expectContinue` {boolean} For HTTP/2, appends the `expect: 100-continue`
@@ -729,6 +733,8 @@ await client.request({ path: '/', method: 'GET' })
729733
```
730734

731735
For the full list of built-in interceptors provided by undici, see [Interceptors](Interceptors.md).
736+
For an example of a custom interceptor that wraps handler callbacks, see
737+
[Custom interceptors](Interceptors.md#custom-interceptors).
732738

733739
### Event: `'connect'`
734740

deps/undici/src/docs/docs/api/Interceptors.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,65 @@ const client = new Client('https://example.com').compose(
3232

3333
---
3434

35+
## Custom interceptors
36+
37+
Custom interceptors use the same shape as
38+
[`dispatcher.compose()`](./Dispatcher.md#dispatchercomposeinterceptors-interceptor):
39+
an interceptor takes a `dispatch` function and returns another dispatch-like
40+
function with the same `(options, handler)` signature.
41+
42+
When an interceptor wraps the handler, forward the callbacks that it does not
43+
handle itself. The complete handler callback list is documented under
44+
[`dispatcher.dispatch(options, handler)`](./Dispatcher.md#dispatcherdispatchoptions-handler).
45+
46+
```js
47+
import { Agent } from 'undici'
48+
49+
const timingInterceptor = dispatch => {
50+
return (options, handler) => {
51+
const started = performance.now()
52+
53+
return dispatch(options, {
54+
...handler,
55+
onResponseStart (controller, statusCode, headers, statusMessage) {
56+
const duration = Math.round(performance.now() - started)
57+
const method = options.method ?? 'GET'
58+
const origin = options.origin ?? ''
59+
60+
console.log(`${method} ${origin}${options.path} -> ${statusCode} in ${duration}ms`)
61+
62+
return handler.onResponseStart?.(
63+
controller,
64+
statusCode,
65+
headers,
66+
statusMessage
67+
)
68+
},
69+
onResponseError (controller, error) {
70+
const duration = Math.round(performance.now() - started)
71+
72+
console.error(`request failed after ${duration}ms`, error)
73+
74+
return handler.onResponseError?.(controller, error)
75+
}
76+
})
77+
}
78+
}
79+
80+
const dispatcher = new Agent().compose(timingInterceptor)
81+
82+
const { body } = await dispatcher.request({
83+
origin: 'https://example.com',
84+
path: '/',
85+
method: 'GET'
86+
})
87+
88+
await body.dump()
89+
await dispatcher.close()
90+
```
91+
92+
---
93+
3594
## `interceptors.dump([opts])`
3695
3796
Reads and discards the response body up to a configurable size limit. Useful

deps/undici/src/docs/docs/api/MockAgent.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ registered on the {MockClient} or {MockPool} instances returned by
1717
`MockAgent` is set as the dispatcher (for example through
1818
[`setGlobalDispatcher()`][] or a per-request `dispatcher` option).
1919

20+
> [!NOTE]
21+
> [`setGlobalDispatcher()`][] only affects undici APIs that use the global
22+
> dispatcher, such as `request()` and `fetch()`. It does not replace or
23+
> monkeypatch {Pool} or {Client} instances that were created separately. To test
24+
> code that accepts or creates a pool/client directly, pass the {MockPool} or
25+
> {MockClient} returned by [`mockAgent.get(origin)`][] into that code.
26+
2027
```mjs
2128
import { MockAgent } from 'undici'
2229

@@ -139,6 +146,26 @@ for await (const data of body) {
139146
}
140147
```
141148

149+
```mjs displayName="Testing code that accepts a pool"
150+
import { MockAgent } from 'undici'
151+
152+
async function getStatus (pool) {
153+
const { statusCode } = await pool.request({
154+
path: '/foo',
155+
method: 'GET'
156+
})
157+
158+
return statusCode
159+
}
160+
161+
const mockAgent = new MockAgent()
162+
const mockPool = mockAgent.get('http://localhost:3000')
163+
164+
mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200)
165+
166+
console.log(await getStatus(mockPool)) // 200
167+
```
168+
142169
```mjs displayName="Returning a MockClient"
143170
import { MockAgent, request } from 'undici'
144171

deps/undici/src/docs/docs/api/MockPool.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ The reply behaviour of a matching request is defined through the returned
136136
computing all reply options dynamically rather than just the body.
137137
* `callback` {Function} A `(opts: MockResponseCallbackOptions) =>
138138
{ statusCode, data, responseOptions }` function invoked with the incoming
139-
request.
139+
request. The callback may be asynchronous; a returned promise is awaited
140+
and must resolve to the same shape.
140141
* Returns: {MockScope}
141142
* `replyWithError(error)` {Function} Defines an error for a matching request to
142143
throw.
@@ -263,6 +264,32 @@ for await (const data of body) {
263264
}
264265
```
265266

267+
```mjs displayName="Reply with an asynchronous options callback"
268+
import { readFile } from 'node:fs/promises'
269+
import { MockAgent, setGlobalDispatcher, request } from 'undici'
270+
271+
const mockAgent = new MockAgent()
272+
setGlobalDispatcher(mockAgent)
273+
274+
const mockPool = mockAgent.get('http://localhost:3000')
275+
276+
mockPool.intercept({
277+
path: '/fixture',
278+
method: 'GET'
279+
}).reply(async ({ path }) => ({
280+
statusCode: 200,
281+
data: await readFile(new URL('./fixture.json', import.meta.url))
282+
}))
283+
284+
const { statusCode, body } = await request('http://localhost:3000/fixture')
285+
286+
console.log('response received', statusCode) // response received 200
287+
288+
for await (const data of body) {
289+
console.log('data', data.toString('utf8')) // contents of fixture.json
290+
}
291+
```
292+
266293
```mjs displayName="Multiple intercepts"
267294
import { MockAgent, setGlobalDispatcher, request } from 'undici'
268295

deps/undici/src/docs/docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,11 @@ For more information about their behavior, please reference the body mixin from
410410

411411
This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site.
412412

413+
For the top-level APIs below, the `url` argument supplies the request origin and
414+
path. Do not pass `origin` or `path` in the second `options` argument. The linked
415+
`Dispatcher` option types include those fields because dispatcher methods are
416+
lower-level APIs that do not receive a separate `url` argument.
417+
413418
### `undici.request([url, options])`
414419

415420
* `url` {string|URL|UrlObject}

deps/undici/src/lib/cache/memory-cache-store.js

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,62 @@ class MemoryCacheStore extends EventEmitter {
218218
}
219219

220220
function findEntry (key, entries, now) {
221-
return entries.find((entry) => (
222-
entry.deleteAt > now &&
223-
entry.method === key.method &&
224-
(entry.vary == null || Object.keys(entry.vary).every(headerName => {
225-
if (entry.vary[headerName] === null) {
226-
return key.headers[headerName] === undefined
221+
for (let i = 0; i < entries.length; i++) {
222+
const entry = entries[i]
223+
if (
224+
entry.deleteAt > now &&
225+
entry.method === key.method &&
226+
varyMatches(key, entry)
227+
) {
228+
return entry
229+
}
230+
}
231+
}
232+
233+
function varyMatches (key, entry) {
234+
if (entry.vary == null) {
235+
return true
236+
}
237+
238+
for (const headerName in entry.vary) {
239+
if (Object.hasOwn(entry.vary, headerName) && !headerValueEquals(key.headers?.[headerName], entry.vary[headerName])) {
240+
return false
241+
}
242+
}
243+
244+
return true
245+
}
246+
247+
/**
248+
* @param {string|string[]|null|undefined} lhs
249+
* @param {string|string[]|null|undefined} rhs
250+
* @returns {boolean}
251+
*/
252+
function headerValueEquals (lhs, rhs) {
253+
if (lhs == null && rhs == null) {
254+
return true
255+
}
256+
257+
if ((lhs == null && rhs != null) ||
258+
(lhs != null && rhs == null)) {
259+
return false
260+
}
261+
262+
if (Array.isArray(lhs) && Array.isArray(rhs)) {
263+
if (lhs.length !== rhs.length) {
264+
return false
265+
}
266+
267+
for (let i = 0; i < lhs.length; i++) {
268+
if (lhs[i] !== rhs[i]) {
269+
return false
227270
}
271+
}
272+
273+
return true
274+
}
228275

229-
return entry.vary[headerName] === key.headers[headerName]
230-
}))
231-
))
276+
return lhs === rhs
232277
}
233278

234279
module.exports = MemoryCacheStore

0 commit comments

Comments
 (0)