Remove the unused React Native Markdown renderer. Pin safe YAML, shell quoting, tar, and cache dependencies; upgrade npm within the 11 series and apply upstream fixes to its hidden bundled Undici and cache copies. Add actual-consumer security regressions and an opt-in real npm/ACP upgrade verifier. Typechecks, Web build, Android APK/signature checks, and 2,997 tests passed (23 skipped). Real old-to-new npm installs, ACP initialize, rollback, restart, and fallback retention passed under the desktop Electron runtime. Android USB-device validation and full desktop automatic-update release gates remain pending. Dependency audit is reduced from 85 to 74 unique advisories; this is not a complete security clearance.
779 lines
24 KiB
Diff
779 lines
24 KiB
Diff
diff --git a/node_modules/undici/lib/dispatcher/client-h1.js b/node_modules/undici/lib/dispatcher/client-h1.js
|
|
--- a/node_modules/undici/lib/dispatcher/client-h1.js
|
|
+++ b/node_modules/undici/lib/dispatcher/client-h1.js
|
|
@@ -876,7 +876,7 @@
|
|
|
|
function clearIdleSocketValidation (socket) {
|
|
if (socket[kIdleSocketValidationTimeout]) {
|
|
- clearTimeout(socket[kIdleSocketValidationTimeout])
|
|
+ clearImmediate(socket[kIdleSocketValidationTimeout])
|
|
socket[kIdleSocketValidationTimeout] = null
|
|
}
|
|
|
|
@@ -885,15 +885,23 @@
|
|
|
|
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?.()
|
|
+ })
|
|
}
|
|
|
|
/**
|
|
diff --git a/node_modules/undici/lib/web/websocket/connection.js b/node_modules/undici/lib/web/websocket/connection.js
|
|
--- a/node_modules/undici/lib/web/websocket/connection.js
|
|
+++ b/node_modules/undici/lib/web/websocket/connection.js
|
|
@@ -192,7 +192,7 @@
|
|
// 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
|
|
}
|
|
diff --git a/node_modules/undici/lib/web/eventsource/eventsource-stream.js b/node_modules/undici/lib/web/eventsource/eventsource-stream.js
|
|
--- a/node_modules/undici/lib/web/eventsource/eventsource-stream.js
|
|
+++ b/node_modules/undici/lib/web/eventsource/eventsource-stream.js
|
|
@@ -22,6 +22,49 @@
|
|
* @type {32} SPACE
|
|
*/
|
|
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
|
|
@@ -63,11 +106,14 @@
|
|
eventEndCheck = false
|
|
|
|
/**
|
|
- * @type {Buffer}
|
|
- */
|
|
- buffer = null
|
|
-
|
|
+ * @type {Buffer[]}
|
|
+ */
|
|
+ chunks = []
|
|
+
|
|
+ chunkIndex = 0
|
|
pos = 0
|
|
+ lineChunkIndex = 0
|
|
+ linePos = 0
|
|
|
|
event = {
|
|
data: undefined,
|
|
@@ -106,92 +152,20 @@
|
|
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
|
|
- }
|
|
- }
|
|
-
|
|
- while (this.pos < this.buffer.length) {
|
|
+ if (this.handleBOM()) {
|
|
+ callback()
|
|
+ return
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
@@ -204,10 +178,9 @@
|
|
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
|
|
@@ -223,19 +196,17 @@
|
|
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()
|
|
@@ -249,22 +220,18 @@
|
|
|
|
// 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.
|
|
@@ -272,7 +239,7 @@
|
|
continue
|
|
}
|
|
|
|
- this.pos++
|
|
+ this.advanceCursor()
|
|
}
|
|
|
|
callback()
|
|
@@ -297,64 +264,53 @@
|
|
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
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -384,12 +340,151 @@
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
diff --git a/node_modules/undici/lib/web/websocket/permessage-deflate.js b/node_modules/undici/lib/web/websocket/permessage-deflate.js
|
|
--- a/node_modules/undici/lib/web/websocket/permessage-deflate.js
|
|
+++ b/node_modules/undici/lib/web/websocket/permessage-deflate.js
|
|
@@ -63,7 +63,12 @@
|
|
|
|
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
|
|
}
|
|
diff --git a/node_modules/undici/lib/handler/retry-handler.js b/node_modules/undici/lib/handler/retry-handler.js
|
|
--- a/node_modules/undici/lib/handler/retry-handler.js
|
|
+++ b/node_modules/undici/lib/handler/retry-handler.js
|
|
@@ -90,6 +90,7 @@
|
|
this.end = null
|
|
this.etag = null
|
|
this.resume = null
|
|
+ this.headersSent = false
|
|
|
|
// Handle possible onConnect duplication
|
|
this.handler.onConnect(reason => {
|
|
@@ -100,6 +101,20 @@
|
|
this.reason = reason
|
|
}
|
|
})
|
|
+ }
|
|
+
|
|
+ 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 () {
|
|
@@ -191,6 +206,8 @@
|
|
|
|
if (statusCode >= 300) {
|
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
+ this.headersSent = true
|
|
+ this.checkpointResponseEnd(headers, resume)
|
|
return this.handler.onHeaders(
|
|
statusCode,
|
|
rawHeaders,
|
|
@@ -259,8 +276,15 @@
|
|
|
|
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
|
|
@@ -272,6 +296,7 @@
|
|
const range = parseRangeHeader(headers['content-range'])
|
|
|
|
if (range == null) {
|
|
+ this.headersSent = true
|
|
return this.handler.onHeaders(
|
|
statusCode,
|
|
rawHeaders,
|
|
@@ -310,6 +335,7 @@
|
|
)
|
|
|
|
this.resume = resume
|
|
+ this.headersSent = true
|
|
this.etag = headers.etag != null ? headers.etag : null
|
|
|
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -349,7 +375,7 @@
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
diff --git a/node_modules/undici/package.json b/node_modules/undici/package.json
|
|
--- a/node_modules/undici/package.json
|
|
+++ b/node_modules/undici/package.json
|
|
@@ -1,6 +1,6 @@
|
|
{
|
|
"name": "undici",
|
|
- "version": "6.28.0",
|
|
+ "version": "6.28.1",
|
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
"homepage": "https://undici.nodejs.org",
|
|
"bugs": {
|
|
diff --git a/node_modules/http-cache-semantics/index.js b/node_modules/http-cache-semantics/index.js
|
|
--- a/node_modules/http-cache-semantics/index.js
|
|
+++ b/node_modules/http-cache-semantics/index.js
|
|
@@ -334,12 +334,13 @@
|
|
|
|
/**
|
|
* @param {{headers: Record<string, string>, synchronous: boolean}|undefined} revalidation - Revalidation information, if any.
|
|
- * @returns {{response: {headers: Record<string, string>}, revalidation: {headers: Record<string, string>, synchronous: boolean}|undefined}} An object with a cached response headers and revalidation info.
|
|
+ * @returns {{response: HttpResponse, revalidation: {headers: Record<string, string>, synchronous: boolean}|undefined}} An object with a cached response headers and revalidation info.
|
|
*/
|
|
_evaluateRequestHitResult(revalidation) {
|
|
return {
|
|
response: {
|
|
headers: this.responseHeaders(),
|
|
+ status: this._status,
|
|
},
|
|
revalidation,
|
|
};
|
|
@@ -387,9 +388,9 @@
|
|
* }
|
|
* ```
|
|
* @param {HttpRequest} req - new incoming HTTP request
|
|
- * @returns {{response: {headers: Record<string, string>}|undefined, revalidation: {headers: Record<string, string>, synchronous: boolean}|undefined}} An object containing keys:
|
|
+ * @returns {{response: HttpResponse|undefined, revalidation: {headers: Record<string, string>, synchronous: boolean}|undefined}} An object containing keys:
|
|
* - revalidation: { headers: Record<string, string>, synchronous: boolean } Set if you should send this to the origin server
|
|
- * - response: { headers: Record<string, string> } Set if you can respond to the client with these cached headers
|
|
+ * - response: HttpResponse Set if you can respond to the client with these cached headers
|
|
*/
|
|
evaluateRequest(req) {
|
|
this._assertRequestHasHeaders(req);
|
|
@@ -484,18 +485,32 @@
|
|
return true;
|
|
}
|
|
|
|
- // A Vary header field-value of "*" always fails to match
|
|
- if (this._resHeaders.vary === '*') {
|
|
- return false;
|
|
- }
|
|
-
|
|
const fields = this._resHeaders.vary
|
|
.trim()
|
|
.toLowerCase()
|
|
.split(/\s*,\s*/);
|
|
+
|
|
for (const name of fields) {
|
|
- if (req.headers[name] !== this._reqHeaders[name]) return false;
|
|
- }
|
|
+ // A Vary header field-value of "*" always fails to match
|
|
+ if (name === '*') {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ const newReq = Object.prototype.hasOwnProperty.call(
|
|
+ req.headers,
|
|
+ name
|
|
+ ) && req.headers[name];
|
|
+
|
|
+ const cachedReq = Object.prototype.hasOwnProperty.call(
|
|
+ this._reqHeaders,
|
|
+ name
|
|
+ ) && this._reqHeaders[name];
|
|
+
|
|
+ if (newReq !== cachedReq) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
return true;
|
|
}
|
|
|
|
@@ -554,6 +569,14 @@
|
|
headers.age = `${Math.round(age)}`;
|
|
headers.date = new Date(this.now()).toUTCString();
|
|
return headers;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Returns the status code of the cached response.
|
|
+ * @returns {number} The response status code.
|
|
+ */
|
|
+ status() {
|
|
+ return this._status;
|
|
}
|
|
|
|
/**
|
|
diff --git a/node_modules/http-cache-semantics/package.json b/node_modules/http-cache-semantics/package.json
|
|
--- a/node_modules/http-cache-semantics/package.json
|
|
+++ b/node_modules/http-cache-semantics/package.json
|
|
@@ -1,6 +1,6 @@
|
|
{
|
|
"name": "http-cache-semantics",
|
|
- "version": "4.2.0",
|
|
+ "version": "4.3.0",
|
|
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies",
|
|
"repository": {
|
|
"type": "git",
|
|
@@ -14,9 +14,9 @@
|
|
"files": [
|
|
"index.js"
|
|
],
|
|
- "author": "Kornel Lesiński <npms2@geekhood.net> (https://kornel.ski/)",
|
|
+ "author": "Kornel Lesiński <kornel@geekhood.net> (https://kornel.ski/)",
|
|
"license": "BSD-2-Clause",
|
|
"devDependencies": {
|
|
- "mocha": "^11.0"
|
|
+ "mocha": "^12.0"
|
|
}
|
|
}
|