aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib/dispatcher
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/undici/lib/dispatcher')
-rw-r--r--node_modules/undici/lib/dispatcher/client-h1.js54
-rw-r--r--node_modules/undici/lib/dispatcher/client-h2.js88
2 files changed, 111 insertions, 31 deletions
diff --git a/node_modules/undici/lib/dispatcher/client-h1.js b/node_modules/undici/lib/dispatcher/client-h1.js
index 9455517..52fd8d0 100644
--- a/node_modules/undici/lib/dispatcher/client-h1.js
+++ b/node_modules/undici/lib/dispatcher/client-h1.js
@@ -10,6 +10,7 @@ const {
RequestContentLengthMismatchError,
ResponseContentLengthMismatchError,
RequestAbortedError,
+ InvalidArgumentError,
HeadersTimeoutError,
HeadersOverflowError,
SocketError,
@@ -431,7 +432,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)
@@ -466,9 +467,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]()
@@ -875,7 +877,7 @@ async function connectH1 (client, socket) {
function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
- clearTimeout(socket[kIdleSocketValidationTimeout])
+ clearImmediate(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}
@@ -884,15 +886,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?.()
+ })
}
/**
@@ -993,8 +1003,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') {
@@ -1033,12 +1051,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
+ }
+
+ if (request.completed) {
+ if (request.upgrade || request.method === 'CONNECT') {
+ util.destroy(socket, new InformationalError('aborted'))
+ }
return
}
- util.errorRequest(client, request, err || new RequestAbortedError())
+ util.errorRequest(client, request, error || new RequestAbortedError())
util.destroy(body)
util.destroy(socket, new InformationalError('aborted'))
diff --git a/node_modules/undici/lib/dispatcher/client-h2.js b/node_modules/undici/lib/dispatcher/client-h2.js
index 4a52eff..bb8eda8 100644
--- a/node_modules/undici/lib/dispatcher/client-h2.js
+++ b/node_modules/undici/lib/dispatcher/client-h2.js
@@ -1,6 +1,7 @@
'use strict'
const assert = require('node:assert')
+const { errorMonitor } = require('node:events')
const { pipeline } = require('node:stream')
const util = require('../core/util.js')
const {
@@ -77,6 +78,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
@@ -297,22 +307,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
+ }
+
+ if (request.completed) {
+ if (method === 'CONNECT' && stream != null) {
+ util.destroy(stream, error || new RequestAbortedError())
+ }
return
}
- err = err || new RequestAbortedError()
+ error = error || new RequestAbortedError()
- util.errorRequest(client, request, err)
+ 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]()
}
@@ -331,25 +351,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', () => {
+ /**
+ * @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)
- ++session[kOpenStreams]
- client[kQueue][client[kRunningIdx]++] = null
- })
+ } 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()
})