mirror of
https://gitea.com/actions/checkout.git
synced 2026-10-15 04:51:42 +08:00
Regenerate dist and license cache for undici 6.29.0
This commit is contained in:
Generated
BIN
Binary file not shown.
Vendored
+589
-199
@@ -6846,11 +6846,77 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
onUpgrade (statusCode, headers, socket) {
|
||||
/**
|
||||
* @param {number|null} statusCode
|
||||
* @param {Buffer[]|null} headers
|
||||
* @param {import('node:stream').Duplex} socket
|
||||
* @param {string} [statusText]
|
||||
*/
|
||||
onUpgrade (statusCode, headers, socket, statusText = '') {
|
||||
this.onFinally()
|
||||
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
return this[kHandler].onUpgrade(statusCode, headers, socket)
|
||||
if (statusCode !== null) {
|
||||
this.#publishUpgradeHeaders(statusCode, headers, statusText)
|
||||
}
|
||||
|
||||
const result = this[kHandler].onUpgrade(statusCode, headers, socket)
|
||||
|
||||
if (!this.aborted) {
|
||||
this.completed = true
|
||||
if (statusCode !== null) {
|
||||
this.#publishUpgradeTrailers()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} statusCode
|
||||
* @param {import('node:http2').IncomingHttpHeaders} headers
|
||||
* @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders
|
||||
* @param {string} [statusText]
|
||||
*/
|
||||
onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') {
|
||||
assert(!this.aborted)
|
||||
assert(this.completed)
|
||||
|
||||
if (channels.headers.hasSubscribers) {
|
||||
this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText)
|
||||
}
|
||||
this.#publishUpgradeTrailers()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Error} error
|
||||
*/
|
||||
onUpgradeError (error) {
|
||||
assert(!this.aborted)
|
||||
assert(this.completed)
|
||||
|
||||
if (channels.error.hasSubscribers) {
|
||||
channels.error.publish({ request: this, error })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} statusCode
|
||||
* @param {Buffer[]} headers
|
||||
* @param {string} statusText
|
||||
*/
|
||||
#publishUpgradeHeaders (statusCode, headers, statusText) {
|
||||
if (channels.headers.hasSubscribers) {
|
||||
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
|
||||
}
|
||||
}
|
||||
|
||||
#publishUpgradeTrailers () {
|
||||
if (channels.trailers.hasSubscribers) {
|
||||
channels.trailers.publish({ request: this, trailers: [] })
|
||||
}
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
@@ -6933,7 +6999,13 @@ function processHeader (request, key, val) {
|
||||
} else if (typeof val[i] === 'object') {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
} else {
|
||||
arr.push(`${val[i]}`)
|
||||
// Coerce primitives (and reject unsafe coercions such as functions
|
||||
// with a crafted toString/Symbol.toPrimitive).
|
||||
const str = `${val[i]}`
|
||||
if (!isValidHeaderValue(str)) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
arr.push(str)
|
||||
}
|
||||
}
|
||||
val = arr
|
||||
@@ -6944,7 +7016,12 @@ function processHeader (request, key, val) {
|
||||
} else if (val === null) {
|
||||
val = ''
|
||||
} else {
|
||||
// Coerce primitives (and reject unsafe coercions such as functions
|
||||
// with a crafted toString/Symbol.toPrimitive).
|
||||
val = `${val}`
|
||||
if (!isValidHeaderValue(val)) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
}
|
||||
|
||||
if (headerName === 'host') {
|
||||
@@ -8316,6 +8393,7 @@ const {
|
||||
RequestContentLengthMismatchError,
|
||||
ResponseContentLengthMismatchError,
|
||||
RequestAbortedError,
|
||||
InvalidArgumentError,
|
||||
HeadersTimeoutError,
|
||||
HeadersOverflowError,
|
||||
SocketError,
|
||||
@@ -8737,7 +8815,7 @@ class Parser {
|
||||
}
|
||||
|
||||
onUpgrade (head) {
|
||||
const { upgrade, client, socket, headers, statusCode } = this
|
||||
const { upgrade, client, socket, headers, statusCode, statusText } = this
|
||||
|
||||
assert(upgrade)
|
||||
assert(client[kSocket] === socket)
|
||||
@@ -8772,9 +8850,10 @@ class Parser {
|
||||
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
||||
|
||||
try {
|
||||
request.onUpgrade(statusCode, headers, socket)
|
||||
} catch (err) {
|
||||
util.destroy(socket, err)
|
||||
request.onUpgrade(statusCode, headers, socket, statusText)
|
||||
} catch (error) {
|
||||
util.errorRequest(client, request, error)
|
||||
util.destroy(socket, error)
|
||||
}
|
||||
|
||||
client[kResume]()
|
||||
@@ -9181,7 +9260,7 @@ async function connectH1 (client, socket) {
|
||||
|
||||
function clearIdleSocketValidation (socket) {
|
||||
if (socket[kIdleSocketValidationTimeout]) {
|
||||
clearTimeout(socket[kIdleSocketValidationTimeout])
|
||||
clearImmediate(socket[kIdleSocketValidationTimeout])
|
||||
socket[kIdleSocketValidationTimeout] = null
|
||||
}
|
||||
|
||||
@@ -9190,15 +9269,23 @@ function clearIdleSocketValidation (socket) {
|
||||
|
||||
function scheduleIdleSocketValidation (client, socket) {
|
||||
socket[kIdleSocketValidation] = 1
|
||||
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
|
||||
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
|
||||
// already pending on this idle keep-alive socket are processed before the
|
||||
// next request is written (GHSA-35p6-xmwp-9g52).
|
||||
//
|
||||
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
|
||||
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
|
||||
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
|
||||
// A ref'd Immediate both keeps the pending request alive and makes poll
|
||||
// return immediately — the hybrid those issues asked for.
|
||||
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
|
||||
socket[kIdleSocketValidationTimeout] = null
|
||||
socket[kIdleSocketValidation] = 2
|
||||
|
||||
if (client[kSocket] === socket && !socket.destroyed) {
|
||||
client[kResume]()
|
||||
}
|
||||
}, 0)
|
||||
socket[kIdleSocketValidationTimeout].unref?.()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9299,8 +9386,16 @@ function writeH1 (client, request) {
|
||||
}
|
||||
body = bodyStream.stream
|
||||
contentLength = bodyStream.length
|
||||
} else if (util.isBlobLike(body) && request.contentType == null && body.type) {
|
||||
headers.push('content-type', body.type)
|
||||
} else if (util.isBlobLike(body) && request.contentType == null) {
|
||||
const contentType = body.type
|
||||
if (contentType) {
|
||||
const contentTypeValue = `${contentType}`
|
||||
if (!util.isValidHeaderValue(contentTypeValue)) {
|
||||
util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'))
|
||||
return false
|
||||
}
|
||||
headers.push('content-type', contentTypeValue)
|
||||
}
|
||||
}
|
||||
|
||||
if (body && typeof body.read === 'function') {
|
||||
@@ -9339,12 +9434,22 @@ function writeH1 (client, request) {
|
||||
const socket = client[kSocket]
|
||||
clearIdleSocketValidation(socket)
|
||||
|
||||
const abort = (err) => {
|
||||
if (request.aborted || request.completed) {
|
||||
/**
|
||||
* @param {Error} [error]
|
||||
*/
|
||||
const abort = (error) => {
|
||||
if (request.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
util.errorRequest(client, request, err || new RequestAbortedError())
|
||||
if (request.completed) {
|
||||
if (request.upgrade || request.method === 'CONNECT') {
|
||||
util.destroy(socket, new InformationalError('aborted'))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
util.errorRequest(client, request, error || new RequestAbortedError())
|
||||
|
||||
util.destroy(body)
|
||||
util.destroy(socket, new InformationalError('aborted'))
|
||||
@@ -9801,6 +9906,7 @@ module.exports = connectH1
|
||||
|
||||
|
||||
const assert = __nccwpck_require__(4589)
|
||||
const { errorMonitor } = __nccwpck_require__(8474)
|
||||
const { pipeline } = __nccwpck_require__(7075)
|
||||
const util = __nccwpck_require__(3440)
|
||||
const {
|
||||
@@ -9877,6 +9983,15 @@ function parseH2Headers (headers) {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('node:http2').IncomingHttpHeaders} headers
|
||||
* @returns {Buffer[]}
|
||||
*/
|
||||
function parseH2ResponseHeaders (headers) {
|
||||
const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers
|
||||
return parseH2Headers(realHeaders)
|
||||
}
|
||||
|
||||
async function connectH2 (client, socket) {
|
||||
client[kSocket] = socket
|
||||
|
||||
@@ -10097,22 +10212,32 @@ function writeH2 (client, request) {
|
||||
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
||||
headers[HTTP2_HEADER_METHOD] = method
|
||||
|
||||
const abort = (err) => {
|
||||
if (request.aborted || request.completed) {
|
||||
/**
|
||||
* @param {Error} [error]
|
||||
*/
|
||||
const abort = (error) => {
|
||||
if (request.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
err = err || new RequestAbortedError()
|
||||
if (request.completed) {
|
||||
if (method === 'CONNECT' && stream != null) {
|
||||
util.destroy(stream, error || new RequestAbortedError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
util.errorRequest(client, request, err)
|
||||
error = error || new RequestAbortedError()
|
||||
|
||||
util.errorRequest(client, request, error)
|
||||
|
||||
if (stream != null) {
|
||||
util.destroy(stream, err)
|
||||
util.destroy(stream, error)
|
||||
}
|
||||
|
||||
// We do not destroy the socket as we can continue using the session
|
||||
// the stream get's destroyed and the session remains to create new streams
|
||||
util.destroy(body, err)
|
||||
util.destroy(body, error)
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
client[kResume]()
|
||||
}
|
||||
@@ -10131,25 +10256,57 @@ function writeH2 (client, request) {
|
||||
|
||||
if (method === 'CONNECT') {
|
||||
session.ref()
|
||||
// We are already connected, streams are pending, first request
|
||||
// will create a new stream. We trigger a request to create the stream and wait until
|
||||
// `ready` event is triggered
|
||||
// We disabled endStream to allow the user to write to the stream
|
||||
stream = session.request(headers, { endStream: false, signal })
|
||||
let upgradeResponseFinished = false
|
||||
|
||||
if (stream.id && !stream.pending) {
|
||||
request.onUpgrade(null, null, stream)
|
||||
++session[kOpenStreams]
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
} else {
|
||||
stream.once('ready', () => {
|
||||
request.onUpgrade(null, null, stream)
|
||||
++session[kOpenStreams]
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
})
|
||||
/**
|
||||
* @param {import('node:http2').IncomingHttpHeaders} headers
|
||||
*/
|
||||
const onResponse = (headers) => {
|
||||
upgradeResponseFinished = true
|
||||
stream.off(errorMonitor, onUpgradeError)
|
||||
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Error} error
|
||||
*/
|
||||
const onUpgradeError = (error) => {
|
||||
upgradeResponseFinished = true
|
||||
stream.off('response', onResponse)
|
||||
request.onUpgradeError(error)
|
||||
}
|
||||
|
||||
const onReady = () => {
|
||||
try {
|
||||
request.onUpgrade(null, null, stream)
|
||||
} catch (error) {
|
||||
stream.off('response', onResponse)
|
||||
abort(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (request.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
stream.off('error', abort)
|
||||
stream.once(errorMonitor, onUpgradeError)
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
}
|
||||
|
||||
stream.once('response', onResponse)
|
||||
stream.once('error', abort)
|
||||
++session[kOpenStreams]
|
||||
onReady()
|
||||
|
||||
stream.once('close', () => {
|
||||
if (!upgradeResponseFinished && request.completed) {
|
||||
stream.off('response', onResponse)
|
||||
stream.off(errorMonitor, onUpgradeError)
|
||||
request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
|
||||
}
|
||||
session[kOpenStreams] -= 1
|
||||
if (session[kOpenStreams] === 0) session.unref()
|
||||
})
|
||||
@@ -12773,6 +12930,28 @@ function calculateRetryAfterHeader (retryAfter) {
|
||||
return new Date(retryAfter).getTime() - current
|
||||
}
|
||||
|
||||
function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
|
||||
const contentLength = headers['content-length']
|
||||
if (contentLength == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const length = Number(contentLength)
|
||||
const expectedLength = range.end - range.start + 1
|
||||
if (!Number.isFinite(length) || length !== expectedLength) {
|
||||
return new RequestRetryError('Content-Length mismatch', statusCode, {
|
||||
headers,
|
||||
data: { count: retryCount }
|
||||
})
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
class RetryHandler {
|
||||
constructor (opts, handlers) {
|
||||
const { retryOptions, ...dispatchOpts } = opts
|
||||
@@ -12826,6 +13005,7 @@ class RetryHandler {
|
||||
this.end = null
|
||||
this.etag = null
|
||||
this.resume = null
|
||||
this.headersSent = false
|
||||
|
||||
// Handle possible onConnect duplication
|
||||
this.handler.onConnect(reason => {
|
||||
@@ -12838,6 +13018,20 @@ class RetryHandler {
|
||||
})
|
||||
}
|
||||
|
||||
checkpointResponseEnd (headers, resume) {
|
||||
if (this.end == null && this.opts.method !== 'HEAD') {
|
||||
const contentLength = headers['content-length']
|
||||
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
||||
|
||||
assert(
|
||||
this.end == null || Number.isFinite(this.end),
|
||||
'invalid content-length'
|
||||
)
|
||||
}
|
||||
|
||||
this.resume = this.end != null ? resume : null
|
||||
}
|
||||
|
||||
onRequestSent () {
|
||||
if (this.handler.onRequestSent) {
|
||||
this.handler.onRequestSent()
|
||||
@@ -12926,7 +13120,12 @@ class RetryHandler {
|
||||
this.retryCount += 1
|
||||
|
||||
if (statusCode >= 300) {
|
||||
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
||||
// Only expose a response if no earlier attempt has reached the caller.
|
||||
// Otherwise abort this attempt so the error settles the existing body
|
||||
// instead of replacing it with a new response.
|
||||
if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) {
|
||||
this.headersSent = true
|
||||
this.checkpointResponseEnd(headers, resume)
|
||||
return this.handler.onHeaders(
|
||||
statusCode,
|
||||
rawHeaders,
|
||||
@@ -12987,10 +13186,23 @@ class RetryHandler {
|
||||
return false
|
||||
}
|
||||
|
||||
const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
|
||||
if (contentLengthError != null) {
|
||||
this.abort(contentLengthError)
|
||||
return false
|
||||
}
|
||||
|
||||
const { start, size, end = size - 1 } = contentRange
|
||||
|
||||
assert(this.start === start, 'content-range mismatch')
|
||||
assert(this.end == null || this.end === end, 'content-range mismatch')
|
||||
if (this.start !== start || (this.end != null && this.end !== end)) {
|
||||
this.abort(
|
||||
new RequestRetryError('Content-Range mismatch', statusCode, {
|
||||
headers,
|
||||
data: { count: this.retryCount }
|
||||
})
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
this.resume = resume
|
||||
return true
|
||||
@@ -13002,6 +13214,7 @@ class RetryHandler {
|
||||
const range = parseRangeHeader(headers['content-range'])
|
||||
|
||||
if (range == null) {
|
||||
this.headersSent = true
|
||||
return this.handler.onHeaders(
|
||||
statusCode,
|
||||
rawHeaders,
|
||||
@@ -13010,6 +13223,12 @@ class RetryHandler {
|
||||
)
|
||||
}
|
||||
|
||||
const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
|
||||
if (contentLengthError != null) {
|
||||
this.abort(contentLengthError)
|
||||
return false
|
||||
}
|
||||
|
||||
const { start, size, end = size - 1 } = range
|
||||
assert(
|
||||
start != null && Number.isFinite(start),
|
||||
@@ -13034,6 +13253,7 @@ class RetryHandler {
|
||||
)
|
||||
|
||||
this.resume = resume
|
||||
this.headersSent = true
|
||||
this.etag = headers.etag != null ? headers.etag : null
|
||||
|
||||
// Weak etags are not useful for comparison nor cache
|
||||
@@ -13073,7 +13293,7 @@ class RetryHandler {
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
if (this.aborted || isDisturbed(this.opts.body)) {
|
||||
if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) {
|
||||
return this.handler.onError(err)
|
||||
}
|
||||
|
||||
@@ -17254,7 +17474,7 @@ function validateCookiePath (path) {
|
||||
|
||||
if (
|
||||
code < 0x20 || // exclude CTLs (0-31)
|
||||
code === 0x7F || // DEL
|
||||
code > 0x7E || // exclude DEL and non-ascii
|
||||
code === 0x3B // ;
|
||||
) {
|
||||
throw new Error('Invalid cookie path')
|
||||
@@ -17263,16 +17483,80 @@ function validateCookiePath (path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* I have no idea why these values aren't allowed to be honest,
|
||||
* but Deno tests these. - Khafra
|
||||
* <let-dig> ::= <letter> | <digit>
|
||||
*
|
||||
* <letter> ::= any one of the 52 alphabetic characters A through Z in
|
||||
* upper case and a through z in lower case
|
||||
*
|
||||
* <digit> ::= any one of the ten digits 0 through 9r
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
|
||||
* @param {number} code
|
||||
*/
|
||||
function isLetterOrDigit (code) {
|
||||
return (
|
||||
(code >= 0x30 && code <= 0x39) || // 0-9
|
||||
(code >= 0x41 && code <= 0x5A) || // A-Z
|
||||
(code >= 0x61 && code <= 0x7A) // a-z
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a cookie domain against the "preferred name syntax".
|
||||
*
|
||||
* <domain> ::= <subdomain> | " "
|
||||
* <subdomain> ::= <label> | <subdomain> "." <label>
|
||||
* <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
||||
* <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
||||
* <let-dig-hyp> ::= <let-dig> | "-"
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
|
||||
* @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
|
||||
* @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
|
||||
* @param {string} domain
|
||||
*/
|
||||
function validateCookieDomain (domain) {
|
||||
if (
|
||||
domain.startsWith('-') ||
|
||||
domain.endsWith('.') ||
|
||||
domain.endsWith('-')
|
||||
) {
|
||||
// <domain> ::= <subdomain> | " "
|
||||
if (domain === ' ') {
|
||||
return
|
||||
}
|
||||
|
||||
if (domain.length > 255) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
|
||||
let labelLength = 0
|
||||
|
||||
for (let i = 0; i < domain.length; ++i) {
|
||||
const code = domain.charCodeAt(i)
|
||||
|
||||
if (code === 0x2E) {
|
||||
if (labelLength === 0) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
|
||||
if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
|
||||
labelLength = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if (labelLength === 0 && !isLetterOrDigit(code)) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
|
||||
if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
|
||||
if (++labelLength > 63) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
}
|
||||
|
||||
if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
}
|
||||
@@ -17415,7 +17699,13 @@ function stringify (cookie) {
|
||||
|
||||
const [key, ...value] = part.split('=')
|
||||
|
||||
out.push(`${key.trim()}=${value.join('=')}`)
|
||||
const trimmedKey = key.trim()
|
||||
const joinedValue = value.join('=')
|
||||
|
||||
validateCookieName(trimmedKey)
|
||||
validateCookieValue(joinedValue)
|
||||
|
||||
out.push(`${trimmedKey}=${joinedValue}`)
|
||||
}
|
||||
|
||||
return out.join('; ')
|
||||
@@ -17461,6 +17751,49 @@ const COLON = 0x3A
|
||||
*/
|
||||
const SPACE = 0x20
|
||||
|
||||
const DATA = Buffer.from('data')
|
||||
const EVENT = Buffer.from('event')
|
||||
const ID = Buffer.from('id')
|
||||
const RETRY = Buffer.from('retry')
|
||||
|
||||
function isASCIINumberBytes (buffer, start) {
|
||||
if (start >= buffer.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = start; i < buffer.length; i++) {
|
||||
if (buffer[i] < 0x30 || buffer[i] > 0x39) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isValidLastEventIdBytes (buffer, start) {
|
||||
for (let i = start; i < buffer.length; i++) {
|
||||
if (buffer[i] === 0x00) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isFieldName (line, length, field) {
|
||||
if (length !== field.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (line[i] !== field[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} EventSourceStreamEvent
|
||||
* @type {object}
|
||||
@@ -17501,11 +17834,14 @@ class EventSourceStream extends Transform {
|
||||
eventEndCheck = false
|
||||
|
||||
/**
|
||||
* @type {Buffer}
|
||||
* @type {Buffer[]}
|
||||
*/
|
||||
buffer = null
|
||||
chunks = []
|
||||
|
||||
chunkIndex = 0
|
||||
pos = 0
|
||||
lineChunkIndex = 0
|
||||
linePos = 0
|
||||
|
||||
event = {
|
||||
data: undefined,
|
||||
@@ -17544,92 +17880,20 @@ class EventSourceStream extends Transform {
|
||||
return
|
||||
}
|
||||
|
||||
// Cache the chunk in the buffer, as the data might not be complete while
|
||||
// processing it
|
||||
// TODO: Investigate if there is a more performant way to handle
|
||||
// incoming chunks
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
if (this.buffer) {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk])
|
||||
} else {
|
||||
this.buffer = chunk
|
||||
}
|
||||
this.chunks.push(chunk)
|
||||
|
||||
// Strip leading byte-order-mark if we opened the stream and started
|
||||
// the processing of the incoming data
|
||||
if (this.checkBOM) {
|
||||
switch (this.buffer.length) {
|
||||
case 1:
|
||||
// Check if the first byte is the same as the first byte of the BOM
|
||||
if (this.buffer[0] === BOM[0]) {
|
||||
// If it is, we need to wait for more data
|
||||
callback()
|
||||
return
|
||||
}
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
|
||||
// The buffer only contains one byte so we need to wait for more data
|
||||
callback()
|
||||
return
|
||||
case 2:
|
||||
// Check if the first two bytes are the same as the first two bytes
|
||||
// of the BOM
|
||||
if (
|
||||
this.buffer[0] === BOM[0] &&
|
||||
this.buffer[1] === BOM[1]
|
||||
) {
|
||||
// If it is, we need to wait for more data, because the third byte
|
||||
// is needed to determine if it is the BOM or not
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
break
|
||||
case 3:
|
||||
// Check if the first three bytes are the same as the first three
|
||||
// bytes of the BOM
|
||||
if (
|
||||
this.buffer[0] === BOM[0] &&
|
||||
this.buffer[1] === BOM[1] &&
|
||||
this.buffer[2] === BOM[2]
|
||||
) {
|
||||
// If it is, we can drop the buffered data, as it is only the BOM
|
||||
this.buffer = Buffer.alloc(0)
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
// BOM anymore
|
||||
this.checkBOM = false
|
||||
|
||||
// Await more data
|
||||
callback()
|
||||
return
|
||||
}
|
||||
// If it is not the BOM, we can start processing the data
|
||||
this.checkBOM = false
|
||||
break
|
||||
default:
|
||||
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
|
||||
// present
|
||||
if (
|
||||
this.buffer[0] === BOM[0] &&
|
||||
this.buffer[1] === BOM[1] &&
|
||||
this.buffer[2] === BOM[2]
|
||||
) {
|
||||
// Remove the BOM from the buffer
|
||||
this.buffer = this.buffer.subarray(3)
|
||||
}
|
||||
|
||||
// Set the checkBOM flag to false as we don't need to check for the
|
||||
this.checkBOM = false
|
||||
break
|
||||
if (this.handleBOM()) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
while (this.pos < this.buffer.length) {
|
||||
while (this.hasCurrentByte()) {
|
||||
const byte = this.currentByte()
|
||||
|
||||
// If the previous line ended with an end-of-line, we need to check
|
||||
// if the next character is also an end-of-line.
|
||||
if (this.eventEndCheck) {
|
||||
@@ -17642,10 +17906,9 @@ class EventSourceStream extends Transform {
|
||||
if (this.crlfCheck) {
|
||||
// If the current character is a line feed, we can remove it
|
||||
// from the buffer and reset the crlfCheck flag
|
||||
if (this.buffer[this.pos] === LF) {
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
this.pos = 0
|
||||
if (byte === LF) {
|
||||
this.crlfCheck = false
|
||||
this.consumeCurrentByte()
|
||||
|
||||
// It is possible that the line feed is not the end of the
|
||||
// event. We need to check if the next character is an
|
||||
@@ -17661,19 +17924,17 @@ class EventSourceStream extends Transform {
|
||||
this.crlfCheck = false
|
||||
}
|
||||
|
||||
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
|
||||
if (byte === LF || byte === CR) {
|
||||
// If the current character is a carriage return, we need to
|
||||
// set the crlfCheck flag to true, as we need to check if the
|
||||
// next character is a line feed so we can remove it from the
|
||||
// buffer
|
||||
if (this.buffer[this.pos] === CR) {
|
||||
if (byte === CR) {
|
||||
this.crlfCheck = true
|
||||
}
|
||||
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
this.pos = 0
|
||||
if (
|
||||
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
||||
this.consumeCurrentByte()
|
||||
if (this.hasPendingEvent()) {
|
||||
this.processEvent(this.event)
|
||||
}
|
||||
this.clearEvent()
|
||||
@@ -17687,22 +17948,18 @@ class EventSourceStream extends Transform {
|
||||
|
||||
// If the current character is an end-of-line, we can process the
|
||||
// line
|
||||
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
|
||||
if (byte === LF || byte === CR) {
|
||||
// If the current character is a carriage return, we need to
|
||||
// set the crlfCheck flag to true, as we need to check if the
|
||||
// next character is a line feed
|
||||
if (this.buffer[this.pos] === CR) {
|
||||
if (byte === CR) {
|
||||
this.crlfCheck = true
|
||||
}
|
||||
|
||||
// In any case, we can process the line as we reached an
|
||||
// end-of-line character
|
||||
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
|
||||
|
||||
// Remove the processed line from the buffer
|
||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
||||
// Reset the position as we removed the processed line from the buffer
|
||||
this.pos = 0
|
||||
this.parseLine(this.readLine(), this.event)
|
||||
this.consumeCurrentByte()
|
||||
// A line was processed and this could be the end of the event. We need
|
||||
// to check if the next line is empty to determine if the event is
|
||||
// finished.
|
||||
@@ -17710,7 +17967,7 @@ class EventSourceStream extends Transform {
|
||||
continue
|
||||
}
|
||||
|
||||
this.pos++
|
||||
this.advanceCursor()
|
||||
}
|
||||
|
||||
callback()
|
||||
@@ -17735,64 +17992,53 @@ class EventSourceStream extends Transform {
|
||||
return
|
||||
}
|
||||
|
||||
let field = ''
|
||||
let value = ''
|
||||
let fieldLength = line.length
|
||||
let valueStart = line.length
|
||||
|
||||
// If the line contains a U+003A COLON character (:)
|
||||
if (colonPosition !== -1) {
|
||||
// Collect the characters on the line before the first U+003A COLON
|
||||
// character (:), and let field be that string.
|
||||
// TODO: Investigate if there is a more performant way to extract the
|
||||
// field
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
field = line.subarray(0, colonPosition).toString('utf8')
|
||||
fieldLength = colonPosition
|
||||
|
||||
// Collect the characters on the line after the first U+003A COLON
|
||||
// character (:), and let value be that string.
|
||||
// If value starts with a U+0020 SPACE character, remove it from value.
|
||||
let valueStart = colonPosition + 1
|
||||
valueStart = colonPosition + 1
|
||||
if (line[valueStart] === SPACE) {
|
||||
++valueStart
|
||||
}
|
||||
// TODO: Investigate if there is a more performant way to extract the
|
||||
// value
|
||||
// see: https://github.com/nodejs/undici/issues/2630
|
||||
value = line.subarray(valueStart).toString('utf8')
|
||||
|
||||
// Otherwise, the string is not empty but does not contain a U+003A COLON
|
||||
// character (:)
|
||||
} else {
|
||||
// Process the field using the steps described below, using the whole
|
||||
// line as the field name, and the empty string as the field value.
|
||||
field = line.toString('utf8')
|
||||
value = ''
|
||||
}
|
||||
|
||||
// Modify the event with the field name and value. The value is also
|
||||
// decoded as UTF-8
|
||||
switch (field) {
|
||||
case 'data':
|
||||
if (event[field] === undefined) {
|
||||
event[field] = value
|
||||
} else {
|
||||
event[field] += `\n${value}`
|
||||
}
|
||||
break
|
||||
case 'retry':
|
||||
if (isASCIINumber(value)) {
|
||||
event[field] = value
|
||||
}
|
||||
break
|
||||
case 'id':
|
||||
if (isValidLastEventId(value)) {
|
||||
event[field] = value
|
||||
}
|
||||
break
|
||||
case 'event':
|
||||
if (value.length > 0) {
|
||||
event[field] = value
|
||||
}
|
||||
break
|
||||
if (isFieldName(line, fieldLength, DATA)) {
|
||||
const value = line.toString('utf8', valueStart)
|
||||
|
||||
if (event.data === undefined) {
|
||||
event.data = value
|
||||
} else {
|
||||
event.data += `\n${value}`
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (isFieldName(line, fieldLength, RETRY)) {
|
||||
if (isASCIINumberBytes(line, valueStart)) {
|
||||
event.retry = line.toString('utf8', valueStart)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (isFieldName(line, fieldLength, ID)) {
|
||||
if (isValidLastEventIdBytes(line, valueStart)) {
|
||||
event.id = line.toString('utf8', valueStart)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (isFieldName(line, fieldLength, EVENT)) {
|
||||
const value = line.toString('utf8', valueStart)
|
||||
|
||||
if (value.length > 0) {
|
||||
event.event = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17822,13 +18068,152 @@ class EventSourceStream extends Transform {
|
||||
}
|
||||
|
||||
clearEvent () {
|
||||
this.event = {
|
||||
data: undefined,
|
||||
event: undefined,
|
||||
id: undefined,
|
||||
retry: undefined
|
||||
this.event.data = undefined
|
||||
this.event.event = undefined
|
||||
this.event.id = undefined
|
||||
this.event.retry = undefined
|
||||
}
|
||||
|
||||
hasPendingEvent () {
|
||||
return this.event.data !== undefined ||
|
||||
this.event.event !== undefined ||
|
||||
this.event.id !== undefined ||
|
||||
this.event.retry !== undefined
|
||||
}
|
||||
|
||||
hasCurrentByte () {
|
||||
return this.chunkIndex < this.chunks.length &&
|
||||
this.pos < this.chunks[this.chunkIndex].length
|
||||
}
|
||||
|
||||
currentByte () {
|
||||
return this.chunks[this.chunkIndex][this.pos]
|
||||
}
|
||||
|
||||
consumeCurrentByte () {
|
||||
this.advanceCursor()
|
||||
this.syncLineStartToCursor()
|
||||
}
|
||||
|
||||
advanceCursor () {
|
||||
this.pos++
|
||||
|
||||
while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
|
||||
this.chunkIndex++
|
||||
this.pos = 0
|
||||
}
|
||||
}
|
||||
|
||||
syncLineStartToCursor () {
|
||||
this.lineChunkIndex = this.chunkIndex
|
||||
this.linePos = this.pos
|
||||
this.dropConsumedChunks()
|
||||
}
|
||||
|
||||
dropConsumedChunks () {
|
||||
while (this.lineChunkIndex > 0) {
|
||||
this.chunks.shift()
|
||||
this.lineChunkIndex--
|
||||
this.chunkIndex--
|
||||
}
|
||||
|
||||
if (this.chunkIndex === this.chunks.length) {
|
||||
this.chunks.length = 0
|
||||
this.chunkIndex = 0
|
||||
this.pos = 0
|
||||
this.lineChunkIndex = 0
|
||||
this.linePos = 0
|
||||
}
|
||||
}
|
||||
|
||||
readLine () {
|
||||
if (this.lineChunkIndex === this.chunkIndex) {
|
||||
return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
|
||||
}
|
||||
|
||||
const chunks = []
|
||||
let length = 0
|
||||
|
||||
for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
|
||||
const chunk = this.chunks[i]
|
||||
const start = i === this.lineChunkIndex ? this.linePos : 0
|
||||
const end = i === this.chunkIndex ? this.pos : chunk.length
|
||||
const slice = chunk.subarray(start, end)
|
||||
length += slice.length
|
||||
chunks.push(slice)
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks, length)
|
||||
}
|
||||
|
||||
peekBufferedByte (offset) {
|
||||
let chunkIndex = this.lineChunkIndex
|
||||
let pos = this.linePos
|
||||
|
||||
while (chunkIndex < this.chunks.length) {
|
||||
const chunk = this.chunks[chunkIndex]
|
||||
const remaining = chunk.length - pos
|
||||
|
||||
if (offset < remaining) {
|
||||
return chunk[pos + offset]
|
||||
}
|
||||
|
||||
offset -= remaining
|
||||
chunkIndex++
|
||||
pos = 0
|
||||
}
|
||||
}
|
||||
|
||||
discardLeadingBytes (count) {
|
||||
while (count > 0 && this.lineChunkIndex < this.chunks.length) {
|
||||
const chunk = this.chunks[this.lineChunkIndex]
|
||||
const remaining = chunk.length - this.linePos
|
||||
|
||||
if (count < remaining) {
|
||||
this.linePos += count
|
||||
count = 0
|
||||
} else {
|
||||
count -= remaining
|
||||
this.lineChunkIndex++
|
||||
this.linePos = 0
|
||||
}
|
||||
}
|
||||
|
||||
this.chunkIndex = this.lineChunkIndex
|
||||
this.pos = this.linePos
|
||||
this.dropConsumedChunks()
|
||||
}
|
||||
|
||||
handleBOM () {
|
||||
const first = this.peekBufferedByte(0)
|
||||
const second = this.peekBufferedByte(1)
|
||||
const third = this.peekBufferedByte(2)
|
||||
|
||||
if (second === undefined) {
|
||||
if (first === BOM[0]) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.checkBOM = false
|
||||
return true
|
||||
}
|
||||
|
||||
if (third === undefined) {
|
||||
if (first === BOM[0] && second === BOM[1]) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.checkBOM = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
|
||||
this.discardLeadingBytes(3)
|
||||
}
|
||||
|
||||
this.checkBOM = false
|
||||
return !this.hasCurrentByte()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -29096,7 +29481,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
||||
// is specified, the server needs to include the same field and one of
|
||||
// the selected subprotocol values in its response for the connection to
|
||||
// be established.
|
||||
if (!requestProtocols.includes(secProtocol)) {
|
||||
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
|
||||
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
||||
return
|
||||
}
|
||||
@@ -29857,7 +30242,12 @@ class PerMessageDeflate {
|
||||
|
||||
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||
callback(new MessageSizeExceededError())
|
||||
// The inflater may still hold buffered input that can emit a late
|
||||
// zlib error. Remove the data listener, then deterministically stop
|
||||
// the stream so a subsequent 'error' cannot fire without a listener
|
||||
// (which would terminate the process as an unhandled error event).
|
||||
this.#inflate.removeAllListeners()
|
||||
this.#inflate.destroy()
|
||||
this.#inflate = null
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user