Skip to content

Commit cf44df3

Browse files
nodejs-github-botaduh95
authored andcommitted
deps: update undici to 7.28.0
PR-URL: #63703 Reviewed-By: Trivikram Kamat <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent f95bedd commit cf44df3

37 files changed

Lines changed: 1766 additions & 1092 deletions

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Returns: `Client`
2424
* **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `2e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 2 seconds.
2525
* **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB.
2626
* **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
27+
* **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
28+
* **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit.
29+
* **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit.
2730
* **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
2831
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
2932
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. **Security Warning:** Disabling this option can expose your application to HTTP Request Smuggling attacks, where mismatched content-length headers cause servers and proxies to interpret request boundaries differently. This can lead to cache poisoning, credential hijacking, and bypassing security controls. Only disable this in controlled environments where you fully trust the request source.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,33 @@ Arguments:
8080

8181
Returns: `Cookie[]`
8282

83+
## `parseCookie(cookie)`
84+
85+
Parses a single `Set-Cookie` header value into a `Cookie` object.
86+
87+
```js
88+
import { parseCookie } from 'undici'
89+
90+
console.log(parseCookie('undici=getSetCookies; Secure; SameSite=Lax'))
91+
// {
92+
// name: 'undici',
93+
// value: 'getSetCookies',
94+
// secure: true,
95+
// sameSite: 'Lax'
96+
// }
97+
```
98+
99+
Notes:
100+
101+
* The cookie value is returned as it appears in the header. Percent-encoded sequences such as `%20` or `%0D%0A` are **not** decoded.
102+
* `sameSite` is only set for exact case-insensitive matches of `Strict`, `Lax`, or `None`.
103+
104+
Arguments:
105+
106+
* **cookie** `string`
107+
108+
Returns: `Cookie | null`
109+
83110
## `setCookie(headers, cookie)`
84111

85112
Appends a cookie to the `Set-Cookie` header.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions)
2222
* **password** `string` (optional) - SOCKS5 proxy password for authentication. Can also be provided in the proxy URL.
2323
* **connect** `Function` (optional) - Custom connector function for the proxy connection.
2424
* **proxyTls** `BuildOptions` (optional) - TLS options for the proxy connection (when using SOCKS5 over TLS).
25+
* **requestTls** `BuildOptions` (optional) - TLS options applied to the HTTPS connection to the target server through the SOCKS5 tunnel. Use this to configure `ca`, `cert`, `key`, `rejectUnauthorized`, `servername`, etc. for the target HTTPS endpoint.
2526

2627
Examples:
2728

deps/undici/src/lib/api/api-request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RequestHandler extends AsyncResource {
2121
throw new InvalidArgumentError('invalid callback')
2222
}
2323

24-
if (highWaterMark && (typeof highWaterMark !== 'number' || highWaterMark < 0)) {
24+
if (highWaterMark != null && (!Number.isFinite(highWaterMark) || highWaterMark < 0)) {
2525
throw new InvalidArgumentError('invalid highWaterMark')
2626
}
2727

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ module.exports = class SqliteCacheStore {
216216
SELECT
217217
id
218218
FROM cacheInterceptorV${VERSION}
219-
ORDER BY cachedAt DESC
219+
ORDER BY cachedAt ASC
220220
LIMIT ?
221221
)
222222
`)
@@ -283,7 +283,6 @@ module.exports = class SqliteCacheStore {
283283
existingValue.id
284284
)
285285
} else {
286-
this.#prune()
287286
// New response, let's insert it
288287
this.#insertValueQuery.run(
289288
url,
@@ -299,6 +298,7 @@ module.exports = class SqliteCacheStore {
299298
value.cachedAt,
300299
value.staleAt
301300
)
301+
this.#prune()
302302
}
303303
}
304304

@@ -409,7 +409,7 @@ module.exports = class SqliteCacheStore {
409409
const now = Date.now()
410410
for (const value of values) {
411411
if (now >= value.deleteAt && !canBeExpired) {
412-
return undefined
412+
continue
413413
}
414414

415415
let matches = true

deps/undici/src/lib/core/connect.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ const SessionCache = class WeakSessionCache {
3838
return
3939
}
4040

41+
if (this._sessionCache.has(sessionKey)) {
42+
this._sessionCache.delete(sessionKey)
43+
} else if (this._sessionCache.size >= this._maxCachedSessions) {
44+
for (const [key, ref] of this._sessionCache) {
45+
if (ref.deref() === undefined) {
46+
this._sessionCache.delete(key)
47+
return
48+
}
49+
}
50+
51+
const oldest = this._sessionCache.keys().next()
52+
if (!oldest.done) {
53+
this._sessionCache.delete(oldest.value)
54+
}
55+
}
56+
4157
this._sessionCache.set(sessionKey, new WeakRef(session))
4258
this._sessionRegistry.register(session, sessionKey)
4359
}

deps/undici/src/lib/core/request.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ const { headerNameLowerCasedRecord } = require('./constants')
2727
// Verifies that a given path is valid does not contain control chars \x00 to \x20
2828
const invalidPathRegex = /[^\u0021-\u00ff]/
2929

30+
function isValidContentLengthHeaderValue (val) {
31+
if (typeof val !== 'string' || val.length === 0) {
32+
return false
33+
}
34+
35+
for (let i = 0; i < val.length; i++) {
36+
const charCode = val.charCodeAt(i)
37+
if (charCode < 48 || charCode > 57) {
38+
return false
39+
}
40+
}
41+
42+
return true
43+
}
44+
3045
const kHandler = Symbol('handler')
3146

3247
class Request {
@@ -402,10 +417,10 @@ function processHeader (request, key, val) {
402417
if (request.contentLength !== null) {
403418
throw new InvalidArgumentError('duplicate content-length header')
404419
}
405-
request.contentLength = parseInt(val, 10)
406-
if (!Number.isFinite(request.contentLength)) {
420+
if (!isValidContentLengthHeaderValue(val)) {
407421
throw new InvalidArgumentError('invalid content-length header')
408422
}
423+
request.contentLength = parseInt(val, 10)
409424
} else if (request.contentType === null && headerName === 'content-type') {
410425
request.contentType = val
411426
request.headers.push(key, val)

deps/undici/src/lib/core/socks5-client.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { debuglog } = require('node:util')
77
const { parseAddress } = require('./socks5-utils')
88

99
const debug = debuglog('undici:socks5')
10+
const EMPTY_BUFFER = Buffer.alloc(0)
1011

1112
// SOCKS5 constants
1213
const SOCKS_VERSION = 0x05
@@ -51,6 +52,7 @@ const STATES = {
5152
INITIAL: 'initial',
5253
HANDSHAKING: 'handshaking',
5354
AUTHENTICATING: 'authenticating',
55+
AUTHENTICATED: 'authenticated',
5456
CONNECTING: 'connecting',
5557
CONNECTED: 'connected',
5658
ERROR: 'error',
@@ -72,7 +74,10 @@ class Socks5Client extends EventEmitter {
7274
this.socket = socket
7375
this.options = options
7476
this.state = STATES.INITIAL
75-
this.buffer = Buffer.alloc(0)
77+
this.buffer = EMPTY_BUFFER
78+
this.onSocketData = this.onData.bind(this)
79+
this.onSocketError = this.onError.bind(this)
80+
this.onSocketClose = this.onClose.bind(this)
7681

7782
// Authentication settings
7883
this.authMethods = []
@@ -82,9 +87,9 @@ class Socks5Client extends EventEmitter {
8287
this.authMethods.push(AUTH_METHODS.NO_AUTH)
8388

8489
// Socket event handlers
85-
this.socket.on('data', this.onData.bind(this))
86-
this.socket.on('error', this.onError.bind(this))
87-
this.socket.on('close', this.onClose.bind(this))
90+
this.socket.on('data', this.onSocketData)
91+
this.socket.on('error', this.onSocketError)
92+
this.socket.on('close', this.onSocketClose)
8893
}
8994

9095
/**
@@ -139,6 +144,11 @@ class Socks5Client extends EventEmitter {
139144
}
140145
}
141146

147+
markAuthenticated () {
148+
this.state = STATES.AUTHENTICATED
149+
this.emit('authenticated')
150+
}
151+
142152
/**
143153
* Start the SOCKS5 handshake
144154
*/
@@ -189,7 +199,7 @@ class Socks5Client extends EventEmitter {
189199
debug('server selected auth method', method)
190200

191201
if (method === AUTH_METHODS.NO_AUTH) {
192-
this.emit('authenticated')
202+
this.markAuthenticated()
193203
} else if (method === AUTH_METHODS.USERNAME_PASSWORD) {
194204
this.state = STATES.AUTHENTICATING
195205
this.sendAuthRequest()
@@ -254,7 +264,7 @@ class Socks5Client extends EventEmitter {
254264

255265
this.buffer = this.buffer.subarray(2)
256266
debug('authentication successful')
257-
this.emit('authenticated')
267+
this.markAuthenticated()
258268
}
259269

260270
/**
@@ -263,8 +273,12 @@ class Socks5Client extends EventEmitter {
263273
* @param {number} port - Target port
264274
*/
265275
connect (address, port) {
266-
if (this.state === STATES.CONNECTED) {
267-
throw new InvalidArgumentError('Already connected')
276+
if (this.state === STATES.CONNECTING || this.state === STATES.CONNECTED) {
277+
throw new InvalidArgumentError('Connection already in progress')
278+
}
279+
280+
if (this.state !== STATES.AUTHENTICATED) {
281+
throw new InvalidArgumentError('Client must be authenticated before CONNECT')
268282
}
269283

270284
debug('connecting to', address, port)
@@ -363,8 +377,9 @@ class Socks5Client extends EventEmitter {
363377

364378
const boundPort = this.buffer.readUInt16BE(offset)
365379

366-
this.buffer = this.buffer.subarray(responseLength)
380+
this.buffer = EMPTY_BUFFER
367381
this.state = STATES.CONNECTED
382+
this.socket.removeListener('data', this.onSocketData)
368383

369384
debug('connected, bound address:', boundAddress, 'port:', boundPort)
370385
this.emit('connected', { address: boundAddress, port: boundPort })

deps/undici/src/lib/core/socks5-utils.js

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,43 @@ function parseAddress (address) {
4646
*/
4747
function parseIPv6 (address) {
4848
const buffer = Buffer.alloc(16)
49-
const parts = address.split(':')
50-
let partIndex = 0
51-
let bufferIndex = 0
49+
let normalizedAddress = address
50+
51+
// Expand an embedded IPv4 tail into the last two IPv6 groups.
52+
if (address.includes('.')) {
53+
const lastColonIndex = address.lastIndexOf(':')
54+
const ipv4Part = address.slice(lastColonIndex + 1)
55+
56+
if (net.isIPv4(ipv4Part)) {
57+
const octets = ipv4Part.split('.').map(Number)
58+
const high = ((octets[0] << 8) | octets[1]).toString(16)
59+
const low = ((octets[2] << 8) | octets[3]).toString(16)
60+
normalizedAddress = `${address.slice(0, lastColonIndex)}:${high}:${low}`
61+
}
62+
}
5263

5364
// Handle compressed notation (::)
54-
const doubleColonIndex = address.indexOf('::')
65+
const doubleColonIndex = normalizedAddress.indexOf('::')
5566
if (doubleColonIndex !== -1) {
56-
// Count non-empty parts
57-
const nonEmptyParts = parts.filter(p => p.length > 0).length
58-
const skipParts = 8 - nonEmptyParts
59-
60-
for (let i = 0; i < parts.length; i++) {
61-
if (parts[i] === '' && i === doubleColonIndex / 3) {
62-
// Skip empty parts for ::
63-
bufferIndex += skipParts * 2
64-
} else if (parts[i] !== '') {
65-
const value = parseInt(parts[i], 16)
66-
buffer.writeUInt16BE(value, bufferIndex)
67-
bufferIndex += 2
68-
}
67+
const before = normalizedAddress.slice(0, doubleColonIndex)
68+
const after = normalizedAddress.slice(doubleColonIndex + 2)
69+
const beforeParts = before === '' ? [] : before.split(':')
70+
const afterParts = after === '' ? [] : after.split(':')
71+
72+
let bufferIndex = 0
73+
for (const part of beforeParts) {
74+
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
75+
bufferIndex += 2
76+
}
77+
bufferIndex = 16 - afterParts.length * 2
78+
for (const part of afterParts) {
79+
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
80+
bufferIndex += 2
6981
}
7082
} else {
71-
// No compression, parse normally
72-
for (const part of parts) {
73-
if (part === '') continue
74-
const value = parseInt(part, 16)
75-
buffer.writeUInt16BE(value, partIndex * 2)
76-
partIndex++
83+
const parts = normalizedAddress.split(':')
84+
for (let i = 0; i < parts.length; i++) {
85+
buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
7786
}
7887
}
7988

deps/undici/src/lib/dispatcher/agent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Agent extends DispatcherBase {
3535
throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
3636
}
3737

38-
super()
38+
super(options)
3939

4040
if (connect && typeof connect !== 'function') {
4141
connect = { ...connect }

0 commit comments

Comments
 (0)