mirror of
https://github.com/dawidd6/action-ansible-playbook.git
synced 2026-08-11 02:46:21 -06:00
node_modules: update (#146)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
+9
-9
@@ -4,9 +4,9 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz",
|
||||||
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
"integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^3.0.0",
|
"@actions/exec": "^3.0.0",
|
||||||
@@ -48,18 +48,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.24.0",
|
"version": "6.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||||
"integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
|
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=18.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.8.2",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "Actions core lib",
|
"description": "Actions core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
+3
@@ -26,6 +26,9 @@ Returns: `Client`
|
|||||||
* **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.
|
* **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.
|
||||||
* **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.
|
* **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.
|
||||||
* **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
|
* **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
|
||||||
|
* **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
|
||||||
|
* **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit.
|
||||||
|
* **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.
|
||||||
* **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.
|
* **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.
|
||||||
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
|
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
|
||||||
* **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.
|
* **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.
|
||||||
|
|||||||
-15
@@ -20,7 +20,6 @@ When passing an object as the second argument, the following options are availab
|
|||||||
* **protocols** `string | string[]` (optional) - Subprotocol(s) to request the server use.
|
* **protocols** `string | string[]` (optional) - Subprotocol(s) to request the server use.
|
||||||
* **dispatcher** `Dispatcher` (optional) - A custom [`Dispatcher`](/docs/docs/api/Dispatcher.md) to use for the connection.
|
* **dispatcher** `Dispatcher` (optional) - A custom [`Dispatcher`](/docs/docs/api/Dispatcher.md) to use for the connection.
|
||||||
* **headers** `HeadersInit` (optional) - Custom headers to include in the WebSocket handshake request.
|
* **headers** `HeadersInit` (optional) - Custom headers to include in the WebSocket handshake request.
|
||||||
* **maxDecompressedMessageSize** `number` (optional) - Maximum allowed size in bytes for decompressed messages when using the `permessage-deflate` extension. **Default:** `4194304` (4 MB).
|
|
||||||
|
|
||||||
### Example:
|
### Example:
|
||||||
|
|
||||||
@@ -45,20 +44,6 @@ import { WebSocket } from 'undici'
|
|||||||
const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
|
const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example with custom decompression limit:
|
|
||||||
|
|
||||||
To protect against decompression bombs (small compressed payloads that expand to very large sizes), you can set a custom limit:
|
|
||||||
|
|
||||||
```mjs
|
|
||||||
import { WebSocket } from 'undici'
|
|
||||||
|
|
||||||
const ws = new WebSocket('wss://echo.websocket.events', {
|
|
||||||
maxDecompressedMessageSize: 1 * 1024 * 1024
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ **Security Note**: The `maxDecompressedMessageSize` option protects against memory exhaustion attacks where a malicious server sends a small compressed payload that decompresses to an extremely large size. If you increase this limit significantly above the default, ensure your application can handle the increased memory usage.
|
|
||||||
|
|
||||||
## Read More
|
## Read More
|
||||||
|
|
||||||
- [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
- [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||||
|
|||||||
+12
-1
@@ -350,7 +350,13 @@ function processHeader (request, key, val) {
|
|||||||
} else if (typeof val[i] === 'object') {
|
} else if (typeof val[i] === 'object') {
|
||||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||||
} else {
|
} 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
|
val = arr
|
||||||
@@ -361,7 +367,12 @@ function processHeader (request, key, val) {
|
|||||||
} else if (val === null) {
|
} else if (val === null) {
|
||||||
val = ''
|
val = ''
|
||||||
} else {
|
} else {
|
||||||
|
// Coerce primitives (and reject unsafe coercions such as functions
|
||||||
|
// with a crafted toString/Symbol.toPrimitive).
|
||||||
val = `${val}`
|
val = `${val}`
|
||||||
|
if (!isValidHeaderValue(val)) {
|
||||||
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headerName === 'host') {
|
if (headerName === 'host') {
|
||||||
|
|||||||
+2
-2
@@ -24,8 +24,6 @@ function defaultFactory (origin, opts) {
|
|||||||
|
|
||||||
class Agent extends DispatcherBase {
|
class Agent extends DispatcherBase {
|
||||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (typeof factory !== 'function') {
|
if (typeof factory !== 'function') {
|
||||||
throw new InvalidArgumentError('factory must be a function.')
|
throw new InvalidArgumentError('factory must be a function.')
|
||||||
}
|
}
|
||||||
@@ -38,6 +36,8 @@ class Agent extends DispatcherBase {
|
|||||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
if (connect && typeof connect !== 'function') {
|
if (connect && typeof connect !== 'function') {
|
||||||
connect = { ...connect }
|
connect = { ...connect }
|
||||||
}
|
}
|
||||||
|
|||||||
+151
-25
@@ -10,6 +10,7 @@ const {
|
|||||||
RequestContentLengthMismatchError,
|
RequestContentLengthMismatchError,
|
||||||
ResponseContentLengthMismatchError,
|
ResponseContentLengthMismatchError,
|
||||||
RequestAbortedError,
|
RequestAbortedError,
|
||||||
|
InvalidArgumentError,
|
||||||
HeadersTimeoutError,
|
HeadersTimeoutError,
|
||||||
HeadersOverflowError,
|
HeadersOverflowError,
|
||||||
SocketError,
|
SocketError,
|
||||||
@@ -57,6 +58,9 @@ const EMPTY_BUF = Buffer.alloc(0)
|
|||||||
const FastBuffer = Buffer[Symbol.species]
|
const FastBuffer = Buffer[Symbol.species]
|
||||||
const addListener = util.addListener
|
const addListener = util.addListener
|
||||||
const removeAllListeners = util.removeAllListeners
|
const removeAllListeners = util.removeAllListeners
|
||||||
|
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
|
||||||
|
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
|
||||||
|
const kSocketUsed = Symbol('kSocketUsed')
|
||||||
|
|
||||||
let extractBody
|
let extractBody
|
||||||
|
|
||||||
@@ -279,29 +283,71 @@ class Parser {
|
|||||||
|
|
||||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
||||||
|
|
||||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
if (ret !== constants.ERROR.OK) {
|
||||||
this.onUpgrade(data.slice(offset))
|
const body = data.subarray(offset)
|
||||||
} else if (ret === constants.ERROR.PAUSED) {
|
|
||||||
this.paused = true
|
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||||
socket.unshift(data.slice(offset))
|
this.onUpgrade(body)
|
||||||
} else if (ret !== constants.ERROR.OK) {
|
} else if (ret === constants.ERROR.PAUSED) {
|
||||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
this.paused = true
|
||||||
let message = ''
|
socket.unshift(body)
|
||||||
/* istanbul ignore else: difficult to make a test case for */
|
} else {
|
||||||
if (ptr) {
|
throw this.createError(ret, body)
|
||||||
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.slice(offset))
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
util.destroy(socket, 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 () {
|
destroy () {
|
||||||
assert(this.ptr != null)
|
assert(this.ptr != null)
|
||||||
assert(currentParser == null)
|
assert(currentParser == null)
|
||||||
@@ -329,6 +375,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return -1
|
return -1
|
||||||
@@ -432,6 +483,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
|
|
||||||
/* istanbul ignore next: difficult to make a test case for */
|
/* istanbul ignore next: difficult to make a test case for */
|
||||||
@@ -605,6 +661,7 @@ class Parser {
|
|||||||
request.onComplete(headers)
|
request.onComplete(headers)
|
||||||
|
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
client[kQueue][client[kRunningIdx]++] = null
|
||||||
|
socket[kSocketUsed] = true
|
||||||
|
|
||||||
if (socket[kWriting]) {
|
if (socket[kWriting]) {
|
||||||
assert(client[kRunning] === 0)
|
assert(client[kRunning] === 0)
|
||||||
@@ -663,6 +720,9 @@ async function connectH1 (client, socket) {
|
|||||||
socket[kWriting] = false
|
socket[kWriting] = false
|
||||||
socket[kReset] = false
|
socket[kReset] = false
|
||||||
socket[kBlocking] = false
|
socket[kBlocking] = false
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kSocketUsed] = false
|
||||||
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
||||||
|
|
||||||
addListener(socket, 'error', function (err) {
|
addListener(socket, 'error', function (err) {
|
||||||
@@ -673,8 +733,11 @@ async function connectH1 (client, socket) {
|
|||||||
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
||||||
// to the user.
|
// to the user.
|
||||||
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so for as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
this[kError] = parserErr
|
||||||
|
this[kClient][kOnError](parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,8 +756,10 @@ async function connectH1 (client, socket) {
|
|||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
if (parser.statusCode && !parser.shouldKeepAlive) {
|
if (parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
util.destroy(this, parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,10 +769,11 @@ async function connectH1 (client, socket) {
|
|||||||
const client = this[kClient]
|
const client = this[kClient]
|
||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
|
clearIdleSocketValidation(this)
|
||||||
|
|
||||||
if (parser) {
|
if (parser) {
|
||||||
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
this[kError] = parser.finish() || this[kError]
|
||||||
parser.onMessageComplete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this[kParser].destroy()
|
this[kParser].destroy()
|
||||||
@@ -770,7 +836,7 @@ async function connectH1 (client, socket) {
|
|||||||
return socket.destroyed
|
return socket.destroyed
|
||||||
},
|
},
|
||||||
busy (request) {
|
busy (request) {
|
||||||
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
|
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,6 +874,31 @@ async function connectH1 (client, socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearIdleSocketValidation (socket) {
|
||||||
|
if (socket[kIdleSocketValidationTimeout]) {
|
||||||
|
clearTimeout(socket[kIdleSocketValidationTimeout])
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleIdleSocketValidation (client, socket) {
|
||||||
|
socket[kIdleSocketValidation] = 1
|
||||||
|
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kIdleSocketValidation] = 2
|
||||||
|
|
||||||
|
if (client[kSocket] === socket && !socket.destroyed) {
|
||||||
|
client[kResume]()
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
socket[kIdleSocketValidationTimeout].unref?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./client.js')} client
|
||||||
|
*/
|
||||||
function resumeH1 (client) {
|
function resumeH1 (client) {
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
|
||||||
@@ -822,6 +913,32 @@ function resumeH1 (client) {
|
|||||||
socket[kNoRef] = false
|
socket[kNoRef] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
|
||||||
|
if (socket[kIdleSocketValidation] === 0) {
|
||||||
|
scheduleIdleSocketValidation(client, socket)
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket[kIdleSocketValidation] === 1) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (client[kSize] === 0) {
|
if (client[kSize] === 0) {
|
||||||
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
||||||
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
||||||
@@ -877,8 +994,16 @@ function writeH1 (client, request) {
|
|||||||
}
|
}
|
||||||
body = bodyStream.stream
|
body = bodyStream.stream
|
||||||
contentLength = bodyStream.length
|
contentLength = bodyStream.length
|
||||||
} else if (util.isBlobLike(body) && request.contentType == null && body.type) {
|
} else if (util.isBlobLike(body) && request.contentType == null) {
|
||||||
headers.push('content-type', body.type)
|
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') {
|
if (body && typeof body.read === 'function') {
|
||||||
@@ -915,6 +1040,7 @@ function writeH1 (client, request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
clearIdleSocketValidation(socket)
|
||||||
|
|
||||||
const abort = (err) => {
|
const abort = (err) => {
|
||||||
if (request.aborted || request.completed) {
|
if (request.aborted || request.completed) {
|
||||||
|
|||||||
+3
-2
@@ -106,9 +106,10 @@ class Client extends DispatcherBase {
|
|||||||
autoSelectFamilyAttemptTimeout,
|
autoSelectFamilyAttemptTimeout,
|
||||||
// h2
|
// h2
|
||||||
maxConcurrentStreams,
|
maxConcurrentStreams,
|
||||||
allowH2
|
allowH2,
|
||||||
|
webSocket
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
super({ webSocket })
|
||||||
|
|
||||||
if (keepAlive !== undefined) {
|
if (keepAlive !== undefined) {
|
||||||
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
||||||
|
|||||||
+10
-1
@@ -11,15 +11,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = requ
|
|||||||
const kOnDestroyed = Symbol('onDestroyed')
|
const kOnDestroyed = Symbol('onDestroyed')
|
||||||
const kOnClosed = Symbol('onClosed')
|
const kOnClosed = Symbol('onClosed')
|
||||||
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
||||||
|
const kWebSocketOptions = Symbol('webSocketOptions')
|
||||||
|
|
||||||
class DispatcherBase extends Dispatcher {
|
class DispatcherBase extends Dispatcher {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this[kDestroyed] = false
|
this[kDestroyed] = false
|
||||||
this[kOnDestroyed] = null
|
this[kOnDestroyed] = null
|
||||||
this[kClosed] = false
|
this[kClosed] = false
|
||||||
this[kOnClosed] = []
|
this[kOnClosed] = []
|
||||||
|
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
get webSocketOptions () {
|
||||||
|
return {
|
||||||
|
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
||||||
|
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get destroyed () {
|
get destroyed () {
|
||||||
|
|||||||
+2
-2
@@ -19,8 +19,8 @@ const kRemoveClient = Symbol('remove client')
|
|||||||
const kStats = Symbol('stats')
|
const kStats = Symbol('stats')
|
||||||
|
|
||||||
class PoolBase extends DispatcherBase {
|
class PoolBase extends DispatcherBase {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super(opts)
|
||||||
|
|
||||||
this[kQueue] = new FixedQueue()
|
this[kQueue] = new FixedQueue()
|
||||||
this[kClients] = []
|
this[kClients] = []
|
||||||
|
|||||||
+2
-2
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
|
|||||||
allowH2,
|
allowH2,
|
||||||
...options
|
...options
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||||
throw new InvalidArgumentError('invalid connections')
|
throw new InvalidArgumentError('invalid connections')
|
||||||
}
|
}
|
||||||
@@ -63,6 +61,8 @@ class Pool extends PoolBase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
||||||
? options.interceptors.Pool
|
? options.interceptors.Pool
|
||||||
: []
|
: []
|
||||||
|
|||||||
+34
@@ -15,6 +15,28 @@ function calculateRetryAfterHeader (retryAfter) {
|
|||||||
return new Date(retryAfter).getTime() - current
|
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 {
|
class RetryHandler {
|
||||||
constructor (opts, handlers) {
|
constructor (opts, handlers) {
|
||||||
const { retryOptions, ...dispatchOpts } = opts
|
const { retryOptions, ...dispatchOpts } = opts
|
||||||
@@ -229,6 +251,12 @@ class RetryHandler {
|
|||||||
return false
|
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
|
const { start, size, end = size - 1 } = contentRange
|
||||||
|
|
||||||
assert(this.start === start, 'content-range mismatch')
|
assert(this.start === start, 'content-range mismatch')
|
||||||
@@ -252,6 +280,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
|
const { start, size, end = size - 1 } = range
|
||||||
assert(
|
assert(
|
||||||
start != null && Number.isFinite(start),
|
start != null && Number.isFinite(start),
|
||||||
|
|||||||
+16
-23
@@ -275,32 +275,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|||||||
// If the attribute-name case-insensitively matches the string
|
// If the attribute-name case-insensitively matches the string
|
||||||
// "SameSite", the user agent MUST process the cookie-av as follows:
|
// "SameSite", the user agent MUST process the cookie-av as follows:
|
||||||
|
|
||||||
// 1. Let enforcement be "Default".
|
|
||||||
let enforcement = 'Default'
|
|
||||||
|
|
||||||
const attributeValueLowercase = attributeValue.toLowerCase()
|
const attributeValueLowercase = attributeValue.toLowerCase()
|
||||||
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
|
||||||
// "None", set enforcement to "None".
|
|
||||||
if (attributeValueLowercase.includes('none')) {
|
|
||||||
enforcement = 'None'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
// 1. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
// "Strict", set enforcement to "Strict".
|
// "None", append an attribute to the cookie-attribute-list with an
|
||||||
if (attributeValueLowercase.includes('strict')) {
|
// attribute-name of "SameSite" and an attribute-value of "None".
|
||||||
enforcement = 'Strict'
|
if (attributeValueLowercase === 'none') {
|
||||||
|
cookieAttributeList.sameSite = 'None'
|
||||||
|
} else if (attributeValueLowercase === 'strict') {
|
||||||
|
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
|
// "Strict", append an attribute to the cookie-attribute-list with
|
||||||
|
// an attribute-name of "SameSite" and an attribute-value of
|
||||||
|
// "Strict".
|
||||||
|
cookieAttributeList.sameSite = 'Strict'
|
||||||
|
} else if (attributeValueLowercase === 'lax') {
|
||||||
|
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
|
// "Lax", append an attribute to the cookie-attribute-list with an
|
||||||
|
// attribute-name of "SameSite" and an attribute-value of "Lax".
|
||||||
|
cookieAttributeList.sameSite = 'Lax'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. If cookie-av's attribute-value is a case-insensitive match for
|
|
||||||
// "Lax", set enforcement to "Lax".
|
|
||||||
if (attributeValueLowercase.includes('lax')) {
|
|
||||||
enforcement = 'Lax'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Append an attribute to the cookie-attribute-list with an
|
|
||||||
// attribute-name of "SameSite" and an attribute-value of
|
|
||||||
// enforcement.
|
|
||||||
cookieAttributeList.sameSite = enforcement
|
|
||||||
} else {
|
} else {
|
||||||
cookieAttributeList.unparsed ??= []
|
cookieAttributeList.unparsed ??= []
|
||||||
|
|
||||||
|
|||||||
+79
-9
@@ -105,7 +105,7 @@ function validateCookiePath (path) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
code < 0x20 || // exclude CTLs (0-31)
|
code < 0x20 || // exclude CTLs (0-31)
|
||||||
code === 0x7F || // DEL
|
code > 0x7E || // exclude DEL and non-ascii
|
||||||
code === 0x3B // ;
|
code === 0x3B // ;
|
||||||
) {
|
) {
|
||||||
throw new Error('Invalid cookie path')
|
throw new Error('Invalid cookie path')
|
||||||
@@ -114,16 +114,80 @@ function validateCookiePath (path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I have no idea why these values aren't allowed to be honest,
|
* <let-dig> ::= <letter> | <digit>
|
||||||
* but Deno tests these. - Khafra
|
*
|
||||||
|
* <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
|
* @param {string} domain
|
||||||
*/
|
*/
|
||||||
function validateCookieDomain (domain) {
|
function validateCookieDomain (domain) {
|
||||||
if (
|
// <domain> ::= <subdomain> | " "
|
||||||
domain.startsWith('-') ||
|
if (domain === ' ') {
|
||||||
domain.endsWith('.') ||
|
return
|
||||||
domain.endsWith('-')
|
}
|
||||||
) {
|
|
||||||
|
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')
|
throw new Error('Invalid cookie domain')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +330,13 @@ function stringify (cookie) {
|
|||||||
|
|
||||||
const [key, ...value] = part.split('=')
|
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('; ')
|
return out.join('; ')
|
||||||
|
|||||||
+13
-36
@@ -8,45 +8,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
|||||||
const kBuffer = Symbol('kBuffer')
|
const kBuffer = Symbol('kBuffer')
|
||||||
const kLength = Symbol('kLength')
|
const kLength = Symbol('kLength')
|
||||||
|
|
||||||
// Default maximum decompressed message size: 4 MB
|
|
||||||
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
|
||||||
|
|
||||||
class PerMessageDeflate {
|
class PerMessageDeflate {
|
||||||
/** @type {import('node:zlib').InflateRaw} */
|
/** @type {import('node:zlib').InflateRaw} */
|
||||||
#inflate
|
#inflate
|
||||||
|
|
||||||
#options = {}
|
#options = {}
|
||||||
|
|
||||||
/** @type {number} */
|
#maxPayloadSize = 0
|
||||||
#maxDecompressedSize
|
|
||||||
|
|
||||||
/** @type {boolean} */
|
|
||||||
#aborted = false
|
|
||||||
|
|
||||||
/** @type {Function|null} */
|
|
||||||
#currentCallback = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Map<string, string>} extensions
|
* @param {Map<string, string>} extensions
|
||||||
* @param {{ maxDecompressedMessageSize?: number }} [options]
|
|
||||||
*/
|
*/
|
||||||
constructor (extensions, options = {}) {
|
constructor (extensions, options) {
|
||||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||||
this.#maxDecompressedSize = options.maxDecompressedMessageSize ?? kDefaultMaxDecompressedSize
|
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a compressed payload.
|
||||||
|
* @param {Buffer} chunk Compressed data
|
||||||
|
* @param {boolean} fin Final fragment flag
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
*/
|
||||||
decompress (chunk, fin, callback) {
|
decompress (chunk, fin, callback) {
|
||||||
// An endpoint uses the following algorithm to decompress a message.
|
// An endpoint uses the following algorithm to decompress a message.
|
||||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||||
// payload of the message.
|
// payload of the message.
|
||||||
// 2. Decompress the resulting data using DEFLATE.
|
// 2. Decompress the resulting data using DEFLATE.
|
||||||
|
|
||||||
if (this.#aborted) {
|
|
||||||
callback(new MessageSizeExceededError())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.#inflate) {
|
if (!this.#inflate) {
|
||||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||||
|
|
||||||
@@ -69,23 +59,12 @@ class PerMessageDeflate {
|
|||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
|
|
||||||
this.#inflate.on('data', (data) => {
|
this.#inflate.on('data', (data) => {
|
||||||
if (this.#aborted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#inflate[kLength] += data.length
|
this.#inflate[kLength] += data.length
|
||||||
|
|
||||||
if (this.#inflate[kLength] > this.#maxDecompressedSize) {
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||||
this.#aborted = true
|
callback(new MessageSizeExceededError())
|
||||||
this.#inflate.removeAllListeners()
|
this.#inflate.removeAllListeners()
|
||||||
this.#inflate.destroy()
|
|
||||||
this.#inflate = null
|
this.#inflate = null
|
||||||
|
|
||||||
if (this.#currentCallback) {
|
|
||||||
const cb = this.#currentCallback
|
|
||||||
this.#currentCallback = null
|
|
||||||
cb(new MessageSizeExceededError())
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +77,13 @@ class PerMessageDeflate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#currentCallback = callback
|
|
||||||
this.#inflate.write(chunk)
|
this.#inflate.write(chunk)
|
||||||
if (fin) {
|
if (fin) {
|
||||||
this.#inflate.write(tail)
|
this.#inflate.write(tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#inflate.flush(() => {
|
this.#inflate.flush(() => {
|
||||||
if (this.#aborted || !this.#inflate) {
|
if (!this.#inflate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +91,6 @@ class PerMessageDeflate {
|
|||||||
|
|
||||||
this.#inflate[kBuffer].length = 0
|
this.#inflate[kBuffer].length = 0
|
||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
this.#currentCallback = null
|
|
||||||
|
|
||||||
callback(null, full)
|
callback(null, full)
|
||||||
})
|
})
|
||||||
|
|||||||
+106
-25
@@ -18,6 +18,12 @@ const {
|
|||||||
const { WebsocketFrameSend } = require('./frame')
|
const { WebsocketFrameSend } = require('./frame')
|
||||||
const { closeWebSocketConnection } = require('./connection')
|
const { closeWebSocketConnection } = require('./connection')
|
||||||
const { PerMessageDeflate } = require('./permessage-deflate')
|
const { PerMessageDeflate } = require('./permessage-deflate')
|
||||||
|
const { MessageSizeExceededError } = require('../../core/errors')
|
||||||
|
|
||||||
|
function failWebsocketConnectionWithCode (ws, code, reason) {
|
||||||
|
closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
|
||||||
|
failWebsocketConnection(ws, reason)
|
||||||
|
}
|
||||||
|
|
||||||
// This code was influenced by ws released under the MIT license.
|
// This code was influenced by ws released under the MIT license.
|
||||||
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||||
@@ -26,6 +32,7 @@ const { PerMessageDeflate } = require('./permessage-deflate')
|
|||||||
|
|
||||||
class ByteParser extends Writable {
|
class ByteParser extends Writable {
|
||||||
#buffers = []
|
#buffers = []
|
||||||
|
#fragmentsBytes = 0
|
||||||
#byteOffset = 0
|
#byteOffset = 0
|
||||||
#loop = false
|
#loop = false
|
||||||
|
|
||||||
@@ -37,20 +44,24 @@ class ByteParser extends Writable {
|
|||||||
/** @type {Map<string, PerMessageDeflate>} */
|
/** @type {Map<string, PerMessageDeflate>} */
|
||||||
#extensions
|
#extensions
|
||||||
|
|
||||||
/** @type {{ maxDecompressedMessageSize?: number }} */
|
/** @type {number} */
|
||||||
#options
|
#maxFragments
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
#maxPayloadSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./websocket').WebSocket} ws
|
* @param {import('./websocket').WebSocket} ws
|
||||||
* @param {Map<string, string>|null} extensions
|
* @param {Map<string, string>|null} extensions
|
||||||
* @param {{ maxDecompressedMessageSize?: number }} [options]
|
* @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
||||||
*/
|
*/
|
||||||
constructor (ws, extensions, options = {}) {
|
constructor (ws, extensions, options = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.ws = ws
|
this.ws = ws
|
||||||
this.#extensions = extensions == null ? new Map() : extensions
|
this.#extensions = extensions == null ? new Map() : extensions
|
||||||
this.#options = options
|
this.#maxFragments = options.maxFragments ?? 0
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize ?? 0
|
||||||
|
|
||||||
if (this.#extensions.has('permessage-deflate')) {
|
if (this.#extensions.has('permessage-deflate')) {
|
||||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
||||||
@@ -69,6 +80,19 @@ class ByteParser extends Writable {
|
|||||||
this.run(callback)
|
this.run(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#validatePayloadLength () {
|
||||||
|
if (
|
||||||
|
this.#maxPayloadSize > 0 &&
|
||||||
|
!isControlFrame(this.#info.opcode) &&
|
||||||
|
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs whenever a new chunk is received.
|
* Runs whenever a new chunk is received.
|
||||||
* Callback is called whenever there are no more chunks buffering,
|
* Callback is called whenever there are no more chunks buffering,
|
||||||
@@ -157,6 +181,10 @@ class ByteParser extends Writable {
|
|||||||
if (payloadLength <= 125) {
|
if (payloadLength <= 125) {
|
||||||
this.#info.payloadLength = payloadLength
|
this.#info.payloadLength = payloadLength
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (payloadLength === 126) {
|
} else if (payloadLength === 126) {
|
||||||
this.#state = parserStates.PAYLOADLENGTH_16
|
this.#state = parserStates.PAYLOADLENGTH_16
|
||||||
} else if (payloadLength === 127) {
|
} else if (payloadLength === 127) {
|
||||||
@@ -181,6 +209,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||||
if (this.#byteOffset < 8) {
|
if (this.#byteOffset < 8) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -203,6 +235,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = lower
|
this.#info.payloadLength = lower
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.READ_DATA) {
|
} else if (this.#state === parserStates.READ_DATA) {
|
||||||
if (this.#byteOffset < this.#info.payloadLength) {
|
if (this.#byteOffset < this.#info.payloadLength) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -215,42 +251,58 @@ class ByteParser extends Writable {
|
|||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
if (!this.#info.compressed) {
|
if (!this.#info.compressed) {
|
||||||
this.#fragments.push(body)
|
if (!this.writeFragments(body)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If the frame is not fragmented, a message has been received.
|
// If the frame is not fragmented, a message has been received.
|
||||||
// If the frame is fragmented, it will terminate with a fin bit set
|
// If the frame is fragmented, it will terminate with a fin bit set
|
||||||
// and an opcode of 0 (continuation), therefore we handle that when
|
// and an opcode of 0 (continuation), therefore we handle that when
|
||||||
// parsing continuation frames, not here.
|
// parsing continuation frames, not here.
|
||||||
if (!this.#info.fragmented && this.#info.fin) {
|
if (!this.#info.fragmented && this.#info.fin) {
|
||||||
const fullMessage = Buffer.concat(this.#fragments)
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
|
||||||
this.#fragments.length = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
this.#extensions.get('permessage-deflate').decompress(
|
||||||
if (error) {
|
body,
|
||||||
failWebsocketConnection(this.ws, error.message)
|
this.#info.fin,
|
||||||
return
|
(error, data) => {
|
||||||
}
|
if (error) {
|
||||||
|
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
||||||
|
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.#fragments.push(data)
|
if (!this.writeFragments(data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#info.fin) {
|
||||||
|
this.#state = parserStates.INFO
|
||||||
|
this.#loop = true
|
||||||
|
this.run(callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
|
|
||||||
if (!this.#info.fin) {
|
|
||||||
this.#state = parserStates.INFO
|
|
||||||
this.#loop = true
|
this.#loop = true
|
||||||
|
this.#state = parserStates.INFO
|
||||||
this.run(callback)
|
this.run(callback)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
|
||||||
|
|
||||||
this.#loop = true
|
|
||||||
this.#state = parserStates.INFO
|
|
||||||
this.#fragments.length = 0
|
|
||||||
this.run(callback)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.#loop = false
|
this.#loop = false
|
||||||
break
|
break
|
||||||
@@ -302,6 +354,35 @@ class ByteParser extends Writable {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeFragments (fragment) {
|
||||||
|
if (
|
||||||
|
this.#maxFragments > 0 &&
|
||||||
|
this.#fragments.length === this.#maxFragments
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#fragmentsBytes += fragment.length
|
||||||
|
this.#fragments.push(fragment)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeFragments () {
|
||||||
|
const fragments = this.#fragments
|
||||||
|
|
||||||
|
if (fragments.length === 1) {
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
return fragments.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
||||||
|
this.#fragments = []
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
parseCloseBody (data) {
|
parseCloseBody (data) {
|
||||||
assert(data.length !== 1)
|
assert(data.length !== 1)
|
||||||
|
|
||||||
|
|||||||
+8
-22
@@ -44,9 +44,6 @@ class WebSocket extends EventTarget {
|
|||||||
/** @type {SendQueue} */
|
/** @type {SendQueue} */
|
||||||
#sendQueue
|
#sendQueue
|
||||||
|
|
||||||
/** @type {{ maxDecompressedMessageSize?: number }} */
|
|
||||||
#options
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {string|string[]} protocols
|
* @param {string|string[]} protocols
|
||||||
@@ -120,11 +117,6 @@ class WebSocket extends EventTarget {
|
|||||||
// 10. Set this's url to urlRecord.
|
// 10. Set this's url to urlRecord.
|
||||||
this[kWebSocketURL] = new URL(urlRecord.href)
|
this[kWebSocketURL] = new URL(urlRecord.href)
|
||||||
|
|
||||||
// Store options for later use (e.g., maxDecompressedMessageSize)
|
|
||||||
this.#options = {
|
|
||||||
maxDecompressedMessageSize: options.maxDecompressedMessageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// 11. Let client be this's relevant settings object.
|
// 11. Let client be this's relevant settings object.
|
||||||
const client = environmentSettingsObject.settingsObject
|
const client = environmentSettingsObject.settingsObject
|
||||||
|
|
||||||
@@ -443,7 +435,14 @@ class WebSocket extends EventTarget {
|
|||||||
// once this happens, the connection is open
|
// once this happens, the connection is open
|
||||||
this[kResponse] = response
|
this[kResponse] = response
|
||||||
|
|
||||||
const parser = new ByteParser(this, parsedExtensions, this.#options)
|
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
||||||
|
const maxFragments = webSocketOptions?.maxFragments
|
||||||
|
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
||||||
|
|
||||||
|
const parser = new ByteParser(this, parsedExtensions, {
|
||||||
|
maxFragments,
|
||||||
|
maxPayloadSize
|
||||||
|
})
|
||||||
parser.on('drain', onParserDrain)
|
parser.on('drain', onParserDrain)
|
||||||
parser.on('error', onParserError.bind(this))
|
parser.on('error', onParserError.bind(this))
|
||||||
|
|
||||||
@@ -546,19 +545,6 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|||||||
{
|
{
|
||||||
key: 'headers',
|
key: 'headers',
|
||||||
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxDecompressedMessageSize',
|
|
||||||
converter: webidl.nullableConverter((V) => {
|
|
||||||
V = webidl.converters['unsigned long long'](V)
|
|
||||||
if (V <= 0) {
|
|
||||||
throw webidl.errors.exception({
|
|
||||||
header: 'WebSocket constructor',
|
|
||||||
message: 'maxDecompressedMessageSize must be greater than 0'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return V
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "undici",
|
"name": "undici",
|
||||||
"version": "6.24.0",
|
"version": "6.28.0",
|
||||||
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
||||||
"homepage": "https://undici.nodejs.org",
|
"homepage": "https://undici.nodejs.org",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|||||||
+17
@@ -78,6 +78,8 @@ export declare namespace Client {
|
|||||||
localAddress?: string;
|
localAddress?: string;
|
||||||
/** Max response body size in bytes, -1 is disabled */
|
/** Max response body size in bytes, -1 is disabled */
|
||||||
maxResponseSize?: number;
|
maxResponseSize?: number;
|
||||||
|
/** WebSocket-specific options */
|
||||||
|
webSocket?: Client.WebSocketOptions;
|
||||||
/** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */
|
/** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */
|
||||||
autoSelectFamily?: boolean;
|
autoSelectFamily?: boolean;
|
||||||
/** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */
|
/** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */
|
||||||
@@ -103,6 +105,21 @@ export declare namespace Client {
|
|||||||
bytesWritten?: number
|
bytesWritten?: number
|
||||||
bytesRead?: number
|
bytesRead?: number
|
||||||
}
|
}
|
||||||
|
export interface WebSocketOptions {
|
||||||
|
/**
|
||||||
|
* Maximum number of fragments in a message.
|
||||||
|
* Set to 0 to disable the limit.
|
||||||
|
* @default 131072
|
||||||
|
*/
|
||||||
|
maxFragments?: number;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @default 134217728 (128 MB)
|
||||||
|
*/
|
||||||
|
maxPayloadSize?: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Client;
|
export default Client;
|
||||||
|
|||||||
+1
-8
@@ -146,12 +146,5 @@ export declare const ErrorEvent: {
|
|||||||
interface WebSocketInit {
|
interface WebSocketInit {
|
||||||
protocols?: string | string[],
|
protocols?: string | string[],
|
||||||
dispatcher?: Dispatcher,
|
dispatcher?: Dispatcher,
|
||||||
headers?: HeadersInit,
|
headers?: HeadersInit
|
||||||
/**
|
|
||||||
* Maximum size in bytes for decompressed WebSocket messages.
|
|
||||||
* When a message exceeds this limit during decompression, the connection
|
|
||||||
* will be closed with status code 1009 (Message Too Big).
|
|
||||||
* @default 4194304 (4 MB)
|
|
||||||
*/
|
|
||||||
maxDecompressedMessageSize?: number
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -28,19 +28,26 @@ function composeNode(ctx, token, props, onError) {
|
|||||||
case 'block-map':
|
case 'block-map':
|
||||||
case 'block-seq':
|
case 'block-seq':
|
||||||
case 'flow-collection':
|
case 'flow-collection':
|
||||||
node = composeCollection(CN, ctx, token, props, onError);
|
try {
|
||||||
if (anchor)
|
node = composeCollection(CN, ctx, token, props, onError);
|
||||||
node.anchor = anchor.source.substring(1);
|
if (anchor)
|
||||||
|
node.anchor = anchor.source.substring(1);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Almost certainly here due to a stack overflow
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
onError(token, 'RESOURCE_EXHAUSTION', message);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
const message = token.type === 'error'
|
const message = token.type === 'error'
|
||||||
? token.message
|
? token.message
|
||||||
: `Unsupported token (type: ${token.type})`;
|
: `Unsupported token (type: ${token.type})`;
|
||||||
onError(token, 'UNEXPECTED_TOKEN', message);
|
onError(token, 'UNEXPECTED_TOKEN', message);
|
||||||
node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
|
|
||||||
isSrcToken = false;
|
isSrcToken = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
||||||
if (anchor && node.anchor === '')
|
if (anchor && node.anchor === '')
|
||||||
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
||||||
if (atKey &&
|
if (atKey &&
|
||||||
|
|||||||
+4
-2
@@ -94,8 +94,10 @@ class Composer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (afterDoc) {
|
if (afterDoc) {
|
||||||
Array.prototype.push.apply(doc.errors, this.errors);
|
for (let i = 0; i < this.errors.length; ++i)
|
||||||
Array.prototype.push.apply(doc.warnings, this.warnings);
|
doc.errors.push(this.errors[i]);
|
||||||
|
for (let i = 0; i < this.warnings.length; ++i)
|
||||||
|
doc.warnings.push(this.warnings[i]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
doc.errors = this.errors;
|
doc.errors = this.errors;
|
||||||
|
|||||||
+5
-3
@@ -142,7 +142,7 @@ function doubleQuotedValue(source, onError) {
|
|||||||
next = source[++i + 1];
|
next = source[++i + 1];
|
||||||
}
|
}
|
||||||
else if (next === 'x' || next === 'u' || next === 'U') {
|
else if (next === 'x' || next === 'u' || next === 'U') {
|
||||||
const length = { x: 2, u: 4, U: 8 }[next];
|
const length = next === 'x' ? 2 : next === 'u' ? 4 : 8;
|
||||||
res += parseCharCode(source, i + 1, length, onError);
|
res += parseCharCode(source, i + 1, length, onError);
|
||||||
i += length;
|
i += length;
|
||||||
}
|
}
|
||||||
@@ -212,12 +212,14 @@ function parseCharCode(source, offset, length, onError) {
|
|||||||
const cc = source.substr(offset, length);
|
const cc = source.substr(offset, length);
|
||||||
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
||||||
const code = ok ? parseInt(cc, 16) : NaN;
|
const code = ok ? parseInt(cc, 16) : NaN;
|
||||||
if (isNaN(code)) {
|
try {
|
||||||
|
return String.fromCodePoint(code);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
const raw = source.substr(offset - 2, length + 2);
|
const raw = source.substr(offset - 2, length + 2);
|
||||||
onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
|
onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
return String.fromCodePoint(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { resolveFlowScalar };
|
export { resolveFlowScalar };
|
||||||
|
|||||||
+2
@@ -19,6 +19,8 @@ class Alias extends NodeBase {
|
|||||||
* instance of the `source` anchor before this node.
|
* instance of the `source` anchor before this node.
|
||||||
*/
|
*/
|
||||||
resolve(doc, ctx) {
|
resolve(doc, ctx) {
|
||||||
|
if (ctx?.maxAliasCount === 0)
|
||||||
|
throw new ReferenceError('Alias resolution is disabled');
|
||||||
let nodes;
|
let nodes;
|
||||||
if (ctx?.aliasResolveCache) {
|
if (ctx?.aliasResolveCache) {
|
||||||
nodes = ctx.aliasResolveCache;
|
nodes = ctx.aliasResolveCache;
|
||||||
|
|||||||
+28
-24
@@ -310,7 +310,7 @@ class Lexer {
|
|||||||
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
||||||
this.indentNext = this.indentValue + 1;
|
this.indentNext = this.indentValue + 1;
|
||||||
this.indentValue += n;
|
this.indentValue += n;
|
||||||
return yield* this.parseBlockStart();
|
return 'block-start';
|
||||||
}
|
}
|
||||||
return 'doc';
|
return 'doc';
|
||||||
}
|
}
|
||||||
@@ -631,32 +631,36 @@ class Lexer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*pushIndicators() {
|
*pushIndicators() {
|
||||||
switch (this.charAt(0)) {
|
let n = 0;
|
||||||
case '!':
|
loop: while (true) {
|
||||||
return ((yield* this.pushTag()) +
|
switch (this.charAt(0)) {
|
||||||
(yield* this.pushSpaces(true)) +
|
case '!':
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushTag();
|
||||||
case '&':
|
n += yield* this.pushSpaces(true);
|
||||||
return ((yield* this.pushUntil(isNotAnchorChar)) +
|
continue loop;
|
||||||
(yield* this.pushSpaces(true)) +
|
case '&':
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushUntil(isNotAnchorChar);
|
||||||
case '-': // this is an error
|
n += yield* this.pushSpaces(true);
|
||||||
case '?': // this is an error outside flow collections
|
continue loop;
|
||||||
case ':': {
|
case '-': // this is an error
|
||||||
const inFlow = this.flowLevel > 0;
|
case '?': // this is an error outside flow collections
|
||||||
const ch1 = this.charAt(1);
|
case ':': {
|
||||||
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
const inFlow = this.flowLevel > 0;
|
||||||
if (!inFlow)
|
const ch1 = this.charAt(1);
|
||||||
this.indentNext = this.indentValue + 1;
|
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
||||||
else if (this.flowKey)
|
if (!inFlow)
|
||||||
this.flowKey = false;
|
this.indentNext = this.indentValue + 1;
|
||||||
return ((yield* this.pushCount(1)) +
|
else if (this.flowKey)
|
||||||
(yield* this.pushSpaces(true)) +
|
this.flowKey = false;
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushCount(1);
|
||||||
|
n += yield* this.pushSpaces(true);
|
||||||
|
continue loop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break loop;
|
||||||
}
|
}
|
||||||
return 0;
|
return n;
|
||||||
}
|
}
|
||||||
*pushTag() {
|
*pushTag() {
|
||||||
if (this.charAt(1) === '<') {
|
if (this.charAt(1) === '<') {
|
||||||
|
|||||||
+12
-4
@@ -67,6 +67,14 @@ function getFirstKeyStartProps(prev) {
|
|||||||
}
|
}
|
||||||
return prev.splice(i, prev.length);
|
return prev.splice(i, prev.length);
|
||||||
}
|
}
|
||||||
|
function arrayPushArray(target, source) {
|
||||||
|
// May exhaust call stack with large `source` array
|
||||||
|
if (source.length < 1e5)
|
||||||
|
Array.prototype.push.apply(target, source);
|
||||||
|
else
|
||||||
|
for (let i = 0; i < source.length; ++i)
|
||||||
|
target.push(source[i]);
|
||||||
|
}
|
||||||
function fixFlowSeqItems(fc) {
|
function fixFlowSeqItems(fc) {
|
||||||
if (fc.start.type === 'flow-seq-start') {
|
if (fc.start.type === 'flow-seq-start') {
|
||||||
for (const it of fc.items) {
|
for (const it of fc.items) {
|
||||||
@@ -79,12 +87,12 @@ function fixFlowSeqItems(fc) {
|
|||||||
delete it.key;
|
delete it.key;
|
||||||
if (isFlowToken(it.value)) {
|
if (isFlowToken(it.value)) {
|
||||||
if (it.value.end)
|
if (it.value.end)
|
||||||
Array.prototype.push.apply(it.value.end, it.sep);
|
arrayPushArray(it.value.end, it.sep);
|
||||||
else
|
else
|
||||||
it.value.end = it.sep;
|
it.value.end = it.sep;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Array.prototype.push.apply(it.start, it.sep);
|
arrayPushArray(it.start, it.sep);
|
||||||
delete it.sep;
|
delete it.sep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,7 +510,7 @@ class Parser {
|
|||||||
const prev = map.items[map.items.length - 2];
|
const prev = map.items[map.items.length - 2];
|
||||||
const end = prev?.value?.end;
|
const end = prev?.value?.end;
|
||||||
if (Array.isArray(end)) {
|
if (Array.isArray(end)) {
|
||||||
Array.prototype.push.apply(end, it.start);
|
arrayPushArray(end, it.start);
|
||||||
end.push(this.sourceToken);
|
end.push(this.sourceToken);
|
||||||
map.items.pop();
|
map.items.pop();
|
||||||
return;
|
return;
|
||||||
@@ -717,7 +725,7 @@ class Parser {
|
|||||||
const prev = seq.items[seq.items.length - 2];
|
const prev = seq.items[seq.items.length - 2];
|
||||||
const end = prev?.value?.end;
|
const end = prev?.value?.end;
|
||||||
if (Array.isArray(end)) {
|
if (Array.isArray(end)) {
|
||||||
Array.prototype.push.apply(end, it.start);
|
arrayPushArray(end, it.start);
|
||||||
end.push(this.sourceToken);
|
end.push(this.sourceToken);
|
||||||
seq.items.pop();
|
seq.items.pop();
|
||||||
return;
|
return;
|
||||||
|
|||||||
+11
-8
@@ -1,4 +1,4 @@
|
|||||||
import { isScalar, isAlias, isSeq, isMap } from '../../nodes/identity.js';
|
import { isScalar, isSeq, isAlias, isMap } from '../../nodes/identity.js';
|
||||||
import { Scalar } from '../../nodes/Scalar.js';
|
import { Scalar } from '../../nodes/Scalar.js';
|
||||||
|
|
||||||
// If the value associated with a merge key is a single mapping node, each of
|
// If the value associated with a merge key is a single mapping node, each of
|
||||||
@@ -26,18 +26,18 @@ const isMergeKey = (ctx, key) => (merge.identify(key) ||
|
|||||||
merge.identify(key.value))) &&
|
merge.identify(key.value))) &&
|
||||||
ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
|
ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
|
||||||
function addMergeToJSMap(ctx, map, value) {
|
function addMergeToJSMap(ctx, map, value) {
|
||||||
value = ctx && isAlias(value) ? value.resolve(ctx.doc) : value;
|
const source = resolveAliasValue(ctx, value);
|
||||||
if (isSeq(value))
|
if (isSeq(source))
|
||||||
for (const it of value.items)
|
for (const it of source.items)
|
||||||
mergeValue(ctx, map, it);
|
mergeValue(ctx, map, it);
|
||||||
else if (Array.isArray(value))
|
else if (Array.isArray(source))
|
||||||
for (const it of value)
|
for (const it of source)
|
||||||
mergeValue(ctx, map, it);
|
mergeValue(ctx, map, it);
|
||||||
else
|
else
|
||||||
mergeValue(ctx, map, value);
|
mergeValue(ctx, map, source);
|
||||||
}
|
}
|
||||||
function mergeValue(ctx, map, value) {
|
function mergeValue(ctx, map, value) {
|
||||||
const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value;
|
const source = resolveAliasValue(ctx, value);
|
||||||
if (!isMap(source))
|
if (!isMap(source))
|
||||||
throw new Error('Merge sources must be maps or map aliases');
|
throw new Error('Merge sources must be maps or map aliases');
|
||||||
const srcMap = source.toJSON(null, ctx, Map);
|
const srcMap = source.toJSON(null, ctx, Map);
|
||||||
@@ -60,5 +60,8 @@ function mergeValue(ctx, map, value) {
|
|||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
function resolveAliasValue(ctx, value) {
|
||||||
|
return ctx && isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
|
||||||
|
}
|
||||||
|
|
||||||
export { addMergeToJSMap, isMergeKey, merge };
|
export { addMergeToJSMap, isMergeKey, merge };
|
||||||
|
|||||||
+1
@@ -20,6 +20,7 @@ function createStringifyContext(doc, options) {
|
|||||||
nullStr: 'null',
|
nullStr: 'null',
|
||||||
simpleKeys: false,
|
simpleKeys: false,
|
||||||
singleQuote: null,
|
singleQuote: null,
|
||||||
|
trailingComma: false,
|
||||||
trueStr: 'true',
|
trueStr: 'true',
|
||||||
verifyAliasOrder: true
|
verifyAliasOrder: true
|
||||||
}, doc.schema.toStringOptions, options);
|
}, doc.schema.toStringOptions, options);
|
||||||
|
|||||||
+13
-3
@@ -102,12 +102,22 @@ function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
|
|||||||
if (comment)
|
if (comment)
|
||||||
reqNewline = true;
|
reqNewline = true;
|
||||||
let str = stringify(item, itemCtx, () => (comment = null));
|
let str = stringify(item, itemCtx, () => (comment = null));
|
||||||
if (i < items.length - 1)
|
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes('\n'));
|
||||||
|
if (i < items.length - 1) {
|
||||||
str += ',';
|
str += ',';
|
||||||
|
}
|
||||||
|
else if (ctx.options.trailingComma) {
|
||||||
|
if (ctx.options.lineWidth > 0) {
|
||||||
|
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) +
|
||||||
|
(str.length + 2) >
|
||||||
|
ctx.options.lineWidth);
|
||||||
|
}
|
||||||
|
if (reqNewline) {
|
||||||
|
str += ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
if (comment)
|
if (comment)
|
||||||
str += lineComment(str, itemIndent, commentString(comment));
|
str += lineComment(str, itemIndent, commentString(comment));
|
||||||
if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
|
|
||||||
reqNewline = true;
|
|
||||||
lines.push(str);
|
lines.push(str);
|
||||||
linesAtValue = lines.length;
|
linesAtValue = lines.length;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@ function stringifyNumber({ format, minFractionDigits, tag, value }) {
|
|||||||
if (!format &&
|
if (!format &&
|
||||||
minFractionDigits &&
|
minFractionDigits &&
|
||||||
(!tag || tag === 'tag:yaml.org,2002:float') &&
|
(!tag || tag === 'tag:yaml.org,2002:float') &&
|
||||||
/^\d/.test(n)) {
|
/^-?\d/.test(n) &&
|
||||||
|
!n.includes('e')) {
|
||||||
let i = n.indexOf('.');
|
let i = n.indexOf('.');
|
||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
i = n.length;
|
i = n.length;
|
||||||
|
|||||||
+11
-4
@@ -30,19 +30,26 @@ function composeNode(ctx, token, props, onError) {
|
|||||||
case 'block-map':
|
case 'block-map':
|
||||||
case 'block-seq':
|
case 'block-seq':
|
||||||
case 'flow-collection':
|
case 'flow-collection':
|
||||||
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
try {
|
||||||
if (anchor)
|
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
||||||
node.anchor = anchor.source.substring(1);
|
if (anchor)
|
||||||
|
node.anchor = anchor.source.substring(1);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Almost certainly here due to a stack overflow
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
onError(token, 'RESOURCE_EXHAUSTION', message);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
const message = token.type === 'error'
|
const message = token.type === 'error'
|
||||||
? token.message
|
? token.message
|
||||||
: `Unsupported token (type: ${token.type})`;
|
: `Unsupported token (type: ${token.type})`;
|
||||||
onError(token, 'UNEXPECTED_TOKEN', message);
|
onError(token, 'UNEXPECTED_TOKEN', message);
|
||||||
node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
|
|
||||||
isSrcToken = false;
|
isSrcToken = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
||||||
if (anchor && node.anchor === '')
|
if (anchor && node.anchor === '')
|
||||||
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
||||||
if (atKey &&
|
if (atKey &&
|
||||||
|
|||||||
+4
-2
@@ -97,8 +97,10 @@ class Composer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (afterDoc) {
|
if (afterDoc) {
|
||||||
Array.prototype.push.apply(doc.errors, this.errors);
|
for (let i = 0; i < this.errors.length; ++i)
|
||||||
Array.prototype.push.apply(doc.warnings, this.warnings);
|
doc.errors.push(this.errors[i]);
|
||||||
|
for (let i = 0; i < this.warnings.length; ++i)
|
||||||
|
doc.warnings.push(this.warnings[i]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
doc.errors = this.errors;
|
doc.errors = this.errors;
|
||||||
|
|||||||
+5
-3
@@ -144,7 +144,7 @@ function doubleQuotedValue(source, onError) {
|
|||||||
next = source[++i + 1];
|
next = source[++i + 1];
|
||||||
}
|
}
|
||||||
else if (next === 'x' || next === 'u' || next === 'U') {
|
else if (next === 'x' || next === 'u' || next === 'U') {
|
||||||
const length = { x: 2, u: 4, U: 8 }[next];
|
const length = next === 'x' ? 2 : next === 'u' ? 4 : 8;
|
||||||
res += parseCharCode(source, i + 1, length, onError);
|
res += parseCharCode(source, i + 1, length, onError);
|
||||||
i += length;
|
i += length;
|
||||||
}
|
}
|
||||||
@@ -214,12 +214,14 @@ function parseCharCode(source, offset, length, onError) {
|
|||||||
const cc = source.substr(offset, length);
|
const cc = source.substr(offset, length);
|
||||||
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
||||||
const code = ok ? parseInt(cc, 16) : NaN;
|
const code = ok ? parseInt(cc, 16) : NaN;
|
||||||
if (isNaN(code)) {
|
try {
|
||||||
|
return String.fromCodePoint(code);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
const raw = source.substr(offset - 2, length + 2);
|
const raw = source.substr(offset - 2, length + 2);
|
||||||
onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
|
onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
return String.fromCodePoint(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.resolveFlowScalar = resolveFlowScalar;
|
exports.resolveFlowScalar = resolveFlowScalar;
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import type { LineCounter } from './parse/line-counter';
|
import type { LineCounter } from './parse/line-counter';
|
||||||
export type ErrorCode = 'ALIAS_PROPS' | 'BAD_ALIAS' | 'BAD_DIRECTIVE' | 'BAD_DQ_ESCAPE' | 'BAD_INDENT' | 'BAD_PROP_ORDER' | 'BAD_SCALAR_START' | 'BLOCK_AS_IMPLICIT_KEY' | 'BLOCK_IN_FLOW' | 'DUPLICATE_KEY' | 'IMPOSSIBLE' | 'KEY_OVER_1024_CHARS' | 'MISSING_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'NON_STRING_KEY' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN' | 'BAD_COLLECTION_TYPE';
|
export type ErrorCode = 'ALIAS_PROPS' | 'BAD_ALIAS' | 'BAD_DIRECTIVE' | 'BAD_DQ_ESCAPE' | 'BAD_INDENT' | 'BAD_PROP_ORDER' | 'BAD_SCALAR_START' | 'BLOCK_AS_IMPLICIT_KEY' | 'BLOCK_IN_FLOW' | 'DUPLICATE_KEY' | 'IMPOSSIBLE' | 'KEY_OVER_1024_CHARS' | 'MISSING_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'NON_STRING_KEY' | 'RESOURCE_EXHAUSTION' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN' | 'BAD_COLLECTION_TYPE';
|
||||||
export type LinePos = {
|
export type LinePos = {
|
||||||
line: number;
|
line: number;
|
||||||
col: number;
|
col: number;
|
||||||
|
|||||||
+2
@@ -21,6 +21,8 @@ class Alias extends Node.NodeBase {
|
|||||||
* instance of the `source` anchor before this node.
|
* instance of the `source` anchor before this node.
|
||||||
*/
|
*/
|
||||||
resolve(doc, ctx) {
|
resolve(doc, ctx) {
|
||||||
|
if (ctx?.maxAliasCount === 0)
|
||||||
|
throw new ReferenceError('Alias resolution is disabled');
|
||||||
let nodes;
|
let nodes;
|
||||||
if (ctx?.aliasResolveCache) {
|
if (ctx?.aliasResolveCache) {
|
||||||
nodes = ctx.aliasResolveCache;
|
nodes = ctx.aliasResolveCache;
|
||||||
|
|||||||
+5
-1
@@ -31,7 +31,11 @@ export declare class Scalar<T = unknown> extends NodeBase {
|
|||||||
* The YAML 1.1 schema also supports 'BIN' and 'TIME'
|
* The YAML 1.1 schema also supports 'BIN' and 'TIME'
|
||||||
*/
|
*/
|
||||||
format?: string;
|
format?: string;
|
||||||
/** If `value` is a number, use this value when stringifying this node. */
|
/**
|
||||||
|
* If `value` is a number that is serialized as a decimal string
|
||||||
|
* (i.e. not using exponential notation),
|
||||||
|
* use this value when stringifying this node.
|
||||||
|
*/
|
||||||
minFractionDigits?: number;
|
minFractionDigits?: number;
|
||||||
/** Set during parsing to the source string value */
|
/** Set during parsing to the source string value */
|
||||||
source?: string;
|
source?: string;
|
||||||
|
|||||||
+6
@@ -326,6 +326,12 @@ export type ToStringOptions = {
|
|||||||
* Default: `null`
|
* Default: `null`
|
||||||
*/
|
*/
|
||||||
singleQuote?: boolean | null;
|
singleQuote?: boolean | null;
|
||||||
|
/**
|
||||||
|
* Add a trailing comma after the last entry in a flow map or flow sequence that's split across multiple lines.
|
||||||
|
*
|
||||||
|
* Default: `'false'`
|
||||||
|
*/
|
||||||
|
trailingComma?: boolean;
|
||||||
/**
|
/**
|
||||||
* String representation for `true`.
|
* String representation for `true`.
|
||||||
* With the core schema, use `'true'`, `'True'`, or `'TRUE'`.
|
* With the core schema, use `'true'`, `'True'`, or `'TRUE'`.
|
||||||
|
|||||||
+28
-24
@@ -312,7 +312,7 @@ class Lexer {
|
|||||||
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
||||||
this.indentNext = this.indentValue + 1;
|
this.indentNext = this.indentValue + 1;
|
||||||
this.indentValue += n;
|
this.indentValue += n;
|
||||||
return yield* this.parseBlockStart();
|
return 'block-start';
|
||||||
}
|
}
|
||||||
return 'doc';
|
return 'doc';
|
||||||
}
|
}
|
||||||
@@ -633,32 +633,36 @@ class Lexer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*pushIndicators() {
|
*pushIndicators() {
|
||||||
switch (this.charAt(0)) {
|
let n = 0;
|
||||||
case '!':
|
loop: while (true) {
|
||||||
return ((yield* this.pushTag()) +
|
switch (this.charAt(0)) {
|
||||||
(yield* this.pushSpaces(true)) +
|
case '!':
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushTag();
|
||||||
case '&':
|
n += yield* this.pushSpaces(true);
|
||||||
return ((yield* this.pushUntil(isNotAnchorChar)) +
|
continue loop;
|
||||||
(yield* this.pushSpaces(true)) +
|
case '&':
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushUntil(isNotAnchorChar);
|
||||||
case '-': // this is an error
|
n += yield* this.pushSpaces(true);
|
||||||
case '?': // this is an error outside flow collections
|
continue loop;
|
||||||
case ':': {
|
case '-': // this is an error
|
||||||
const inFlow = this.flowLevel > 0;
|
case '?': // this is an error outside flow collections
|
||||||
const ch1 = this.charAt(1);
|
case ':': {
|
||||||
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
const inFlow = this.flowLevel > 0;
|
||||||
if (!inFlow)
|
const ch1 = this.charAt(1);
|
||||||
this.indentNext = this.indentValue + 1;
|
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
||||||
else if (this.flowKey)
|
if (!inFlow)
|
||||||
this.flowKey = false;
|
this.indentNext = this.indentValue + 1;
|
||||||
return ((yield* this.pushCount(1)) +
|
else if (this.flowKey)
|
||||||
(yield* this.pushSpaces(true)) +
|
this.flowKey = false;
|
||||||
(yield* this.pushIndicators()));
|
n += yield* this.pushCount(1);
|
||||||
|
n += yield* this.pushSpaces(true);
|
||||||
|
continue loop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break loop;
|
||||||
}
|
}
|
||||||
return 0;
|
return n;
|
||||||
}
|
}
|
||||||
*pushTag() {
|
*pushTag() {
|
||||||
if (this.charAt(1) === '<') {
|
if (this.charAt(1) === '<') {
|
||||||
|
|||||||
+12
-4
@@ -70,6 +70,14 @@ function getFirstKeyStartProps(prev) {
|
|||||||
}
|
}
|
||||||
return prev.splice(i, prev.length);
|
return prev.splice(i, prev.length);
|
||||||
}
|
}
|
||||||
|
function arrayPushArray(target, source) {
|
||||||
|
// May exhaust call stack with large `source` array
|
||||||
|
if (source.length < 1e5)
|
||||||
|
Array.prototype.push.apply(target, source);
|
||||||
|
else
|
||||||
|
for (let i = 0; i < source.length; ++i)
|
||||||
|
target.push(source[i]);
|
||||||
|
}
|
||||||
function fixFlowSeqItems(fc) {
|
function fixFlowSeqItems(fc) {
|
||||||
if (fc.start.type === 'flow-seq-start') {
|
if (fc.start.type === 'flow-seq-start') {
|
||||||
for (const it of fc.items) {
|
for (const it of fc.items) {
|
||||||
@@ -82,12 +90,12 @@ function fixFlowSeqItems(fc) {
|
|||||||
delete it.key;
|
delete it.key;
|
||||||
if (isFlowToken(it.value)) {
|
if (isFlowToken(it.value)) {
|
||||||
if (it.value.end)
|
if (it.value.end)
|
||||||
Array.prototype.push.apply(it.value.end, it.sep);
|
arrayPushArray(it.value.end, it.sep);
|
||||||
else
|
else
|
||||||
it.value.end = it.sep;
|
it.value.end = it.sep;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Array.prototype.push.apply(it.start, it.sep);
|
arrayPushArray(it.start, it.sep);
|
||||||
delete it.sep;
|
delete it.sep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,7 +515,7 @@ class Parser {
|
|||||||
const prev = map.items[map.items.length - 2];
|
const prev = map.items[map.items.length - 2];
|
||||||
const end = prev?.value?.end;
|
const end = prev?.value?.end;
|
||||||
if (Array.isArray(end)) {
|
if (Array.isArray(end)) {
|
||||||
Array.prototype.push.apply(end, it.start);
|
arrayPushArray(end, it.start);
|
||||||
end.push(this.sourceToken);
|
end.push(this.sourceToken);
|
||||||
map.items.pop();
|
map.items.pop();
|
||||||
return;
|
return;
|
||||||
@@ -722,7 +730,7 @@ class Parser {
|
|||||||
const prev = seq.items[seq.items.length - 2];
|
const prev = seq.items[seq.items.length - 2];
|
||||||
const end = prev?.value?.end;
|
const end = prev?.value?.end;
|
||||||
if (Array.isArray(end)) {
|
if (Array.isArray(end)) {
|
||||||
Array.prototype.push.apply(end, it.start);
|
arrayPushArray(end, it.start);
|
||||||
end.push(this.sourceToken);
|
end.push(this.sourceToken);
|
||||||
seq.items.pop();
|
seq.items.pop();
|
||||||
return;
|
return;
|
||||||
|
|||||||
+10
-7
@@ -28,18 +28,18 @@ const isMergeKey = (ctx, key) => (merge.identify(key) ||
|
|||||||
merge.identify(key.value))) &&
|
merge.identify(key.value))) &&
|
||||||
ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
|
ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
|
||||||
function addMergeToJSMap(ctx, map, value) {
|
function addMergeToJSMap(ctx, map, value) {
|
||||||
value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
|
const source = resolveAliasValue(ctx, value);
|
||||||
if (identity.isSeq(value))
|
if (identity.isSeq(source))
|
||||||
for (const it of value.items)
|
for (const it of source.items)
|
||||||
mergeValue(ctx, map, it);
|
mergeValue(ctx, map, it);
|
||||||
else if (Array.isArray(value))
|
else if (Array.isArray(source))
|
||||||
for (const it of value)
|
for (const it of source)
|
||||||
mergeValue(ctx, map, it);
|
mergeValue(ctx, map, it);
|
||||||
else
|
else
|
||||||
mergeValue(ctx, map, value);
|
mergeValue(ctx, map, source);
|
||||||
}
|
}
|
||||||
function mergeValue(ctx, map, value) {
|
function mergeValue(ctx, map, value) {
|
||||||
const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
|
const source = resolveAliasValue(ctx, value);
|
||||||
if (!identity.isMap(source))
|
if (!identity.isMap(source))
|
||||||
throw new Error('Merge sources must be maps or map aliases');
|
throw new Error('Merge sources must be maps or map aliases');
|
||||||
const srcMap = source.toJSON(null, ctx, Map);
|
const srcMap = source.toJSON(null, ctx, Map);
|
||||||
@@ -62,6 +62,9 @@ function mergeValue(ctx, map, value) {
|
|||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
function resolveAliasValue(ctx, value) {
|
||||||
|
return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
|
||||||
|
}
|
||||||
|
|
||||||
exports.addMergeToJSMap = addMergeToJSMap;
|
exports.addMergeToJSMap = addMergeToJSMap;
|
||||||
exports.isMergeKey = isMergeKey;
|
exports.isMergeKey = isMergeKey;
|
||||||
|
|||||||
+1
@@ -22,6 +22,7 @@ function createStringifyContext(doc, options) {
|
|||||||
nullStr: 'null',
|
nullStr: 'null',
|
||||||
simpleKeys: false,
|
simpleKeys: false,
|
||||||
singleQuote: null,
|
singleQuote: null,
|
||||||
|
trailingComma: false,
|
||||||
trueStr: 'true',
|
trueStr: 'true',
|
||||||
verifyAliasOrder: true
|
verifyAliasOrder: true
|
||||||
}, doc.schema.toStringOptions, options);
|
}, doc.schema.toStringOptions, options);
|
||||||
|
|||||||
+13
-3
@@ -104,12 +104,22 @@ function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
|
|||||||
if (comment)
|
if (comment)
|
||||||
reqNewline = true;
|
reqNewline = true;
|
||||||
let str = stringify.stringify(item, itemCtx, () => (comment = null));
|
let str = stringify.stringify(item, itemCtx, () => (comment = null));
|
||||||
if (i < items.length - 1)
|
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes('\n'));
|
||||||
|
if (i < items.length - 1) {
|
||||||
str += ',';
|
str += ',';
|
||||||
|
}
|
||||||
|
else if (ctx.options.trailingComma) {
|
||||||
|
if (ctx.options.lineWidth > 0) {
|
||||||
|
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) +
|
||||||
|
(str.length + 2) >
|
||||||
|
ctx.options.lineWidth);
|
||||||
|
}
|
||||||
|
if (reqNewline) {
|
||||||
|
str += ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
if (comment)
|
if (comment)
|
||||||
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
||||||
if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
|
|
||||||
reqNewline = true;
|
|
||||||
lines.push(str);
|
lines.push(str);
|
||||||
linesAtValue = lines.length;
|
linesAtValue = lines.length;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,8 @@ function stringifyNumber({ format, minFractionDigits, tag, value }) {
|
|||||||
if (!format &&
|
if (!format &&
|
||||||
minFractionDigits &&
|
minFractionDigits &&
|
||||||
(!tag || tag === 'tag:yaml.org,2002:float') &&
|
(!tag || tag === 'tag:yaml.org,2002:float') &&
|
||||||
/^\d/.test(n)) {
|
/^-?\d/.test(n) &&
|
||||||
|
!n.includes('e')) {
|
||||||
let i = n.indexOf('.');
|
let i = n.indexOf('.');
|
||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
i = n.length;
|
i = n.length;
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yaml",
|
"name": "yaml",
|
||||||
"version": "2.8.2",
|
"version": "2.9.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "Eemeli Aro <eemeli@gmail.com>",
|
"author": "Eemeli Aro <eemeli@gmail.com>",
|
||||||
"funding": "https://github.com/sponsors/eemeli",
|
"funding": "https://github.com/sponsors/eemeli",
|
||||||
|
|||||||
Reference in New Issue
Block a user