Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deps/undici/src/lib/api/api-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RequestHandler extends AsyncResource {
throw new InvalidArgumentError('invalid callback')
}

if (highWaterMark && (typeof highWaterMark !== 'number' || highWaterMark < 0)) {
if (highWaterMark != null && (!Number.isFinite(highWaterMark) || highWaterMark < 0)) {
throw new InvalidArgumentError('invalid highWaterMark')
}

Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/cache/sqlite-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ module.exports = class SqliteCacheStore {
SELECT
id
FROM cacheInterceptorV${VERSION}
ORDER BY cachedAt DESC
ORDER BY cachedAt ASC
LIMIT ?
)
`)
Expand Down Expand Up @@ -283,7 +283,6 @@ module.exports = class SqliteCacheStore {
existingValue.id
)
} else {
this.#prune()
// New response, let's insert it
this.#insertValueQuery.run(
url,
Expand All @@ -299,6 +298,7 @@ module.exports = class SqliteCacheStore {
value.cachedAt,
value.staleAt
)
this.#prune()
}
}

Expand Down Expand Up @@ -409,7 +409,7 @@ module.exports = class SqliteCacheStore {
const now = Date.now()
for (const value of values) {
if (now >= value.deleteAt && !canBeExpired) {
return undefined
continue
}

let matches = true
Expand Down
16 changes: 16 additions & 0 deletions deps/undici/src/lib/core/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ const SessionCache = class WeakSessionCache {
return
}

if (this._sessionCache.has(sessionKey)) {
this._sessionCache.delete(sessionKey)
} else if (this._sessionCache.size >= this._maxCachedSessions) {
for (const [key, ref] of this._sessionCache) {
if (ref.deref() === undefined) {
this._sessionCache.delete(key)
return
}
}

const oldest = this._sessionCache.keys().next()
if (!oldest.done) {
this._sessionCache.delete(oldest.value)
}
}

this._sessionCache.set(sessionKey, new WeakRef(session))
this._sessionRegistry.register(session, sessionKey)
}
Expand Down
19 changes: 17 additions & 2 deletions deps/undici/src/lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ const { headerNameLowerCasedRecord } = require('./constants')
// Verifies that a given path is valid does not contain control chars \x00 to \x20
const invalidPathRegex = /[^\u0021-\u00ff]/

function isValidContentLengthHeaderValue (val) {
if (typeof val !== 'string' || val.length === 0) {
return false
}

for (let i = 0; i < val.length; i++) {
const charCode = val.charCodeAt(i)
if (charCode < 48 || charCode > 57) {
return false
}
}

return true
}

const kHandler = Symbol('handler')

class Request {
Expand Down Expand Up @@ -402,10 +417,10 @@ function processHeader (request, key, val) {
if (request.contentLength !== null) {
throw new InvalidArgumentError('duplicate content-length header')
}
request.contentLength = parseInt(val, 10)
if (!Number.isFinite(request.contentLength)) {
if (!isValidContentLengthHeaderValue(val)) {
throw new InvalidArgumentError('invalid content-length header')
}
request.contentLength = parseInt(val, 10)
} else if (request.contentType === null && headerName === 'content-type') {
request.contentType = val
request.headers.push(key, val)
Expand Down
15 changes: 10 additions & 5 deletions deps/undici/src/lib/core/socks5-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { debuglog } = require('node:util')
const { parseAddress } = require('./socks5-utils')

const debug = debuglog('undici:socks5')
const EMPTY_BUFFER = Buffer.alloc(0)

// SOCKS5 constants
const SOCKS_VERSION = 0x05
Expand Down Expand Up @@ -72,7 +73,10 @@ class Socks5Client extends EventEmitter {
this.socket = socket
this.options = options
this.state = STATES.INITIAL
this.buffer = Buffer.alloc(0)
this.buffer = EMPTY_BUFFER
this.onSocketData = this.onData.bind(this)
this.onSocketError = this.onError.bind(this)
this.onSocketClose = this.onClose.bind(this)

// Authentication settings
this.authMethods = []
Expand All @@ -82,9 +86,9 @@ class Socks5Client extends EventEmitter {
this.authMethods.push(AUTH_METHODS.NO_AUTH)

// Socket event handlers
this.socket.on('data', this.onData.bind(this))
this.socket.on('error', this.onError.bind(this))
this.socket.on('close', this.onClose.bind(this))
this.socket.on('data', this.onSocketData)
this.socket.on('error', this.onSocketError)
this.socket.on('close', this.onSocketClose)
}

/**
Expand Down Expand Up @@ -363,8 +367,9 @@ class Socks5Client extends EventEmitter {

const boundPort = this.buffer.readUInt16BE(offset)

this.buffer = this.buffer.subarray(responseLength)
this.buffer = EMPTY_BUFFER
this.state = STATES.CONNECTED
this.socket.removeListener('data', this.onSocketData)

debug('connected, bound address:', boundAddress, 'port:', boundPort)
this.emit('connected', { address: boundAddress, port: boundPort })
Expand Down
39 changes: 17 additions & 22 deletions deps/undici/src/lib/core/socks5-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,34 +46,29 @@ function parseAddress (address) {
*/
function parseIPv6 (address) {
const buffer = Buffer.alloc(16)
const parts = address.split(':')
let partIndex = 0
let bufferIndex = 0

// Handle compressed notation (::)
const doubleColonIndex = address.indexOf('::')
if (doubleColonIndex !== -1) {
// Count non-empty parts
const nonEmptyParts = parts.filter(p => p.length > 0).length
const skipParts = 8 - nonEmptyParts

for (let i = 0; i < parts.length; i++) {
if (parts[i] === '' && i === doubleColonIndex / 3) {
// Skip empty parts for ::
bufferIndex += skipParts * 2
} else if (parts[i] !== '') {
const value = parseInt(parts[i], 16)
buffer.writeUInt16BE(value, bufferIndex)
bufferIndex += 2
}
const before = address.slice(0, doubleColonIndex)
const after = address.slice(doubleColonIndex + 2)
const beforeParts = before === '' ? [] : before.split(':')
const afterParts = after === '' ? [] : after.split(':')

let bufferIndex = 0
for (const part of beforeParts) {
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
bufferIndex += 2
}
bufferIndex = 16 - afterParts.length * 2
for (const part of afterParts) {
buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
bufferIndex += 2
}
} else {
// No compression, parse normally
for (const part of parts) {
if (part === '') continue
const value = parseInt(part, 16)
buffer.writeUInt16BE(value, partIndex * 2)
partIndex++
const parts = address.split(':')
for (let i = 0; i < parts.length; i++) {
buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
}
}

Expand Down
84 changes: 64 additions & 20 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ function lazyllhttp () {
let useWasmSIMD = process.arch !== 'ppc64'
// The Env Variable UNDICI_NO_WASM_SIMD allows explicitly overriding the default behavior
if (process.env.UNDICI_NO_WASM_SIMD === '1') {
useWasmSIMD = true
} else if (process.env.UNDICI_NO_WASM_SIMD === '0') {
useWasmSIMD = false
} else if (process.env.UNDICI_NO_WASM_SIMD === '0') {
useWasmSIMD = true
}

if (useWasmSIMD) {
Expand Down Expand Up @@ -216,6 +216,7 @@ class Parser {
*/
this.socket = socket
this.timeout = null
this.timeoutWeakRef = new WeakRef(this)
this.timeoutValue = null
this.timeoutType = null
this.statusCode = 0
Expand Down Expand Up @@ -253,9 +254,9 @@ class Parser {

if (delay) {
if (type & USE_FAST_TIMER) {
this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this))
this.timeout = timers.setFastTimeout(onParserTimeout, delay, this.timeoutWeakRef)
} else {
this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this))
this.timeout = setTimeout(onParserTimeout, delay, this.timeoutWeakRef)
this.timeout?.unref()
}
}
Expand Down Expand Up @@ -349,23 +350,62 @@ class Parser {
this.paused = true
socket.unshift(data)
} else {
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
let message = ''
if (ptr) {
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
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)
throw this.createError(ret, data)
}
}
} catch (err) {
util.destroy(socket, err)
}
}

finish () {
assert(currentParser === null)
assert(this.ptr != null)
assert(!this.paused)

const { llhttp } = this

let ret

try {
currentParser = this
ret = llhttp.llhttp_finish(this.ptr)
} finally {
currentParser = null
}

if (ret === constants.ERROR.OK) {
return null
}

if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
this.paused = true
return null
}

return this.createError(ret, EMPTY_BUF)
}

createError (ret, data) {
const { llhttp, contentLength, bytesRead } = this

if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
return new ResponseContentLengthMismatchError()
}

const ptr = llhttp.llhttp_get_error_reason(this.ptr)
let message = ''
if (ptr) {
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
message =
'Response does not match the HTTP/1.1 protocol (' +
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
')'
}

return new HTTPParserError(message, constants.ERROR[ret], data)
}

destroy () {
assert(currentParser === null)
assert(this.ptr != null)
Expand Down Expand Up @@ -870,8 +910,11 @@ function onHttpSocketError (err) {
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
// to the user.
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
// We treat all incoming data so for as a valid response.
parser.onMessageComplete()
const parserErr = parser.finish()
if (parserErr) {
this[kError] = parserErr
this[kClient][kOnError](parserErr)
}
return
}

Expand All @@ -888,8 +931,10 @@ function onHttpSocketEnd () {
const parser = this[kParser]

if (parser.statusCode && !parser.shouldKeepAlive) {
// We treat all incoming data so far as a valid response.
parser.onMessageComplete()
const parserErr = parser.finish()
if (parserErr) {
util.destroy(this, parserErr)
}
return
}

Expand All @@ -901,8 +946,7 @@ function onHttpSocketClose () {

if (parser) {
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
// We treat all incoming data so far as a valid response.
parser.onMessageComplete()
this[kError] = parser.finish() || this[kError]
}

this[kParser].destroy()
Expand Down
8 changes: 6 additions & 2 deletions deps/undici/src/lib/dispatcher/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,13 @@ class Client extends DispatcherBase {
...(typeof autoSelectFamily === 'boolean' ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
...connect
})
} else if (socketPath != null) {
} else {
const customConnect = connect
connect = (opts, callback) => customConnect({ ...opts, socketPath }, callback)
connect = (opts, callback) => customConnect({
...opts,
...(socketPath != null ? { socketPath } : null),
...(allowH2 != null ? { allowH2 } : null)
}, callback)
}

this[kUrl] = util.parseOrigin(url)
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/lib/dispatcher/h2c-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class H2CClient extends Client {
)
}

const { connect, maxConcurrentStreams, pipelining, ...opts } =
const { maxConcurrentStreams, pipelining, ...opts } =
clientOpts ?? {}
let defaultMaxConcurrentStreams = 100
let defaultPipelining = 100
Expand Down
11 changes: 10 additions & 1 deletion deps/undici/src/lib/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

// We include a version number for the Dispatcher API. In case of breaking changes,
// this version number must be increased to avoid conflicts.
const globalDispatcher = Symbol.for('undici.globalDispatcher.1')
const globalDispatcher = Symbol.for('undici.globalDispatcher.2')
const legacyGlobalDispatcher = Symbol.for('undici.globalDispatcher.1')
const { InvalidArgumentError } = require('./core/errors')
const Agent = require('./dispatcher/agent')

Expand All @@ -14,12 +15,20 @@ function setGlobalDispatcher (agent) {
if (!agent || typeof agent.dispatch !== 'function') {
throw new InvalidArgumentError('Argument agent must implement Agent')
}

Object.defineProperty(globalThis, globalDispatcher, {
value: agent,
writable: true,
enumerable: false,
configurable: false
})

Object.defineProperty(globalThis, legacyGlobalDispatcher, {
value: agent,
writable: true,
enumerable: false,
configurable: false
})
}

function getGlobalDispatcher () {
Expand Down
Loading
Loading