faf-taf-git 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +4 -4
- package/.github/workflows/{claude-code-review.yml.disabled → claude-code-review.yml} +1 -1
- package/.github/workflows/claude.yml +1 -1
- package/CHANGELOG.md +20 -1
- package/README.md +7 -5
- package/RECEIPT-FORMAT.md +90 -0
- package/action.yml +2 -2
- package/dist/cli.js +3 -53
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1016 -346
- package/dist/index.js.map +1 -1
- package/dist/parsers/index.d.ts +6 -9
- package/dist/parsers/index.d.ts.map +1 -1
- package/dist/parsers/index.js +28 -10
- package/dist/parsers/index.js.map +1 -1
- package/dist/parsers/wjttc.d.ts +38 -0
- package/dist/parsers/wjttc.d.ts.map +1 -0
- package/dist/parsers/wjttc.js +152 -0
- package/dist/parsers/wjttc.js.map +1 -0
- package/package.json +4 -1
- package/project.faf +1 -1
- package/src/cli.ts +4 -54
- package/src/index.ts +2 -2
- package/src/parsers/index.ts +28 -10
- package/src/parsers/wjttc.ts +160 -0
- package/tests/action-bundle.test.ts +34 -0
- package/tests/action-parse-error.test.ts +47 -0
- package/tests/wjttc-parser.test.ts +116 -0
package/dist/index.js
CHANGED
|
@@ -7245,7 +7245,13 @@ function processHeader (request, key, val) {
|
|
|
7245
7245
|
} else if (typeof val[i] === 'object') {
|
|
7246
7246
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
7247
7247
|
} else {
|
|
7248
|
-
|
|
7248
|
+
// Coerce primitives (and reject unsafe coercions such as functions
|
|
7249
|
+
// with a crafted toString/Symbol.toPrimitive).
|
|
7250
|
+
const str = `${val[i]}`
|
|
7251
|
+
if (!isValidHeaderValue(str)) {
|
|
7252
|
+
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
7253
|
+
}
|
|
7254
|
+
arr.push(str)
|
|
7249
7255
|
}
|
|
7250
7256
|
}
|
|
7251
7257
|
val = arr
|
|
@@ -7256,7 +7262,12 @@ function processHeader (request, key, val) {
|
|
|
7256
7262
|
} else if (val === null) {
|
|
7257
7263
|
val = ''
|
|
7258
7264
|
} else {
|
|
7265
|
+
// Coerce primitives (and reject unsafe coercions such as functions
|
|
7266
|
+
// with a crafted toString/Symbol.toPrimitive).
|
|
7259
7267
|
val = `${val}`
|
|
7268
|
+
if (!isValidHeaderValue(val)) {
|
|
7269
|
+
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
7270
|
+
}
|
|
7260
7271
|
}
|
|
7261
7272
|
|
|
7262
7273
|
if (headerName === 'host') {
|
|
@@ -8293,8 +8304,6 @@ function defaultFactory (origin, opts) {
|
|
|
8293
8304
|
|
|
8294
8305
|
class Agent extends DispatcherBase {
|
|
8295
8306
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
|
8296
|
-
super()
|
|
8297
|
-
|
|
8298
8307
|
if (typeof factory !== 'function') {
|
|
8299
8308
|
throw new InvalidArgumentError('factory must be a function.')
|
|
8300
8309
|
}
|
|
@@ -8307,6 +8316,8 @@ class Agent extends DispatcherBase {
|
|
|
8307
8316
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
8308
8317
|
}
|
|
8309
8318
|
|
|
8319
|
+
super(options)
|
|
8320
|
+
|
|
8310
8321
|
if (connect && typeof connect !== 'function') {
|
|
8311
8322
|
connect = { ...connect }
|
|
8312
8323
|
}
|
|
@@ -8633,6 +8644,7 @@ const {
|
|
|
8633
8644
|
RequestContentLengthMismatchError,
|
|
8634
8645
|
ResponseContentLengthMismatchError,
|
|
8635
8646
|
RequestAbortedError,
|
|
8647
|
+
InvalidArgumentError,
|
|
8636
8648
|
HeadersTimeoutError,
|
|
8637
8649
|
HeadersOverflowError,
|
|
8638
8650
|
SocketError,
|
|
@@ -8680,6 +8692,9 @@ const EMPTY_BUF = Buffer.alloc(0)
|
|
|
8680
8692
|
const FastBuffer = Buffer[Symbol.species]
|
|
8681
8693
|
const addListener = util.addListener
|
|
8682
8694
|
const removeAllListeners = util.removeAllListeners
|
|
8695
|
+
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
|
|
8696
|
+
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
|
|
8697
|
+
const kSocketUsed = Symbol('kSocketUsed')
|
|
8683
8698
|
|
|
8684
8699
|
let extractBody
|
|
8685
8700
|
|
|
@@ -8902,29 +8917,71 @@ class Parser {
|
|
|
8902
8917
|
|
|
8903
8918
|
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
|
8904
8919
|
|
|
8905
|
-
if (ret
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
message =
|
|
8917
|
-
'Response does not match the HTTP/1.1 protocol (' +
|
|
8918
|
-
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
|
8919
|
-
')'
|
|
8920
|
-
}
|
|
8921
|
-
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
|
8920
|
+
if (ret !== constants.ERROR.OK) {
|
|
8921
|
+
const body = data.subarray(offset)
|
|
8922
|
+
|
|
8923
|
+
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
|
8924
|
+
this.onUpgrade(body)
|
|
8925
|
+
} else if (ret === constants.ERROR.PAUSED) {
|
|
8926
|
+
this.paused = true
|
|
8927
|
+
socket.unshift(body)
|
|
8928
|
+
} else {
|
|
8929
|
+
throw this.createError(ret, body)
|
|
8930
|
+
}
|
|
8922
8931
|
}
|
|
8923
8932
|
} catch (err) {
|
|
8924
8933
|
util.destroy(socket, err)
|
|
8925
8934
|
}
|
|
8926
8935
|
}
|
|
8927
8936
|
|
|
8937
|
+
finish () {
|
|
8938
|
+
assert(currentParser === null)
|
|
8939
|
+
assert(this.ptr != null)
|
|
8940
|
+
assert(!this.paused)
|
|
8941
|
+
|
|
8942
|
+
const { llhttp } = this
|
|
8943
|
+
|
|
8944
|
+
let ret
|
|
8945
|
+
|
|
8946
|
+
try {
|
|
8947
|
+
currentParser = this
|
|
8948
|
+
ret = llhttp.llhttp_finish(this.ptr)
|
|
8949
|
+
} finally {
|
|
8950
|
+
currentParser = null
|
|
8951
|
+
}
|
|
8952
|
+
|
|
8953
|
+
if (ret === constants.ERROR.OK) {
|
|
8954
|
+
return null
|
|
8955
|
+
}
|
|
8956
|
+
|
|
8957
|
+
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
|
|
8958
|
+
this.paused = true
|
|
8959
|
+
return null
|
|
8960
|
+
}
|
|
8961
|
+
|
|
8962
|
+
return this.createError(ret, EMPTY_BUF)
|
|
8963
|
+
}
|
|
8964
|
+
|
|
8965
|
+
createError (ret, data) {
|
|
8966
|
+
const { llhttp, contentLength, bytesRead } = this
|
|
8967
|
+
|
|
8968
|
+
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
|
8969
|
+
return new ResponseContentLengthMismatchError()
|
|
8970
|
+
}
|
|
8971
|
+
|
|
8972
|
+
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
|
8973
|
+
let message = ''
|
|
8974
|
+
if (ptr) {
|
|
8975
|
+
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
|
8976
|
+
message =
|
|
8977
|
+
'Response does not match the HTTP/1.1 protocol (' +
|
|
8978
|
+
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
|
8979
|
+
')'
|
|
8980
|
+
}
|
|
8981
|
+
|
|
8982
|
+
return new HTTPParserError(message, constants.ERROR[ret], data)
|
|
8983
|
+
}
|
|
8984
|
+
|
|
8928
8985
|
destroy () {
|
|
8929
8986
|
assert(this.ptr != null)
|
|
8930
8987
|
assert(currentParser == null)
|
|
@@ -8952,6 +9009,11 @@ class Parser {
|
|
|
8952
9009
|
return -1
|
|
8953
9010
|
}
|
|
8954
9011
|
|
|
9012
|
+
if (client[kRunning] === 0) {
|
|
9013
|
+
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
|
9014
|
+
return -1
|
|
9015
|
+
}
|
|
9016
|
+
|
|
8955
9017
|
const request = client[kQueue][client[kRunningIdx]]
|
|
8956
9018
|
if (!request) {
|
|
8957
9019
|
return -1
|
|
@@ -9055,6 +9117,11 @@ class Parser {
|
|
|
9055
9117
|
return -1
|
|
9056
9118
|
}
|
|
9057
9119
|
|
|
9120
|
+
if (client[kRunning] === 0) {
|
|
9121
|
+
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
|
9122
|
+
return -1
|
|
9123
|
+
}
|
|
9124
|
+
|
|
9058
9125
|
const request = client[kQueue][client[kRunningIdx]]
|
|
9059
9126
|
|
|
9060
9127
|
/* istanbul ignore next: difficult to make a test case for */
|
|
@@ -9228,6 +9295,7 @@ class Parser {
|
|
|
9228
9295
|
request.onComplete(headers)
|
|
9229
9296
|
|
|
9230
9297
|
client[kQueue][client[kRunningIdx]++] = null
|
|
9298
|
+
socket[kSocketUsed] = true
|
|
9231
9299
|
|
|
9232
9300
|
if (socket[kWriting]) {
|
|
9233
9301
|
assert(client[kRunning] === 0)
|
|
@@ -9286,6 +9354,9 @@ async function connectH1 (client, socket) {
|
|
|
9286
9354
|
socket[kWriting] = false
|
|
9287
9355
|
socket[kReset] = false
|
|
9288
9356
|
socket[kBlocking] = false
|
|
9357
|
+
socket[kIdleSocketValidation] = 0
|
|
9358
|
+
socket[kIdleSocketValidationTimeout] = null
|
|
9359
|
+
socket[kSocketUsed] = false
|
|
9289
9360
|
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
|
9290
9361
|
|
|
9291
9362
|
addListener(socket, 'error', function (err) {
|
|
@@ -9296,8 +9367,11 @@ async function connectH1 (client, socket) {
|
|
|
9296
9367
|
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
|
9297
9368
|
// to the user.
|
|
9298
9369
|
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
|
9299
|
-
|
|
9300
|
-
|
|
9370
|
+
const parserErr = parser.finish()
|
|
9371
|
+
if (parserErr) {
|
|
9372
|
+
this[kError] = parserErr
|
|
9373
|
+
this[kClient][kOnError](parserErr)
|
|
9374
|
+
}
|
|
9301
9375
|
return
|
|
9302
9376
|
}
|
|
9303
9377
|
|
|
@@ -9316,8 +9390,10 @@ async function connectH1 (client, socket) {
|
|
|
9316
9390
|
const parser = this[kParser]
|
|
9317
9391
|
|
|
9318
9392
|
if (parser.statusCode && !parser.shouldKeepAlive) {
|
|
9319
|
-
|
|
9320
|
-
|
|
9393
|
+
const parserErr = parser.finish()
|
|
9394
|
+
if (parserErr) {
|
|
9395
|
+
util.destroy(this, parserErr)
|
|
9396
|
+
}
|
|
9321
9397
|
return
|
|
9322
9398
|
}
|
|
9323
9399
|
|
|
@@ -9327,10 +9403,11 @@ async function connectH1 (client, socket) {
|
|
|
9327
9403
|
const client = this[kClient]
|
|
9328
9404
|
const parser = this[kParser]
|
|
9329
9405
|
|
|
9406
|
+
clearIdleSocketValidation(this)
|
|
9407
|
+
|
|
9330
9408
|
if (parser) {
|
|
9331
9409
|
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
|
9332
|
-
|
|
9333
|
-
parser.onMessageComplete()
|
|
9410
|
+
this[kError] = parser.finish() || this[kError]
|
|
9334
9411
|
}
|
|
9335
9412
|
|
|
9336
9413
|
this[kParser].destroy()
|
|
@@ -9393,7 +9470,7 @@ async function connectH1 (client, socket) {
|
|
|
9393
9470
|
return socket.destroyed
|
|
9394
9471
|
},
|
|
9395
9472
|
busy (request) {
|
|
9396
|
-
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
|
|
9473
|
+
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
|
|
9397
9474
|
return true
|
|
9398
9475
|
}
|
|
9399
9476
|
|
|
@@ -9431,6 +9508,39 @@ async function connectH1 (client, socket) {
|
|
|
9431
9508
|
}
|
|
9432
9509
|
}
|
|
9433
9510
|
|
|
9511
|
+
function clearIdleSocketValidation (socket) {
|
|
9512
|
+
if (socket[kIdleSocketValidationTimeout]) {
|
|
9513
|
+
clearImmediate(socket[kIdleSocketValidationTimeout])
|
|
9514
|
+
socket[kIdleSocketValidationTimeout] = null
|
|
9515
|
+
}
|
|
9516
|
+
|
|
9517
|
+
socket[kIdleSocketValidation] = 0
|
|
9518
|
+
}
|
|
9519
|
+
|
|
9520
|
+
function scheduleIdleSocketValidation (client, socket) {
|
|
9521
|
+
socket[kIdleSocketValidation] = 1
|
|
9522
|
+
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
|
|
9523
|
+
// already pending on this idle keep-alive socket are processed before the
|
|
9524
|
+
// next request is written (GHSA-35p6-xmwp-9g52).
|
|
9525
|
+
//
|
|
9526
|
+
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
|
|
9527
|
+
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
|
|
9528
|
+
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
|
|
9529
|
+
// A ref'd Immediate both keeps the pending request alive and makes poll
|
|
9530
|
+
// return immediately — the hybrid those issues asked for.
|
|
9531
|
+
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
|
|
9532
|
+
socket[kIdleSocketValidationTimeout] = null
|
|
9533
|
+
socket[kIdleSocketValidation] = 2
|
|
9534
|
+
|
|
9535
|
+
if (client[kSocket] === socket && !socket.destroyed) {
|
|
9536
|
+
client[kResume]()
|
|
9537
|
+
}
|
|
9538
|
+
})
|
|
9539
|
+
}
|
|
9540
|
+
|
|
9541
|
+
/**
|
|
9542
|
+
* @param {import('./client.js')} client
|
|
9543
|
+
*/
|
|
9434
9544
|
function resumeH1 (client) {
|
|
9435
9545
|
const socket = client[kSocket]
|
|
9436
9546
|
|
|
@@ -9445,6 +9555,32 @@ function resumeH1 (client) {
|
|
|
9445
9555
|
socket[kNoRef] = false
|
|
9446
9556
|
}
|
|
9447
9557
|
|
|
9558
|
+
if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
|
|
9559
|
+
if (socket[kIdleSocketValidation] === 0) {
|
|
9560
|
+
scheduleIdleSocketValidation(client, socket)
|
|
9561
|
+
socket[kParser].readMore()
|
|
9562
|
+
if (socket.destroyed) {
|
|
9563
|
+
return
|
|
9564
|
+
}
|
|
9565
|
+
return
|
|
9566
|
+
}
|
|
9567
|
+
|
|
9568
|
+
if (socket[kIdleSocketValidation] === 1) {
|
|
9569
|
+
socket[kParser].readMore()
|
|
9570
|
+
if (socket.destroyed) {
|
|
9571
|
+
return
|
|
9572
|
+
}
|
|
9573
|
+
return
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
|
|
9577
|
+
if (client[kRunning] === 0) {
|
|
9578
|
+
socket[kParser].readMore()
|
|
9579
|
+
if (socket.destroyed) {
|
|
9580
|
+
return
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9583
|
+
|
|
9448
9584
|
if (client[kSize] === 0) {
|
|
9449
9585
|
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
|
9450
9586
|
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
|
@@ -9500,8 +9636,16 @@ function writeH1 (client, request) {
|
|
|
9500
9636
|
}
|
|
9501
9637
|
body = bodyStream.stream
|
|
9502
9638
|
contentLength = bodyStream.length
|
|
9503
|
-
} else if (util.isBlobLike(body) && request.contentType == null
|
|
9504
|
-
|
|
9639
|
+
} else if (util.isBlobLike(body) && request.contentType == null) {
|
|
9640
|
+
const contentType = body.type
|
|
9641
|
+
if (contentType) {
|
|
9642
|
+
const contentTypeValue = `${contentType}`
|
|
9643
|
+
if (!util.isValidHeaderValue(contentTypeValue)) {
|
|
9644
|
+
util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'))
|
|
9645
|
+
return false
|
|
9646
|
+
}
|
|
9647
|
+
headers.push('content-type', contentTypeValue)
|
|
9648
|
+
}
|
|
9505
9649
|
}
|
|
9506
9650
|
|
|
9507
9651
|
if (body && typeof body.read === 'function') {
|
|
@@ -9538,6 +9682,7 @@ function writeH1 (client, request) {
|
|
|
9538
9682
|
}
|
|
9539
9683
|
|
|
9540
9684
|
const socket = client[kSocket]
|
|
9685
|
+
clearIdleSocketValidation(socket)
|
|
9541
9686
|
|
|
9542
9687
|
const abort = (err) => {
|
|
9543
9688
|
if (request.aborted || request.completed) {
|
|
@@ -10859,9 +11004,10 @@ class Client extends DispatcherBase {
|
|
|
10859
11004
|
autoSelectFamilyAttemptTimeout,
|
|
10860
11005
|
// h2
|
|
10861
11006
|
maxConcurrentStreams,
|
|
10862
|
-
allowH2
|
|
11007
|
+
allowH2,
|
|
11008
|
+
webSocket
|
|
10863
11009
|
} = {}) {
|
|
10864
|
-
super()
|
|
11010
|
+
super({ webSocket })
|
|
10865
11011
|
|
|
10866
11012
|
if (keepAlive !== undefined) {
|
|
10867
11013
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
|
@@ -11394,15 +11540,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
|
|
|
11394
11540
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
11395
11541
|
const kOnClosed = Symbol('onClosed')
|
|
11396
11542
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
|
11543
|
+
const kWebSocketOptions = Symbol('webSocketOptions')
|
|
11397
11544
|
|
|
11398
11545
|
class DispatcherBase extends Dispatcher {
|
|
11399
|
-
constructor () {
|
|
11546
|
+
constructor (opts) {
|
|
11400
11547
|
super()
|
|
11401
11548
|
|
|
11402
11549
|
this[kDestroyed] = false
|
|
11403
11550
|
this[kOnDestroyed] = null
|
|
11404
11551
|
this[kClosed] = false
|
|
11405
11552
|
this[kOnClosed] = []
|
|
11553
|
+
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
|
11554
|
+
}
|
|
11555
|
+
|
|
11556
|
+
get webSocketOptions () {
|
|
11557
|
+
return {
|
|
11558
|
+
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
|
11559
|
+
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
|
11560
|
+
}
|
|
11406
11561
|
}
|
|
11407
11562
|
|
|
11408
11563
|
get destroyed () {
|
|
@@ -11966,8 +12121,8 @@ const kRemoveClient = Symbol('remove client')
|
|
|
11966
12121
|
const kStats = Symbol('stats')
|
|
11967
12122
|
|
|
11968
12123
|
class PoolBase extends DispatcherBase {
|
|
11969
|
-
constructor () {
|
|
11970
|
-
super()
|
|
12124
|
+
constructor (opts) {
|
|
12125
|
+
super(opts)
|
|
11971
12126
|
|
|
11972
12127
|
this[kQueue] = new FixedQueue()
|
|
11973
12128
|
this[kClients] = []
|
|
@@ -12227,8 +12382,6 @@ class Pool extends PoolBase {
|
|
|
12227
12382
|
allowH2,
|
|
12228
12383
|
...options
|
|
12229
12384
|
} = {}) {
|
|
12230
|
-
super()
|
|
12231
|
-
|
|
12232
12385
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
|
12233
12386
|
throw new InvalidArgumentError('invalid connections')
|
|
12234
12387
|
}
|
|
@@ -12253,6 +12406,8 @@ class Pool extends PoolBase {
|
|
|
12253
12406
|
})
|
|
12254
12407
|
}
|
|
12255
12408
|
|
|
12409
|
+
super(options)
|
|
12410
|
+
|
|
12256
12411
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
|
12257
12412
|
? options.interceptors.Pool
|
|
12258
12413
|
: []
|
|
@@ -12977,6 +13132,28 @@ function calculateRetryAfterHeader (retryAfter) {
|
|
|
12977
13132
|
return new Date(retryAfter).getTime() - current
|
|
12978
13133
|
}
|
|
12979
13134
|
|
|
13135
|
+
function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
|
|
13136
|
+
const contentLength = headers['content-length']
|
|
13137
|
+
if (contentLength == null) {
|
|
13138
|
+
return null
|
|
13139
|
+
}
|
|
13140
|
+
|
|
13141
|
+
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
|
|
13142
|
+
return null
|
|
13143
|
+
}
|
|
13144
|
+
|
|
13145
|
+
const length = Number(contentLength)
|
|
13146
|
+
const expectedLength = range.end - range.start + 1
|
|
13147
|
+
if (!Number.isFinite(length) || length !== expectedLength) {
|
|
13148
|
+
return new RequestRetryError('Content-Length mismatch', statusCode, {
|
|
13149
|
+
headers,
|
|
13150
|
+
data: { count: retryCount }
|
|
13151
|
+
})
|
|
13152
|
+
}
|
|
13153
|
+
|
|
13154
|
+
return null
|
|
13155
|
+
}
|
|
13156
|
+
|
|
12980
13157
|
class RetryHandler {
|
|
12981
13158
|
constructor (opts, handlers) {
|
|
12982
13159
|
const { retryOptions, ...dispatchOpts } = opts
|
|
@@ -13030,6 +13207,7 @@ class RetryHandler {
|
|
|
13030
13207
|
this.end = null
|
|
13031
13208
|
this.etag = null
|
|
13032
13209
|
this.resume = null
|
|
13210
|
+
this.headersSent = false
|
|
13033
13211
|
|
|
13034
13212
|
// Handle possible onConnect duplication
|
|
13035
13213
|
this.handler.onConnect(reason => {
|
|
@@ -13042,6 +13220,20 @@ class RetryHandler {
|
|
|
13042
13220
|
})
|
|
13043
13221
|
}
|
|
13044
13222
|
|
|
13223
|
+
checkpointResponseEnd (headers, resume) {
|
|
13224
|
+
if (this.end == null && this.opts.method !== 'HEAD') {
|
|
13225
|
+
const contentLength = headers['content-length']
|
|
13226
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
13227
|
+
|
|
13228
|
+
assert(
|
|
13229
|
+
this.end == null || Number.isFinite(this.end),
|
|
13230
|
+
'invalid content-length'
|
|
13231
|
+
)
|
|
13232
|
+
}
|
|
13233
|
+
|
|
13234
|
+
this.resume = this.end != null ? resume : null
|
|
13235
|
+
}
|
|
13236
|
+
|
|
13045
13237
|
onRequestSent () {
|
|
13046
13238
|
if (this.handler.onRequestSent) {
|
|
13047
13239
|
this.handler.onRequestSent()
|
|
@@ -13131,6 +13323,8 @@ class RetryHandler {
|
|
|
13131
13323
|
|
|
13132
13324
|
if (statusCode >= 300) {
|
|
13133
13325
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
13326
|
+
this.headersSent = true
|
|
13327
|
+
this.checkpointResponseEnd(headers, resume)
|
|
13134
13328
|
return this.handler.onHeaders(
|
|
13135
13329
|
statusCode,
|
|
13136
13330
|
rawHeaders,
|
|
@@ -13191,10 +13385,23 @@ class RetryHandler {
|
|
|
13191
13385
|
return false
|
|
13192
13386
|
}
|
|
13193
13387
|
|
|
13388
|
+
const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
|
|
13389
|
+
if (contentLengthError != null) {
|
|
13390
|
+
this.abort(contentLengthError)
|
|
13391
|
+
return false
|
|
13392
|
+
}
|
|
13393
|
+
|
|
13194
13394
|
const { start, size, end = size - 1 } = contentRange
|
|
13195
13395
|
|
|
13196
|
-
|
|
13197
|
-
|
|
13396
|
+
if (this.start !== start || (this.end != null && this.end !== end)) {
|
|
13397
|
+
this.abort(
|
|
13398
|
+
new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
13399
|
+
headers,
|
|
13400
|
+
data: { count: this.retryCount }
|
|
13401
|
+
})
|
|
13402
|
+
)
|
|
13403
|
+
return false
|
|
13404
|
+
}
|
|
13198
13405
|
|
|
13199
13406
|
this.resume = resume
|
|
13200
13407
|
return true
|
|
@@ -13206,6 +13413,7 @@ class RetryHandler {
|
|
|
13206
13413
|
const range = parseRangeHeader(headers['content-range'])
|
|
13207
13414
|
|
|
13208
13415
|
if (range == null) {
|
|
13416
|
+
this.headersSent = true
|
|
13209
13417
|
return this.handler.onHeaders(
|
|
13210
13418
|
statusCode,
|
|
13211
13419
|
rawHeaders,
|
|
@@ -13214,6 +13422,12 @@ class RetryHandler {
|
|
|
13214
13422
|
)
|
|
13215
13423
|
}
|
|
13216
13424
|
|
|
13425
|
+
const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
|
|
13426
|
+
if (contentLengthError != null) {
|
|
13427
|
+
this.abort(contentLengthError)
|
|
13428
|
+
return false
|
|
13429
|
+
}
|
|
13430
|
+
|
|
13217
13431
|
const { start, size, end = size - 1 } = range
|
|
13218
13432
|
assert(
|
|
13219
13433
|
start != null && Number.isFinite(start),
|
|
@@ -13238,6 +13452,7 @@ class RetryHandler {
|
|
|
13238
13452
|
)
|
|
13239
13453
|
|
|
13240
13454
|
this.resume = resume
|
|
13455
|
+
this.headersSent = true
|
|
13241
13456
|
this.etag = headers.etag != null ? headers.etag : null
|
|
13242
13457
|
|
|
13243
13458
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -13277,7 +13492,7 @@ class RetryHandler {
|
|
|
13277
13492
|
}
|
|
13278
13493
|
|
|
13279
13494
|
onError (err) {
|
|
13280
|
-
if (this.aborted || isDisturbed(this.opts.body)) {
|
|
13495
|
+
if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) {
|
|
13281
13496
|
return this.handler.onError(err)
|
|
13282
13497
|
}
|
|
13283
13498
|
|
|
@@ -17337,32 +17552,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|
|
17337
17552
|
// If the attribute-name case-insensitively matches the string
|
|
17338
17553
|
// "SameSite", the user agent MUST process the cookie-av as follows:
|
|
17339
17554
|
|
|
17340
|
-
// 1. Let enforcement be "Default".
|
|
17341
|
-
let enforcement = 'Default'
|
|
17342
|
-
|
|
17343
17555
|
const attributeValueLowercase = attributeValue.toLowerCase()
|
|
17344
|
-
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
|
17345
|
-
// "None", set enforcement to "None".
|
|
17346
|
-
if (attributeValueLowercase.includes('none')) {
|
|
17347
|
-
enforcement = 'None'
|
|
17348
|
-
}
|
|
17349
|
-
|
|
17350
|
-
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
|
17351
|
-
// "Strict", set enforcement to "Strict".
|
|
17352
|
-
if (attributeValueLowercase.includes('strict')) {
|
|
17353
|
-
enforcement = 'Strict'
|
|
17354
|
-
}
|
|
17355
17556
|
|
|
17356
|
-
//
|
|
17357
|
-
// "
|
|
17358
|
-
|
|
17359
|
-
|
|
17557
|
+
// 1. If cookie-av's attribute-value is a case-insensitive match for
|
|
17558
|
+
// "None", append an attribute to the cookie-attribute-list with an
|
|
17559
|
+
// attribute-name of "SameSite" and an attribute-value of "None".
|
|
17560
|
+
if (attributeValueLowercase === 'none') {
|
|
17561
|
+
cookieAttributeList.sameSite = 'None'
|
|
17562
|
+
} else if (attributeValueLowercase === 'strict') {
|
|
17563
|
+
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
|
17564
|
+
// "Strict", append an attribute to the cookie-attribute-list with
|
|
17565
|
+
// an attribute-name of "SameSite" and an attribute-value of
|
|
17566
|
+
// "Strict".
|
|
17567
|
+
cookieAttributeList.sameSite = 'Strict'
|
|
17568
|
+
} else if (attributeValueLowercase === 'lax') {
|
|
17569
|
+
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
|
17570
|
+
// "Lax", append an attribute to the cookie-attribute-list with an
|
|
17571
|
+
// attribute-name of "SameSite" and an attribute-value of "Lax".
|
|
17572
|
+
cookieAttributeList.sameSite = 'Lax'
|
|
17360
17573
|
}
|
|
17361
|
-
|
|
17362
|
-
// 5. Append an attribute to the cookie-attribute-list with an
|
|
17363
|
-
// attribute-name of "SameSite" and an attribute-value of
|
|
17364
|
-
// enforcement.
|
|
17365
|
-
cookieAttributeList.sameSite = enforcement
|
|
17366
17574
|
} else {
|
|
17367
17575
|
cookieAttributeList.unparsed ??= []
|
|
17368
17576
|
|
|
@@ -17492,7 +17700,7 @@ function validateCookiePath (path) {
|
|
|
17492
17700
|
|
|
17493
17701
|
if (
|
|
17494
17702
|
code < 0x20 || // exclude CTLs (0-31)
|
|
17495
|
-
code
|
|
17703
|
+
code > 0x7E || // exclude DEL and non-ascii
|
|
17496
17704
|
code === 0x3B // ;
|
|
17497
17705
|
) {
|
|
17498
17706
|
throw new Error('Invalid cookie path')
|
|
@@ -17501,16 +17709,80 @@ function validateCookiePath (path) {
|
|
|
17501
17709
|
}
|
|
17502
17710
|
|
|
17503
17711
|
/**
|
|
17504
|
-
*
|
|
17505
|
-
*
|
|
17712
|
+
* <let-dig> ::= <letter> | <digit>
|
|
17713
|
+
*
|
|
17714
|
+
* <letter> ::= any one of the 52 alphabetic characters A through Z in
|
|
17715
|
+
* upper case and a through z in lower case
|
|
17716
|
+
*
|
|
17717
|
+
* <digit> ::= any one of the ten digits 0 through 9r
|
|
17718
|
+
*
|
|
17719
|
+
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
|
|
17720
|
+
* @param {number} code
|
|
17721
|
+
*/
|
|
17722
|
+
function isLetterOrDigit (code) {
|
|
17723
|
+
return (
|
|
17724
|
+
(code >= 0x30 && code <= 0x39) || // 0-9
|
|
17725
|
+
(code >= 0x41 && code <= 0x5A) || // A-Z
|
|
17726
|
+
(code >= 0x61 && code <= 0x7A) // a-z
|
|
17727
|
+
)
|
|
17728
|
+
}
|
|
17729
|
+
|
|
17730
|
+
/**
|
|
17731
|
+
* Validates a cookie domain against the "preferred name syntax".
|
|
17732
|
+
*
|
|
17733
|
+
* <domain> ::= <subdomain> | " "
|
|
17734
|
+
* <subdomain> ::= <label> | <subdomain> "." <label>
|
|
17735
|
+
* <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
|
17736
|
+
* <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
|
17737
|
+
* <let-dig-hyp> ::= <let-dig> | "-"
|
|
17738
|
+
*
|
|
17739
|
+
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
|
|
17740
|
+
* @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
|
|
17741
|
+
* @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
|
|
17506
17742
|
* @param {string} domain
|
|
17507
17743
|
*/
|
|
17508
17744
|
function validateCookieDomain (domain) {
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17745
|
+
// <domain> ::= <subdomain> | " "
|
|
17746
|
+
if (domain === ' ') {
|
|
17747
|
+
return
|
|
17748
|
+
}
|
|
17749
|
+
|
|
17750
|
+
if (domain.length > 255) {
|
|
17751
|
+
throw new Error('Invalid cookie domain')
|
|
17752
|
+
}
|
|
17753
|
+
|
|
17754
|
+
let labelLength = 0
|
|
17755
|
+
|
|
17756
|
+
for (let i = 0; i < domain.length; ++i) {
|
|
17757
|
+
const code = domain.charCodeAt(i)
|
|
17758
|
+
|
|
17759
|
+
if (code === 0x2E) {
|
|
17760
|
+
if (labelLength === 0) {
|
|
17761
|
+
throw new Error('Invalid cookie domain')
|
|
17762
|
+
}
|
|
17763
|
+
|
|
17764
|
+
if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
|
|
17765
|
+
throw new Error('Invalid cookie domain')
|
|
17766
|
+
}
|
|
17767
|
+
|
|
17768
|
+
labelLength = 0
|
|
17769
|
+
continue
|
|
17770
|
+
}
|
|
17771
|
+
|
|
17772
|
+
if (labelLength === 0 && !isLetterOrDigit(code)) {
|
|
17773
|
+
throw new Error('Invalid cookie domain')
|
|
17774
|
+
}
|
|
17775
|
+
|
|
17776
|
+
if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
|
|
17777
|
+
throw new Error('Invalid cookie domain')
|
|
17778
|
+
}
|
|
17779
|
+
|
|
17780
|
+
if (++labelLength > 63) {
|
|
17781
|
+
throw new Error('Invalid cookie domain')
|
|
17782
|
+
}
|
|
17783
|
+
}
|
|
17784
|
+
|
|
17785
|
+
if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
|
|
17514
17786
|
throw new Error('Invalid cookie domain')
|
|
17515
17787
|
}
|
|
17516
17788
|
}
|
|
@@ -17653,7 +17925,13 @@ function stringify (cookie) {
|
|
|
17653
17925
|
|
|
17654
17926
|
const [key, ...value] = part.split('=')
|
|
17655
17927
|
|
|
17656
|
-
|
|
17928
|
+
const trimmedKey = key.trim()
|
|
17929
|
+
const joinedValue = value.join('=')
|
|
17930
|
+
|
|
17931
|
+
validateCookieName(trimmedKey)
|
|
17932
|
+
validateCookieValue(joinedValue)
|
|
17933
|
+
|
|
17934
|
+
out.push(`${trimmedKey}=${joinedValue}`)
|
|
17657
17935
|
}
|
|
17658
17936
|
|
|
17659
17937
|
return out.join('; ')
|
|
@@ -17700,6 +17978,49 @@ const COLON = 0x3A
|
|
|
17700
17978
|
*/
|
|
17701
17979
|
const SPACE = 0x20
|
|
17702
17980
|
|
|
17981
|
+
const DATA = Buffer.from('data')
|
|
17982
|
+
const EVENT = Buffer.from('event')
|
|
17983
|
+
const ID = Buffer.from('id')
|
|
17984
|
+
const RETRY = Buffer.from('retry')
|
|
17985
|
+
|
|
17986
|
+
function isASCIINumberBytes (buffer, start) {
|
|
17987
|
+
if (start >= buffer.length) {
|
|
17988
|
+
return false
|
|
17989
|
+
}
|
|
17990
|
+
|
|
17991
|
+
for (let i = start; i < buffer.length; i++) {
|
|
17992
|
+
if (buffer[i] < 0x30 || buffer[i] > 0x39) {
|
|
17993
|
+
return false
|
|
17994
|
+
}
|
|
17995
|
+
}
|
|
17996
|
+
|
|
17997
|
+
return true
|
|
17998
|
+
}
|
|
17999
|
+
|
|
18000
|
+
function isValidLastEventIdBytes (buffer, start) {
|
|
18001
|
+
for (let i = start; i < buffer.length; i++) {
|
|
18002
|
+
if (buffer[i] === 0x00) {
|
|
18003
|
+
return false
|
|
18004
|
+
}
|
|
18005
|
+
}
|
|
18006
|
+
|
|
18007
|
+
return true
|
|
18008
|
+
}
|
|
18009
|
+
|
|
18010
|
+
function isFieldName (line, length, field) {
|
|
18011
|
+
if (length !== field.length) {
|
|
18012
|
+
return false
|
|
18013
|
+
}
|
|
18014
|
+
|
|
18015
|
+
for (let i = 0; i < length; i++) {
|
|
18016
|
+
if (line[i] !== field[i]) {
|
|
18017
|
+
return false
|
|
18018
|
+
}
|
|
18019
|
+
}
|
|
18020
|
+
|
|
18021
|
+
return true
|
|
18022
|
+
}
|
|
18023
|
+
|
|
17703
18024
|
/**
|
|
17704
18025
|
* @typedef {object} EventSourceStreamEvent
|
|
17705
18026
|
* @type {object}
|
|
@@ -17740,11 +18061,14 @@ class EventSourceStream extends Transform {
|
|
|
17740
18061
|
eventEndCheck = false
|
|
17741
18062
|
|
|
17742
18063
|
/**
|
|
17743
|
-
* @type {Buffer}
|
|
18064
|
+
* @type {Buffer[]}
|
|
17744
18065
|
*/
|
|
17745
|
-
|
|
18066
|
+
chunks = []
|
|
17746
18067
|
|
|
18068
|
+
chunkIndex = 0
|
|
17747
18069
|
pos = 0
|
|
18070
|
+
lineChunkIndex = 0
|
|
18071
|
+
linePos = 0
|
|
17748
18072
|
|
|
17749
18073
|
event = {
|
|
17750
18074
|
data: undefined,
|
|
@@ -17783,92 +18107,20 @@ class EventSourceStream extends Transform {
|
|
|
17783
18107
|
return
|
|
17784
18108
|
}
|
|
17785
18109
|
|
|
17786
|
-
|
|
17787
|
-
// processing it
|
|
17788
|
-
// TODO: Investigate if there is a more performant way to handle
|
|
17789
|
-
// incoming chunks
|
|
17790
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
17791
|
-
if (this.buffer) {
|
|
17792
|
-
this.buffer = Buffer.concat([this.buffer, chunk])
|
|
17793
|
-
} else {
|
|
17794
|
-
this.buffer = chunk
|
|
17795
|
-
}
|
|
18110
|
+
this.chunks.push(chunk)
|
|
17796
18111
|
|
|
17797
18112
|
// Strip leading byte-order-mark if we opened the stream and started
|
|
17798
18113
|
// the processing of the incoming data
|
|
17799
18114
|
if (this.checkBOM) {
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
if (this.buffer[0] === BOM[0]) {
|
|
17804
|
-
// If it is, we need to wait for more data
|
|
17805
|
-
callback()
|
|
17806
|
-
return
|
|
17807
|
-
}
|
|
17808
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
17809
|
-
// BOM anymore
|
|
17810
|
-
this.checkBOM = false
|
|
17811
|
-
|
|
17812
|
-
// The buffer only contains one byte so we need to wait for more data
|
|
17813
|
-
callback()
|
|
17814
|
-
return
|
|
17815
|
-
case 2:
|
|
17816
|
-
// Check if the first two bytes are the same as the first two bytes
|
|
17817
|
-
// of the BOM
|
|
17818
|
-
if (
|
|
17819
|
-
this.buffer[0] === BOM[0] &&
|
|
17820
|
-
this.buffer[1] === BOM[1]
|
|
17821
|
-
) {
|
|
17822
|
-
// If it is, we need to wait for more data, because the third byte
|
|
17823
|
-
// is needed to determine if it is the BOM or not
|
|
17824
|
-
callback()
|
|
17825
|
-
return
|
|
17826
|
-
}
|
|
17827
|
-
|
|
17828
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
17829
|
-
// BOM anymore
|
|
17830
|
-
this.checkBOM = false
|
|
17831
|
-
break
|
|
17832
|
-
case 3:
|
|
17833
|
-
// Check if the first three bytes are the same as the first three
|
|
17834
|
-
// bytes of the BOM
|
|
17835
|
-
if (
|
|
17836
|
-
this.buffer[0] === BOM[0] &&
|
|
17837
|
-
this.buffer[1] === BOM[1] &&
|
|
17838
|
-
this.buffer[2] === BOM[2]
|
|
17839
|
-
) {
|
|
17840
|
-
// If it is, we can drop the buffered data, as it is only the BOM
|
|
17841
|
-
this.buffer = Buffer.alloc(0)
|
|
17842
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
17843
|
-
// BOM anymore
|
|
17844
|
-
this.checkBOM = false
|
|
17845
|
-
|
|
17846
|
-
// Await more data
|
|
17847
|
-
callback()
|
|
17848
|
-
return
|
|
17849
|
-
}
|
|
17850
|
-
// If it is not the BOM, we can start processing the data
|
|
17851
|
-
this.checkBOM = false
|
|
17852
|
-
break
|
|
17853
|
-
default:
|
|
17854
|
-
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
|
|
17855
|
-
// present
|
|
17856
|
-
if (
|
|
17857
|
-
this.buffer[0] === BOM[0] &&
|
|
17858
|
-
this.buffer[1] === BOM[1] &&
|
|
17859
|
-
this.buffer[2] === BOM[2]
|
|
17860
|
-
) {
|
|
17861
|
-
// Remove the BOM from the buffer
|
|
17862
|
-
this.buffer = this.buffer.subarray(3)
|
|
17863
|
-
}
|
|
17864
|
-
|
|
17865
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
17866
|
-
this.checkBOM = false
|
|
17867
|
-
break
|
|
18115
|
+
if (this.handleBOM()) {
|
|
18116
|
+
callback()
|
|
18117
|
+
return
|
|
17868
18118
|
}
|
|
17869
18119
|
}
|
|
17870
18120
|
|
|
17871
|
-
while (this.
|
|
18121
|
+
while (this.hasCurrentByte()) {
|
|
18122
|
+
const byte = this.currentByte()
|
|
18123
|
+
|
|
17872
18124
|
// If the previous line ended with an end-of-line, we need to check
|
|
17873
18125
|
// if the next character is also an end-of-line.
|
|
17874
18126
|
if (this.eventEndCheck) {
|
|
@@ -17881,10 +18133,9 @@ class EventSourceStream extends Transform {
|
|
|
17881
18133
|
if (this.crlfCheck) {
|
|
17882
18134
|
// If the current character is a line feed, we can remove it
|
|
17883
18135
|
// from the buffer and reset the crlfCheck flag
|
|
17884
|
-
if (
|
|
17885
|
-
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
17886
|
-
this.pos = 0
|
|
18136
|
+
if (byte === LF) {
|
|
17887
18137
|
this.crlfCheck = false
|
|
18138
|
+
this.consumeCurrentByte()
|
|
17888
18139
|
|
|
17889
18140
|
// It is possible that the line feed is not the end of the
|
|
17890
18141
|
// event. We need to check if the next character is an
|
|
@@ -17900,19 +18151,17 @@ class EventSourceStream extends Transform {
|
|
|
17900
18151
|
this.crlfCheck = false
|
|
17901
18152
|
}
|
|
17902
18153
|
|
|
17903
|
-
if (
|
|
18154
|
+
if (byte === LF || byte === CR) {
|
|
17904
18155
|
// If the current character is a carriage return, we need to
|
|
17905
18156
|
// set the crlfCheck flag to true, as we need to check if the
|
|
17906
18157
|
// next character is a line feed so we can remove it from the
|
|
17907
18158
|
// buffer
|
|
17908
|
-
if (
|
|
18159
|
+
if (byte === CR) {
|
|
17909
18160
|
this.crlfCheck = true
|
|
17910
18161
|
}
|
|
17911
18162
|
|
|
17912
|
-
this.
|
|
17913
|
-
this.
|
|
17914
|
-
if (
|
|
17915
|
-
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
|
18163
|
+
this.consumeCurrentByte()
|
|
18164
|
+
if (this.hasPendingEvent()) {
|
|
17916
18165
|
this.processEvent(this.event)
|
|
17917
18166
|
}
|
|
17918
18167
|
this.clearEvent()
|
|
@@ -17926,22 +18175,18 @@ class EventSourceStream extends Transform {
|
|
|
17926
18175
|
|
|
17927
18176
|
// If the current character is an end-of-line, we can process the
|
|
17928
18177
|
// line
|
|
17929
|
-
if (
|
|
18178
|
+
if (byte === LF || byte === CR) {
|
|
17930
18179
|
// If the current character is a carriage return, we need to
|
|
17931
18180
|
// set the crlfCheck flag to true, as we need to check if the
|
|
17932
18181
|
// next character is a line feed
|
|
17933
|
-
if (
|
|
18182
|
+
if (byte === CR) {
|
|
17934
18183
|
this.crlfCheck = true
|
|
17935
18184
|
}
|
|
17936
18185
|
|
|
17937
18186
|
// In any case, we can process the line as we reached an
|
|
17938
18187
|
// end-of-line character
|
|
17939
|
-
this.parseLine(this.
|
|
17940
|
-
|
|
17941
|
-
// Remove the processed line from the buffer
|
|
17942
|
-
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
17943
|
-
// Reset the position as we removed the processed line from the buffer
|
|
17944
|
-
this.pos = 0
|
|
18188
|
+
this.parseLine(this.readLine(), this.event)
|
|
18189
|
+
this.consumeCurrentByte()
|
|
17945
18190
|
// A line was processed and this could be the end of the event. We need
|
|
17946
18191
|
// to check if the next line is empty to determine if the event is
|
|
17947
18192
|
// finished.
|
|
@@ -17949,7 +18194,7 @@ class EventSourceStream extends Transform {
|
|
|
17949
18194
|
continue
|
|
17950
18195
|
}
|
|
17951
18196
|
|
|
17952
|
-
this.
|
|
18197
|
+
this.advanceCursor()
|
|
17953
18198
|
}
|
|
17954
18199
|
|
|
17955
18200
|
callback()
|
|
@@ -17974,64 +18219,53 @@ class EventSourceStream extends Transform {
|
|
|
17974
18219
|
return
|
|
17975
18220
|
}
|
|
17976
18221
|
|
|
17977
|
-
let
|
|
17978
|
-
let
|
|
18222
|
+
let fieldLength = line.length
|
|
18223
|
+
let valueStart = line.length
|
|
17979
18224
|
|
|
17980
18225
|
// If the line contains a U+003A COLON character (:)
|
|
17981
18226
|
if (colonPosition !== -1) {
|
|
17982
|
-
|
|
17983
|
-
// character (:), and let field be that string.
|
|
17984
|
-
// TODO: Investigate if there is a more performant way to extract the
|
|
17985
|
-
// field
|
|
17986
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
17987
|
-
field = line.subarray(0, colonPosition).toString('utf8')
|
|
18227
|
+
fieldLength = colonPosition
|
|
17988
18228
|
|
|
17989
18229
|
// Collect the characters on the line after the first U+003A COLON
|
|
17990
18230
|
// character (:), and let value be that string.
|
|
17991
18231
|
// If value starts with a U+0020 SPACE character, remove it from value.
|
|
17992
|
-
|
|
18232
|
+
valueStart = colonPosition + 1
|
|
17993
18233
|
if (line[valueStart] === SPACE) {
|
|
17994
18234
|
++valueStart
|
|
17995
18235
|
}
|
|
17996
|
-
|
|
17997
|
-
// value
|
|
17998
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
17999
|
-
value = line.subarray(valueStart).toString('utf8')
|
|
18236
|
+
}
|
|
18000
18237
|
|
|
18001
|
-
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
18028
|
-
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
event[field] = value
|
|
18033
|
-
}
|
|
18034
|
-
break
|
|
18238
|
+
if (isFieldName(line, fieldLength, DATA)) {
|
|
18239
|
+
const value = line.toString('utf8', valueStart)
|
|
18240
|
+
|
|
18241
|
+
if (event.data === undefined) {
|
|
18242
|
+
event.data = value
|
|
18243
|
+
} else {
|
|
18244
|
+
event.data += `\n${value}`
|
|
18245
|
+
}
|
|
18246
|
+
return
|
|
18247
|
+
}
|
|
18248
|
+
|
|
18249
|
+
if (isFieldName(line, fieldLength, RETRY)) {
|
|
18250
|
+
if (isASCIINumberBytes(line, valueStart)) {
|
|
18251
|
+
event.retry = line.toString('utf8', valueStart)
|
|
18252
|
+
}
|
|
18253
|
+
return
|
|
18254
|
+
}
|
|
18255
|
+
|
|
18256
|
+
if (isFieldName(line, fieldLength, ID)) {
|
|
18257
|
+
if (isValidLastEventIdBytes(line, valueStart)) {
|
|
18258
|
+
event.id = line.toString('utf8', valueStart)
|
|
18259
|
+
}
|
|
18260
|
+
return
|
|
18261
|
+
}
|
|
18262
|
+
|
|
18263
|
+
if (isFieldName(line, fieldLength, EVENT)) {
|
|
18264
|
+
const value = line.toString('utf8', valueStart)
|
|
18265
|
+
|
|
18266
|
+
if (value.length > 0) {
|
|
18267
|
+
event.event = value
|
|
18268
|
+
}
|
|
18035
18269
|
}
|
|
18036
18270
|
}
|
|
18037
18271
|
|
|
@@ -18061,12 +18295,151 @@ class EventSourceStream extends Transform {
|
|
|
18061
18295
|
}
|
|
18062
18296
|
|
|
18063
18297
|
clearEvent () {
|
|
18064
|
-
this.event =
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18298
|
+
this.event.data = undefined
|
|
18299
|
+
this.event.event = undefined
|
|
18300
|
+
this.event.id = undefined
|
|
18301
|
+
this.event.retry = undefined
|
|
18302
|
+
}
|
|
18303
|
+
|
|
18304
|
+
hasPendingEvent () {
|
|
18305
|
+
return this.event.data !== undefined ||
|
|
18306
|
+
this.event.event !== undefined ||
|
|
18307
|
+
this.event.id !== undefined ||
|
|
18308
|
+
this.event.retry !== undefined
|
|
18309
|
+
}
|
|
18310
|
+
|
|
18311
|
+
hasCurrentByte () {
|
|
18312
|
+
return this.chunkIndex < this.chunks.length &&
|
|
18313
|
+
this.pos < this.chunks[this.chunkIndex].length
|
|
18314
|
+
}
|
|
18315
|
+
|
|
18316
|
+
currentByte () {
|
|
18317
|
+
return this.chunks[this.chunkIndex][this.pos]
|
|
18318
|
+
}
|
|
18319
|
+
|
|
18320
|
+
consumeCurrentByte () {
|
|
18321
|
+
this.advanceCursor()
|
|
18322
|
+
this.syncLineStartToCursor()
|
|
18323
|
+
}
|
|
18324
|
+
|
|
18325
|
+
advanceCursor () {
|
|
18326
|
+
this.pos++
|
|
18327
|
+
|
|
18328
|
+
while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
|
|
18329
|
+
this.chunkIndex++
|
|
18330
|
+
this.pos = 0
|
|
18331
|
+
}
|
|
18332
|
+
}
|
|
18333
|
+
|
|
18334
|
+
syncLineStartToCursor () {
|
|
18335
|
+
this.lineChunkIndex = this.chunkIndex
|
|
18336
|
+
this.linePos = this.pos
|
|
18337
|
+
this.dropConsumedChunks()
|
|
18338
|
+
}
|
|
18339
|
+
|
|
18340
|
+
dropConsumedChunks () {
|
|
18341
|
+
while (this.lineChunkIndex > 0) {
|
|
18342
|
+
this.chunks.shift()
|
|
18343
|
+
this.lineChunkIndex--
|
|
18344
|
+
this.chunkIndex--
|
|
18345
|
+
}
|
|
18346
|
+
|
|
18347
|
+
if (this.chunkIndex === this.chunks.length) {
|
|
18348
|
+
this.chunks.length = 0
|
|
18349
|
+
this.chunkIndex = 0
|
|
18350
|
+
this.pos = 0
|
|
18351
|
+
this.lineChunkIndex = 0
|
|
18352
|
+
this.linePos = 0
|
|
18353
|
+
}
|
|
18354
|
+
}
|
|
18355
|
+
|
|
18356
|
+
readLine () {
|
|
18357
|
+
if (this.lineChunkIndex === this.chunkIndex) {
|
|
18358
|
+
return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
|
|
18359
|
+
}
|
|
18360
|
+
|
|
18361
|
+
const chunks = []
|
|
18362
|
+
let length = 0
|
|
18363
|
+
|
|
18364
|
+
for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
|
|
18365
|
+
const chunk = this.chunks[i]
|
|
18366
|
+
const start = i === this.lineChunkIndex ? this.linePos : 0
|
|
18367
|
+
const end = i === this.chunkIndex ? this.pos : chunk.length
|
|
18368
|
+
const slice = chunk.subarray(start, end)
|
|
18369
|
+
length += slice.length
|
|
18370
|
+
chunks.push(slice)
|
|
18371
|
+
}
|
|
18372
|
+
|
|
18373
|
+
return Buffer.concat(chunks, length)
|
|
18374
|
+
}
|
|
18375
|
+
|
|
18376
|
+
peekBufferedByte (offset) {
|
|
18377
|
+
let chunkIndex = this.lineChunkIndex
|
|
18378
|
+
let pos = this.linePos
|
|
18379
|
+
|
|
18380
|
+
while (chunkIndex < this.chunks.length) {
|
|
18381
|
+
const chunk = this.chunks[chunkIndex]
|
|
18382
|
+
const remaining = chunk.length - pos
|
|
18383
|
+
|
|
18384
|
+
if (offset < remaining) {
|
|
18385
|
+
return chunk[pos + offset]
|
|
18386
|
+
}
|
|
18387
|
+
|
|
18388
|
+
offset -= remaining
|
|
18389
|
+
chunkIndex++
|
|
18390
|
+
pos = 0
|
|
18391
|
+
}
|
|
18392
|
+
}
|
|
18393
|
+
|
|
18394
|
+
discardLeadingBytes (count) {
|
|
18395
|
+
while (count > 0 && this.lineChunkIndex < this.chunks.length) {
|
|
18396
|
+
const chunk = this.chunks[this.lineChunkIndex]
|
|
18397
|
+
const remaining = chunk.length - this.linePos
|
|
18398
|
+
|
|
18399
|
+
if (count < remaining) {
|
|
18400
|
+
this.linePos += count
|
|
18401
|
+
count = 0
|
|
18402
|
+
} else {
|
|
18403
|
+
count -= remaining
|
|
18404
|
+
this.lineChunkIndex++
|
|
18405
|
+
this.linePos = 0
|
|
18406
|
+
}
|
|
18069
18407
|
}
|
|
18408
|
+
|
|
18409
|
+
this.chunkIndex = this.lineChunkIndex
|
|
18410
|
+
this.pos = this.linePos
|
|
18411
|
+
this.dropConsumedChunks()
|
|
18412
|
+
}
|
|
18413
|
+
|
|
18414
|
+
handleBOM () {
|
|
18415
|
+
const first = this.peekBufferedByte(0)
|
|
18416
|
+
const second = this.peekBufferedByte(1)
|
|
18417
|
+
const third = this.peekBufferedByte(2)
|
|
18418
|
+
|
|
18419
|
+
if (second === undefined) {
|
|
18420
|
+
if (first === BOM[0]) {
|
|
18421
|
+
return true
|
|
18422
|
+
}
|
|
18423
|
+
|
|
18424
|
+
this.checkBOM = false
|
|
18425
|
+
return true
|
|
18426
|
+
}
|
|
18427
|
+
|
|
18428
|
+
if (third === undefined) {
|
|
18429
|
+
if (first === BOM[0] && second === BOM[1]) {
|
|
18430
|
+
return true
|
|
18431
|
+
}
|
|
18432
|
+
|
|
18433
|
+
this.checkBOM = false
|
|
18434
|
+
return false
|
|
18435
|
+
}
|
|
18436
|
+
|
|
18437
|
+
if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
|
|
18438
|
+
this.discardLeadingBytes(3)
|
|
18439
|
+
}
|
|
18440
|
+
|
|
18441
|
+
this.checkBOM = false
|
|
18442
|
+
return !this.hasCurrentByte()
|
|
18070
18443
|
}
|
|
18071
18444
|
}
|
|
18072
18445
|
|
|
@@ -29358,7 +29731,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
29358
29731
|
// is specified, the server needs to include the same field and one of
|
|
29359
29732
|
// the selected subprotocol values in its response for the connection to
|
|
29360
29733
|
// be established.
|
|
29361
|
-
if (!requestProtocols.includes(secProtocol)) {
|
|
29734
|
+
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
|
|
29362
29735
|
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
|
29363
29736
|
return
|
|
29364
29737
|
}
|
|
@@ -30068,40 +30441,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
|
|
30068
30441
|
const kBuffer = Symbol('kBuffer')
|
|
30069
30442
|
const kLength = Symbol('kLength')
|
|
30070
30443
|
|
|
30071
|
-
// Default maximum decompressed message size: 4 MB
|
|
30072
|
-
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
|
30073
|
-
|
|
30074
30444
|
class PerMessageDeflate {
|
|
30075
30445
|
/** @type {import('node:zlib').InflateRaw} */
|
|
30076
30446
|
#inflate
|
|
30077
30447
|
|
|
30078
30448
|
#options = {}
|
|
30079
30449
|
|
|
30080
|
-
|
|
30081
|
-
#aborted = false
|
|
30082
|
-
|
|
30083
|
-
/** @type {Function|null} */
|
|
30084
|
-
#currentCallback = null
|
|
30450
|
+
#maxPayloadSize = 0
|
|
30085
30451
|
|
|
30086
30452
|
/**
|
|
30087
30453
|
* @param {Map<string, string>} extensions
|
|
30088
30454
|
*/
|
|
30089
|
-
constructor (extensions) {
|
|
30455
|
+
constructor (extensions, options) {
|
|
30090
30456
|
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
|
30091
30457
|
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
|
30458
|
+
|
|
30459
|
+
this.#maxPayloadSize = options.maxPayloadSize
|
|
30092
30460
|
}
|
|
30093
30461
|
|
|
30462
|
+
/**
|
|
30463
|
+
* Decompress a compressed payload.
|
|
30464
|
+
* @param {Buffer} chunk Compressed data
|
|
30465
|
+
* @param {boolean} fin Final fragment flag
|
|
30466
|
+
* @param {Function} callback Callback function
|
|
30467
|
+
*/
|
|
30094
30468
|
decompress (chunk, fin, callback) {
|
|
30095
30469
|
// An endpoint uses the following algorithm to decompress a message.
|
|
30096
30470
|
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
|
30097
30471
|
// payload of the message.
|
|
30098
30472
|
// 2. Decompress the resulting data using DEFLATE.
|
|
30099
|
-
|
|
30100
|
-
if (this.#aborted) {
|
|
30101
|
-
callback(new MessageSizeExceededError())
|
|
30102
|
-
return
|
|
30103
|
-
}
|
|
30104
|
-
|
|
30105
30473
|
if (!this.#inflate) {
|
|
30106
30474
|
let windowBits = Z_DEFAULT_WINDOWBITS
|
|
30107
30475
|
|
|
@@ -30124,23 +30492,17 @@ class PerMessageDeflate {
|
|
|
30124
30492
|
this.#inflate[kLength] = 0
|
|
30125
30493
|
|
|
30126
30494
|
this.#inflate.on('data', (data) => {
|
|
30127
|
-
if (this.#aborted) {
|
|
30128
|
-
return
|
|
30129
|
-
}
|
|
30130
|
-
|
|
30131
30495
|
this.#inflate[kLength] += data.length
|
|
30132
30496
|
|
|
30133
|
-
if (this.#inflate[kLength] >
|
|
30134
|
-
|
|
30497
|
+
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
|
30498
|
+
callback(new MessageSizeExceededError())
|
|
30499
|
+
// The inflater may still hold buffered input that can emit a late
|
|
30500
|
+
// zlib error. Remove the data listener, then deterministically stop
|
|
30501
|
+
// the stream so a subsequent 'error' cannot fire without a listener
|
|
30502
|
+
// (which would terminate the process as an unhandled error event).
|
|
30135
30503
|
this.#inflate.removeAllListeners()
|
|
30136
30504
|
this.#inflate.destroy()
|
|
30137
30505
|
this.#inflate = null
|
|
30138
|
-
|
|
30139
|
-
if (this.#currentCallback) {
|
|
30140
|
-
const cb = this.#currentCallback
|
|
30141
|
-
this.#currentCallback = null
|
|
30142
|
-
cb(new MessageSizeExceededError())
|
|
30143
|
-
}
|
|
30144
30506
|
return
|
|
30145
30507
|
}
|
|
30146
30508
|
|
|
@@ -30153,14 +30515,13 @@ class PerMessageDeflate {
|
|
|
30153
30515
|
})
|
|
30154
30516
|
}
|
|
30155
30517
|
|
|
30156
|
-
this.#currentCallback = callback
|
|
30157
30518
|
this.#inflate.write(chunk)
|
|
30158
30519
|
if (fin) {
|
|
30159
30520
|
this.#inflate.write(tail)
|
|
30160
30521
|
}
|
|
30161
30522
|
|
|
30162
30523
|
this.#inflate.flush(() => {
|
|
30163
|
-
if (
|
|
30524
|
+
if (!this.#inflate) {
|
|
30164
30525
|
return
|
|
30165
30526
|
}
|
|
30166
30527
|
|
|
@@ -30168,7 +30529,6 @@ class PerMessageDeflate {
|
|
|
30168
30529
|
|
|
30169
30530
|
this.#inflate[kBuffer].length = 0
|
|
30170
30531
|
this.#inflate[kLength] = 0
|
|
30171
|
-
this.#currentCallback = null
|
|
30172
30532
|
|
|
30173
30533
|
callback(null, full)
|
|
30174
30534
|
})
|
|
@@ -30204,6 +30564,12 @@ const {
|
|
|
30204
30564
|
const { WebsocketFrameSend } = __nccwpck_require__(9272)
|
|
30205
30565
|
const { closeWebSocketConnection } = __nccwpck_require__(2569)
|
|
30206
30566
|
const { PerMessageDeflate } = __nccwpck_require__(2869)
|
|
30567
|
+
const { MessageSizeExceededError } = __nccwpck_require__(8091)
|
|
30568
|
+
|
|
30569
|
+
function failWebsocketConnectionWithCode (ws, code, reason) {
|
|
30570
|
+
closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
|
|
30571
|
+
failWebsocketConnection(ws, reason)
|
|
30572
|
+
}
|
|
30207
30573
|
|
|
30208
30574
|
// This code was influenced by ws released under the MIT license.
|
|
30209
30575
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -30212,6 +30578,7 @@ const { PerMessageDeflate } = __nccwpck_require__(2869)
|
|
|
30212
30578
|
|
|
30213
30579
|
class ByteParser extends Writable {
|
|
30214
30580
|
#buffers = []
|
|
30581
|
+
#fragmentsBytes = 0
|
|
30215
30582
|
#byteOffset = 0
|
|
30216
30583
|
#loop = false
|
|
30217
30584
|
|
|
@@ -30223,18 +30590,27 @@ class ByteParser extends Writable {
|
|
|
30223
30590
|
/** @type {Map<string, PerMessageDeflate>} */
|
|
30224
30591
|
#extensions
|
|
30225
30592
|
|
|
30593
|
+
/** @type {number} */
|
|
30594
|
+
#maxFragments
|
|
30595
|
+
|
|
30596
|
+
/** @type {number} */
|
|
30597
|
+
#maxPayloadSize
|
|
30598
|
+
|
|
30226
30599
|
/**
|
|
30227
30600
|
* @param {import('./websocket').WebSocket} ws
|
|
30228
30601
|
* @param {Map<string, string>|null} extensions
|
|
30602
|
+
* @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
|
30229
30603
|
*/
|
|
30230
|
-
constructor (ws, extensions) {
|
|
30604
|
+
constructor (ws, extensions, options = {}) {
|
|
30231
30605
|
super()
|
|
30232
30606
|
|
|
30233
30607
|
this.ws = ws
|
|
30234
30608
|
this.#extensions = extensions == null ? new Map() : extensions
|
|
30609
|
+
this.#maxFragments = options.maxFragments ?? 0
|
|
30610
|
+
this.#maxPayloadSize = options.maxPayloadSize ?? 0
|
|
30235
30611
|
|
|
30236
30612
|
if (this.#extensions.has('permessage-deflate')) {
|
|
30237
|
-
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
|
30613
|
+
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
|
30238
30614
|
}
|
|
30239
30615
|
}
|
|
30240
30616
|
|
|
@@ -30250,6 +30626,19 @@ class ByteParser extends Writable {
|
|
|
30250
30626
|
this.run(callback)
|
|
30251
30627
|
}
|
|
30252
30628
|
|
|
30629
|
+
#validatePayloadLength () {
|
|
30630
|
+
if (
|
|
30631
|
+
this.#maxPayloadSize > 0 &&
|
|
30632
|
+
!isControlFrame(this.#info.opcode) &&
|
|
30633
|
+
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
|
30634
|
+
) {
|
|
30635
|
+
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
|
30636
|
+
return false
|
|
30637
|
+
}
|
|
30638
|
+
|
|
30639
|
+
return true
|
|
30640
|
+
}
|
|
30641
|
+
|
|
30253
30642
|
/**
|
|
30254
30643
|
* Runs whenever a new chunk is received.
|
|
30255
30644
|
* Callback is called whenever there are no more chunks buffering,
|
|
@@ -30338,6 +30727,10 @@ class ByteParser extends Writable {
|
|
|
30338
30727
|
if (payloadLength <= 125) {
|
|
30339
30728
|
this.#info.payloadLength = payloadLength
|
|
30340
30729
|
this.#state = parserStates.READ_DATA
|
|
30730
|
+
|
|
30731
|
+
if (!this.#validatePayloadLength()) {
|
|
30732
|
+
return
|
|
30733
|
+
}
|
|
30341
30734
|
} else if (payloadLength === 126) {
|
|
30342
30735
|
this.#state = parserStates.PAYLOADLENGTH_16
|
|
30343
30736
|
} else if (payloadLength === 127) {
|
|
@@ -30362,6 +30755,10 @@ class ByteParser extends Writable {
|
|
|
30362
30755
|
|
|
30363
30756
|
this.#info.payloadLength = buffer.readUInt16BE(0)
|
|
30364
30757
|
this.#state = parserStates.READ_DATA
|
|
30758
|
+
|
|
30759
|
+
if (!this.#validatePayloadLength()) {
|
|
30760
|
+
return
|
|
30761
|
+
}
|
|
30365
30762
|
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
|
30366
30763
|
if (this.#byteOffset < 8) {
|
|
30367
30764
|
return callback()
|
|
@@ -30384,6 +30781,10 @@ class ByteParser extends Writable {
|
|
|
30384
30781
|
|
|
30385
30782
|
this.#info.payloadLength = lower
|
|
30386
30783
|
this.#state = parserStates.READ_DATA
|
|
30784
|
+
|
|
30785
|
+
if (!this.#validatePayloadLength()) {
|
|
30786
|
+
return
|
|
30787
|
+
}
|
|
30387
30788
|
} else if (this.#state === parserStates.READ_DATA) {
|
|
30388
30789
|
if (this.#byteOffset < this.#info.payloadLength) {
|
|
30389
30790
|
return callback()
|
|
@@ -30396,42 +30797,58 @@ class ByteParser extends Writable {
|
|
|
30396
30797
|
this.#state = parserStates.INFO
|
|
30397
30798
|
} else {
|
|
30398
30799
|
if (!this.#info.compressed) {
|
|
30399
|
-
this
|
|
30800
|
+
if (!this.writeFragments(body)) {
|
|
30801
|
+
return
|
|
30802
|
+
}
|
|
30803
|
+
|
|
30804
|
+
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
|
30805
|
+
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
|
30806
|
+
return
|
|
30807
|
+
}
|
|
30400
30808
|
|
|
30401
30809
|
// If the frame is not fragmented, a message has been received.
|
|
30402
30810
|
// If the frame is fragmented, it will terminate with a fin bit set
|
|
30403
30811
|
// and an opcode of 0 (continuation), therefore we handle that when
|
|
30404
30812
|
// parsing continuation frames, not here.
|
|
30405
30813
|
if (!this.#info.fragmented && this.#info.fin) {
|
|
30406
|
-
|
|
30407
|
-
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
|
30408
|
-
this.#fragments.length = 0
|
|
30814
|
+
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
|
30409
30815
|
}
|
|
30410
30816
|
|
|
30411
30817
|
this.#state = parserStates.INFO
|
|
30412
30818
|
} else {
|
|
30413
|
-
this.#extensions.get('permessage-deflate').decompress(
|
|
30414
|
-
|
|
30415
|
-
|
|
30416
|
-
|
|
30417
|
-
|
|
30819
|
+
this.#extensions.get('permessage-deflate').decompress(
|
|
30820
|
+
body,
|
|
30821
|
+
this.#info.fin,
|
|
30822
|
+
(error, data) => {
|
|
30823
|
+
if (error) {
|
|
30824
|
+
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
|
30825
|
+
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
|
30826
|
+
return
|
|
30827
|
+
}
|
|
30418
30828
|
|
|
30419
|
-
|
|
30829
|
+
if (!this.writeFragments(data)) {
|
|
30830
|
+
return
|
|
30831
|
+
}
|
|
30832
|
+
|
|
30833
|
+
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
|
30834
|
+
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
|
30835
|
+
return
|
|
30836
|
+
}
|
|
30837
|
+
|
|
30838
|
+
if (!this.#info.fin) {
|
|
30839
|
+
this.#state = parserStates.INFO
|
|
30840
|
+
this.#loop = true
|
|
30841
|
+
this.run(callback)
|
|
30842
|
+
return
|
|
30843
|
+
}
|
|
30844
|
+
|
|
30845
|
+
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
|
30420
30846
|
|
|
30421
|
-
if (!this.#info.fin) {
|
|
30422
|
-
this.#state = parserStates.INFO
|
|
30423
30847
|
this.#loop = true
|
|
30848
|
+
this.#state = parserStates.INFO
|
|
30424
30849
|
this.run(callback)
|
|
30425
|
-
return
|
|
30426
30850
|
}
|
|
30427
|
-
|
|
30428
|
-
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
|
30429
|
-
|
|
30430
|
-
this.#loop = true
|
|
30431
|
-
this.#state = parserStates.INFO
|
|
30432
|
-
this.#fragments.length = 0
|
|
30433
|
-
this.run(callback)
|
|
30434
|
-
})
|
|
30851
|
+
)
|
|
30435
30852
|
|
|
30436
30853
|
this.#loop = false
|
|
30437
30854
|
break
|
|
@@ -30483,6 +30900,35 @@ class ByteParser extends Writable {
|
|
|
30483
30900
|
return buffer
|
|
30484
30901
|
}
|
|
30485
30902
|
|
|
30903
|
+
writeFragments (fragment) {
|
|
30904
|
+
if (
|
|
30905
|
+
this.#maxFragments > 0 &&
|
|
30906
|
+
this.#fragments.length === this.#maxFragments
|
|
30907
|
+
) {
|
|
30908
|
+
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
|
30909
|
+
return false
|
|
30910
|
+
}
|
|
30911
|
+
|
|
30912
|
+
this.#fragmentsBytes += fragment.length
|
|
30913
|
+
this.#fragments.push(fragment)
|
|
30914
|
+
return true
|
|
30915
|
+
}
|
|
30916
|
+
|
|
30917
|
+
consumeFragments () {
|
|
30918
|
+
const fragments = this.#fragments
|
|
30919
|
+
|
|
30920
|
+
if (fragments.length === 1) {
|
|
30921
|
+
this.#fragmentsBytes = 0
|
|
30922
|
+
return fragments.shift()
|
|
30923
|
+
}
|
|
30924
|
+
|
|
30925
|
+
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
|
30926
|
+
this.#fragments = []
|
|
30927
|
+
this.#fragmentsBytes = 0
|
|
30928
|
+
|
|
30929
|
+
return output
|
|
30930
|
+
}
|
|
30931
|
+
|
|
30486
30932
|
parseCloseBody (data) {
|
|
30487
30933
|
assert(data.length !== 1)
|
|
30488
30934
|
|
|
@@ -31518,7 +31964,14 @@ class WebSocket extends EventTarget {
|
|
|
31518
31964
|
// once this happens, the connection is open
|
|
31519
31965
|
this[kResponse] = response
|
|
31520
31966
|
|
|
31521
|
-
const
|
|
31967
|
+
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
|
31968
|
+
const maxFragments = webSocketOptions?.maxFragments
|
|
31969
|
+
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
|
31970
|
+
|
|
31971
|
+
const parser = new ByteParser(this, parsedExtensions, {
|
|
31972
|
+
maxFragments,
|
|
31973
|
+
maxPayloadSize
|
|
31974
|
+
})
|
|
31522
31975
|
parser.on('drain', onParserDrain)
|
|
31523
31976
|
parser.on('error', onParserError.bind(this))
|
|
31524
31977
|
|
|
@@ -32606,7 +33059,7 @@ async function run() {
|
|
|
32606
33059
|
// Parse test results
|
|
32607
33060
|
const testResults = (0, parsers_1.parseTestOutput)(testOutput);
|
|
32608
33061
|
if (!testResults) {
|
|
32609
|
-
core.setFailed(
|
|
33062
|
+
core.setFailed(parsers_1.UNPARSEABLE_OUTPUT);
|
|
32610
33063
|
return;
|
|
32611
33064
|
}
|
|
32612
33065
|
core.info(`Parsed results: ${testResults.passed}/${testResults.total} tests passing`);
|
|
@@ -32779,23 +33232,41 @@ function parseBunOutput(output) {
|
|
|
32779
33232
|
* Tries each framework parser in order until one succeeds.
|
|
32780
33233
|
*/
|
|
32781
33234
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
33235
|
+
exports.UNPARSEABLE_OUTPUT = exports.SUPPORTED_PARSERS = exports.parseWjttcOutput = void 0;
|
|
32782
33236
|
exports.parseTestOutput = parseTestOutput;
|
|
32783
33237
|
const bun_1 = __nccwpck_require__(1505);
|
|
32784
33238
|
const jest_1 = __nccwpck_require__(3416);
|
|
32785
33239
|
const vitest_1 = __nccwpck_require__(9839);
|
|
33240
|
+
const wjttc_1 = __nccwpck_require__(8750);
|
|
33241
|
+
var wjttc_2 = __nccwpck_require__(8750);
|
|
33242
|
+
Object.defineProperty(exports, "parseWjttcOutput", ({ enumerable: true, get: function () { return wjttc_2.parseWjttcOutput; } }));
|
|
33243
|
+
/**
|
|
33244
|
+
* Every parser, in the order parseTestOutput tries them. Order matters:
|
|
33245
|
+
* 1. Bun — `Ran N tests across M files` is bun-unique.
|
|
33246
|
+
* 2. WJTTC — `pc-ai WJTTC` / suite `pc-ai-wjttc` (FAF bar; not Jest-shaped).
|
|
33247
|
+
* 3. Jest — `Tests: … total`.
|
|
33248
|
+
* 4. Vitest — `Tests … (N)`.
|
|
33249
|
+
*/
|
|
33250
|
+
const PARSERS = [
|
|
33251
|
+
['Bun', bun_1.parseBunOutput],
|
|
33252
|
+
['WJTTC', wjttc_1.parseWjttcOutput],
|
|
33253
|
+
['Jest', jest_1.parseJestOutput],
|
|
33254
|
+
['Vitest', vitest_1.parseVitestOutput],
|
|
33255
|
+
];
|
|
33256
|
+
exports.SUPPORTED_PARSERS = PARSERS.map(([name]) => name);
|
|
33257
|
+
/** What the CLI and the Action report when no parser matches. */
|
|
33258
|
+
exports.UNPARSEABLE_OUTPUT = `Could not parse test output. Supported: ${exports.SUPPORTED_PARSERS.join(', ')}.`;
|
|
32786
33259
|
/**
|
|
32787
|
-
* Parse test output from any supported framework
|
|
32788
|
-
*
|
|
32789
|
-
* Order matters: Bun is tried first because its `Ran N tests across M files`
|
|
32790
|
-
* anchor is bun-unique — it never false-matches on jest/vitest output. Putting
|
|
32791
|
-
* it first also lets bun consumers ship truthful totals even when their CI
|
|
32792
|
-
* still has a legacy `Tests: N passed, N total` jest-shim line appended to the
|
|
32793
|
-
* test output (the bun parser ignores it and reads the canonical `Ran` line).
|
|
32794
|
-
*
|
|
32795
|
-
* Tries parsers in order: Bun, Jest, Vitest.
|
|
33260
|
+
* Parse test output from any supported framework: the first parser in
|
|
33261
|
+
* PARSERS that recognises the output wins.
|
|
32796
33262
|
*/
|
|
32797
33263
|
function parseTestOutput(output) {
|
|
32798
|
-
|
|
33264
|
+
for (const [, parse] of PARSERS) {
|
|
33265
|
+
const results = parse(output);
|
|
33266
|
+
if (results)
|
|
33267
|
+
return results;
|
|
33268
|
+
}
|
|
33269
|
+
return null;
|
|
32799
33270
|
}
|
|
32800
33271
|
|
|
32801
33272
|
|
|
@@ -32961,6 +33432,165 @@ function parseVitestOutput(output) {
|
|
|
32961
33432
|
}
|
|
32962
33433
|
|
|
32963
33434
|
|
|
33435
|
+
/***/ }),
|
|
33436
|
+
|
|
33437
|
+
/***/ 8750:
|
|
33438
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
33439
|
+
|
|
33440
|
+
"use strict";
|
|
33441
|
+
|
|
33442
|
+
/**
|
|
33443
|
+
* WJTTC output parser
|
|
33444
|
+
*
|
|
33445
|
+
* Extracts test counts from the FAF / pc-ai WJTTC bar runner
|
|
33446
|
+
* (tests/wjttc/run-wjttc.mjs and sibling suite runners).
|
|
33447
|
+
*
|
|
33448
|
+
* Two shapes:
|
|
33449
|
+
*
|
|
33450
|
+
* 1) Human log (default):
|
|
33451
|
+
* ```
|
|
33452
|
+
* pc-ai WJTTC
|
|
33453
|
+
* ----------
|
|
33454
|
+
* PASS VAL-01 valid-simple portfolio accepts
|
|
33455
|
+
* FAIL OPT-01 …
|
|
33456
|
+
* ----------
|
|
33457
|
+
* 31/33 passed
|
|
33458
|
+
* ```
|
|
33459
|
+
*
|
|
33460
|
+
* 2) Machine JSON (`--json`):
|
|
33461
|
+
* ```
|
|
33462
|
+
* {
|
|
33463
|
+
* "suite": "pc-ai-wjttc",
|
|
33464
|
+
* "total": 33,
|
|
33465
|
+
* "passed": 31,
|
|
33466
|
+
* "failed": 2,
|
|
33467
|
+
* "results": [ … ]
|
|
33468
|
+
* }
|
|
33469
|
+
* ```
|
|
33470
|
+
*
|
|
33471
|
+
* Anchors are WJTTC-specific so this never false-matches Jest/Vitest/Bun.
|
|
33472
|
+
* Prefer JSON when both appear in a teed stream (structured totals win).
|
|
33473
|
+
*/
|
|
33474
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
33475
|
+
exports.parseWjttcOutput = parseWjttcOutput;
|
|
33476
|
+
function stripAnsi(output) {
|
|
33477
|
+
// eslint-disable-next-line no-control-regex
|
|
33478
|
+
return output
|
|
33479
|
+
.replace(/\x1b\[[0-9;]*m/g, '')
|
|
33480
|
+
.replace(/\r\n/g, '\n')
|
|
33481
|
+
.replace(/\r/g, '\n');
|
|
33482
|
+
}
|
|
33483
|
+
/**
|
|
33484
|
+
* Try to parse a WJTTC --json summary object from output.
|
|
33485
|
+
* Handles pure JSON or JSON embedded after build noise in a tee file.
|
|
33486
|
+
*/
|
|
33487
|
+
function parseJsonSuite(clean) {
|
|
33488
|
+
// Prefer a top-level object that names the suite.
|
|
33489
|
+
const candidates = [];
|
|
33490
|
+
const trimmed = clean.trim();
|
|
33491
|
+
if (trimmed.startsWith('{')) {
|
|
33492
|
+
candidates.push(trimmed);
|
|
33493
|
+
}
|
|
33494
|
+
// Last {...} block often is the suite when logs precede JSON.
|
|
33495
|
+
const lastBrace = clean.lastIndexOf('{');
|
|
33496
|
+
if (lastBrace !== -1) {
|
|
33497
|
+
candidates.push(clean.slice(lastBrace));
|
|
33498
|
+
}
|
|
33499
|
+
for (const raw of candidates) {
|
|
33500
|
+
try {
|
|
33501
|
+
const body = JSON.parse(raw);
|
|
33502
|
+
if (!body || typeof body !== 'object')
|
|
33503
|
+
continue;
|
|
33504
|
+
if (body.suite !== 'pc-ai-wjttc')
|
|
33505
|
+
continue;
|
|
33506
|
+
const total = Number(body.total);
|
|
33507
|
+
const passed = Number(body.passed);
|
|
33508
|
+
const failed = Number(body.failed);
|
|
33509
|
+
if (!Number.isFinite(total) || total <= 0)
|
|
33510
|
+
continue;
|
|
33511
|
+
if (!Number.isFinite(passed) || !Number.isFinite(failed))
|
|
33512
|
+
continue;
|
|
33513
|
+
// Prefer explicit fields; fall back to results[] length if needed.
|
|
33514
|
+
let p = passed;
|
|
33515
|
+
let f = failed;
|
|
33516
|
+
let t = total;
|
|
33517
|
+
if (Array.isArray(body.results) && body.results.length > 0) {
|
|
33518
|
+
const fromRows = body.results.length;
|
|
33519
|
+
if (t !== fromRows && fromRows > 0) {
|
|
33520
|
+
// Trust results array as the executed set (partition honesty).
|
|
33521
|
+
t = fromRows;
|
|
33522
|
+
p = body.results.filter((r) => r && r.ok === true).length;
|
|
33523
|
+
f = t - p;
|
|
33524
|
+
}
|
|
33525
|
+
}
|
|
33526
|
+
return {
|
|
33527
|
+
total: t,
|
|
33528
|
+
passed: p,
|
|
33529
|
+
failed: f,
|
|
33530
|
+
result: f > 0 ? 'FAILED' : 'PASSED',
|
|
33531
|
+
};
|
|
33532
|
+
}
|
|
33533
|
+
catch {
|
|
33534
|
+
// try next candidate
|
|
33535
|
+
}
|
|
33536
|
+
}
|
|
33537
|
+
return null;
|
|
33538
|
+
}
|
|
33539
|
+
/**
|
|
33540
|
+
* Human WJTTC log: header + optional PASS/FAIL lines + "N/N passed".
|
|
33541
|
+
*/
|
|
33542
|
+
function parseHumanLog(clean) {
|
|
33543
|
+
// Require the suite banner so we do not steal "12/12 passed" from other tools.
|
|
33544
|
+
if (!/pc-ai\s+WJTTC/i.test(clean)) {
|
|
33545
|
+
return null;
|
|
33546
|
+
}
|
|
33547
|
+
const summary = clean.match(/(\d+)\s*\/\s*(\d+)\s+passed\b/i);
|
|
33548
|
+
if (!summary)
|
|
33549
|
+
return null;
|
|
33550
|
+
const passedFromSummary = parseInt(summary[1], 10);
|
|
33551
|
+
const totalFromSummary = parseInt(summary[2], 10);
|
|
33552
|
+
if (totalFromSummary <= 0)
|
|
33553
|
+
return null;
|
|
33554
|
+
// Cross-check with PASS/FAIL lines when present (stronger than summary alone).
|
|
33555
|
+
const passLines = clean.match(/^PASS\s+\S+/gm);
|
|
33556
|
+
const failLines = clean.match(/^FAIL\s+\S+/gm);
|
|
33557
|
+
const linePassed = passLines ? passLines.length : -1;
|
|
33558
|
+
const lineFailed = failLines ? failLines.length : -1;
|
|
33559
|
+
let total = totalFromSummary;
|
|
33560
|
+
let passed = passedFromSummary;
|
|
33561
|
+
let failed = total - passed;
|
|
33562
|
+
if (linePassed >= 0 && lineFailed >= 0) {
|
|
33563
|
+
const lineTotal = linePassed + lineFailed;
|
|
33564
|
+
if (lineTotal === totalFromSummary) {
|
|
33565
|
+
passed = linePassed;
|
|
33566
|
+
failed = lineFailed;
|
|
33567
|
+
total = lineTotal;
|
|
33568
|
+
}
|
|
33569
|
+
// If lines disagree with summary, still use summary (runner is source of truth)
|
|
33570
|
+
// but failed must be non-negative.
|
|
33571
|
+
if (failed < 0)
|
|
33572
|
+
failed = 0;
|
|
33573
|
+
}
|
|
33574
|
+
return {
|
|
33575
|
+
total,
|
|
33576
|
+
passed,
|
|
33577
|
+
failed,
|
|
33578
|
+
result: failed > 0 ? 'FAILED' : 'PASSED',
|
|
33579
|
+
};
|
|
33580
|
+
}
|
|
33581
|
+
/**
|
|
33582
|
+
* Parse WJTTC (pc-ai bar) output to TestResults, or null if not WJTTC.
|
|
33583
|
+
*/
|
|
33584
|
+
function parseWjttcOutput(output) {
|
|
33585
|
+
const clean = stripAnsi(output);
|
|
33586
|
+
// JSON first — structured totals when --json was used.
|
|
33587
|
+
const fromJson = parseJsonSuite(clean);
|
|
33588
|
+
if (fromJson)
|
|
33589
|
+
return fromJson;
|
|
33590
|
+
return parseHumanLog(clean);
|
|
33591
|
+
}
|
|
33592
|
+
|
|
33593
|
+
|
|
32964
33594
|
/***/ }),
|
|
32965
33595
|
|
|
32966
33596
|
/***/ 390:
|
|
@@ -33461,7 +34091,7 @@ function composeCollection(CN, ctx, token, props, onError) {
|
|
|
33461
34091
|
let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType);
|
|
33462
34092
|
if (!tag) {
|
|
33463
34093
|
const kt = ctx.schema.knownTags[tagName];
|
|
33464
|
-
if (kt
|
|
34094
|
+
if (kt?.collection === expType) {
|
|
33465
34095
|
ctx.schema.tags.push(Object.assign({}, kt, { default: false }));
|
|
33466
34096
|
tag = kt;
|
|
33467
34097
|
}
|
|
@@ -33581,19 +34211,26 @@ function composeNode(ctx, token, props, onError) {
|
|
|
33581
34211
|
case 'block-map':
|
|
33582
34212
|
case 'block-seq':
|
|
33583
34213
|
case 'flow-collection':
|
|
33584
|
-
|
|
33585
|
-
|
|
33586
|
-
|
|
34214
|
+
try {
|
|
34215
|
+
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
|
34216
|
+
if (anchor)
|
|
34217
|
+
node.anchor = anchor.source.substring(1);
|
|
34218
|
+
}
|
|
34219
|
+
catch (error) {
|
|
34220
|
+
// Almost certainly here due to a stack overflow
|
|
34221
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34222
|
+
onError(token, 'RESOURCE_EXHAUSTION', message);
|
|
34223
|
+
}
|
|
33587
34224
|
break;
|
|
33588
34225
|
default: {
|
|
33589
34226
|
const message = token.type === 'error'
|
|
33590
34227
|
? token.message
|
|
33591
34228
|
: `Unsupported token (type: ${token.type})`;
|
|
33592
34229
|
onError(token, 'UNEXPECTED_TOKEN', message);
|
|
33593
|
-
node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
|
|
33594
34230
|
isSrcToken = false;
|
|
33595
34231
|
}
|
|
33596
34232
|
}
|
|
34233
|
+
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
|
33597
34234
|
if (anchor && node.anchor === '')
|
|
33598
34235
|
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
|
33599
34236
|
if (atKey &&
|
|
@@ -33857,8 +34494,10 @@ class Composer {
|
|
|
33857
34494
|
}
|
|
33858
34495
|
}
|
|
33859
34496
|
if (afterDoc) {
|
|
33860
|
-
|
|
33861
|
-
|
|
34497
|
+
for (let i = 0; i < this.errors.length; ++i)
|
|
34498
|
+
doc.errors.push(this.errors[i]);
|
|
34499
|
+
for (let i = 0; i < this.warnings.length; ++i)
|
|
34500
|
+
doc.warnings.push(this.warnings[i]);
|
|
33862
34501
|
}
|
|
33863
34502
|
else {
|
|
33864
34503
|
doc.errors = this.errors;
|
|
@@ -34347,7 +34986,7 @@ function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, ta
|
|
|
34347
34986
|
});
|
|
34348
34987
|
if (!props.found) {
|
|
34349
34988
|
if (props.anchor || props.tag || value) {
|
|
34350
|
-
if (value
|
|
34989
|
+
if (value?.type === 'block-seq')
|
|
34351
34990
|
onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column');
|
|
34352
34991
|
else
|
|
34353
34992
|
onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator');
|
|
@@ -34564,7 +35203,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
|
|
|
34564
35203
|
}
|
|
34565
35204
|
}
|
|
34566
35205
|
else if (value) {
|
|
34567
|
-
if ('source' in value && value.source
|
|
35206
|
+
if ('source' in value && value.source?.[0] === ':')
|
|
34568
35207
|
onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`);
|
|
34569
35208
|
else
|
|
34570
35209
|
onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`);
|
|
@@ -34608,7 +35247,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
|
|
|
34608
35247
|
const expectedEnd = isMap ? '}' : ']';
|
|
34609
35248
|
const [ce, ...ee] = fc.end;
|
|
34610
35249
|
let cePos = offset;
|
|
34611
|
-
if (ce
|
|
35250
|
+
if (ce?.source === expectedEnd)
|
|
34612
35251
|
cePos = ce.offset + ce.source.length;
|
|
34613
35252
|
else {
|
|
34614
35253
|
const name = fcName[0].toUpperCase() + fcName.substring(1);
|
|
@@ -34790,7 +35429,7 @@ function doubleQuotedValue(source, onError) {
|
|
|
34790
35429
|
next = source[++i + 1];
|
|
34791
35430
|
}
|
|
34792
35431
|
else if (next === 'x' || next === 'u' || next === 'U') {
|
|
34793
|
-
const length =
|
|
35432
|
+
const length = next === 'x' ? 2 : next === 'u' ? 4 : 8;
|
|
34794
35433
|
res += parseCharCode(source, i + 1, length, onError);
|
|
34795
35434
|
i += length;
|
|
34796
35435
|
}
|
|
@@ -34860,12 +35499,14 @@ function parseCharCode(source, offset, length, onError) {
|
|
|
34860
35499
|
const cc = source.substr(offset, length);
|
|
34861
35500
|
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
|
34862
35501
|
const code = ok ? parseInt(cc, 16) : NaN;
|
|
34863
|
-
|
|
35502
|
+
try {
|
|
35503
|
+
return String.fromCodePoint(code);
|
|
35504
|
+
}
|
|
35505
|
+
catch {
|
|
34864
35506
|
const raw = source.substr(offset - 2, length + 2);
|
|
34865
35507
|
onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
|
|
34866
35508
|
return raw;
|
|
34867
35509
|
}
|
|
34868
|
-
return String.fromCodePoint(code);
|
|
34869
35510
|
}
|
|
34870
35511
|
|
|
34871
35512
|
exports.resolveFlowScalar = resolveFlowScalar;
|
|
@@ -35989,7 +36630,7 @@ const prettifyError = (src, lc) => (error) => {
|
|
|
35989
36630
|
if (/[^ ]/.test(lineStr)) {
|
|
35990
36631
|
let count = 1;
|
|
35991
36632
|
const end = error.linePos[1];
|
|
35992
|
-
if (end
|
|
36633
|
+
if (end?.line === line && end.col > col) {
|
|
35993
36634
|
count = Math.max(1, Math.min(end.col - col, 80 - ci));
|
|
35994
36635
|
}
|
|
35995
36636
|
const pointer = ' '.repeat(ci) + '^'.repeat(count);
|
|
@@ -36117,6 +36758,8 @@ class Alias extends Node.NodeBase {
|
|
|
36117
36758
|
* instance of the `source` anchor before this node.
|
|
36118
36759
|
*/
|
|
36119
36760
|
resolve(doc, ctx) {
|
|
36761
|
+
if (ctx?.maxAliasCount === 0)
|
|
36762
|
+
throw new ReferenceError('Alias resolution is disabled');
|
|
36120
36763
|
let nodes;
|
|
36121
36764
|
if (ctx?.aliasResolveCache) {
|
|
36122
36765
|
nodes = ctx.aliasResolveCache;
|
|
@@ -36157,7 +36800,7 @@ class Alias extends Node.NodeBase {
|
|
|
36157
36800
|
data = anchors.get(source);
|
|
36158
36801
|
}
|
|
36159
36802
|
/* istanbul ignore if */
|
|
36160
|
-
if (
|
|
36803
|
+
if (data?.res === undefined) {
|
|
36161
36804
|
const msg = 'This should not happen: Alias anchor was not resolved?';
|
|
36162
36805
|
throw new ReferenceError(msg);
|
|
36163
36806
|
}
|
|
@@ -37804,7 +38447,7 @@ class Lexer {
|
|
|
37804
38447
|
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
|
37805
38448
|
this.indentNext = this.indentValue + 1;
|
|
37806
38449
|
this.indentValue += n;
|
|
37807
|
-
return
|
|
38450
|
+
return 'block-start';
|
|
37808
38451
|
}
|
|
37809
38452
|
return 'doc';
|
|
37810
38453
|
}
|
|
@@ -38125,32 +38768,36 @@ class Lexer {
|
|
|
38125
38768
|
return 0;
|
|
38126
38769
|
}
|
|
38127
38770
|
*pushIndicators() {
|
|
38128
|
-
|
|
38129
|
-
|
|
38130
|
-
|
|
38131
|
-
|
|
38132
|
-
|
|
38133
|
-
|
|
38134
|
-
|
|
38135
|
-
|
|
38136
|
-
|
|
38137
|
-
|
|
38138
|
-
|
|
38139
|
-
|
|
38140
|
-
|
|
38141
|
-
|
|
38142
|
-
|
|
38143
|
-
|
|
38144
|
-
|
|
38145
|
-
|
|
38146
|
-
|
|
38147
|
-
|
|
38148
|
-
|
|
38149
|
-
|
|
38771
|
+
let n = 0;
|
|
38772
|
+
loop: while (true) {
|
|
38773
|
+
switch (this.charAt(0)) {
|
|
38774
|
+
case '!':
|
|
38775
|
+
n += yield* this.pushTag();
|
|
38776
|
+
n += yield* this.pushSpaces(true);
|
|
38777
|
+
continue loop;
|
|
38778
|
+
case '&':
|
|
38779
|
+
n += yield* this.pushUntil(isNotAnchorChar);
|
|
38780
|
+
n += yield* this.pushSpaces(true);
|
|
38781
|
+
continue loop;
|
|
38782
|
+
case '-': // this is an error
|
|
38783
|
+
case '?': // this is an error outside flow collections
|
|
38784
|
+
case ':': {
|
|
38785
|
+
const inFlow = this.flowLevel > 0;
|
|
38786
|
+
const ch1 = this.charAt(1);
|
|
38787
|
+
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
|
38788
|
+
if (!inFlow)
|
|
38789
|
+
this.indentNext = this.indentValue + 1;
|
|
38790
|
+
else if (this.flowKey)
|
|
38791
|
+
this.flowKey = false;
|
|
38792
|
+
n += yield* this.pushCount(1);
|
|
38793
|
+
n += yield* this.pushSpaces(true);
|
|
38794
|
+
continue loop;
|
|
38795
|
+
}
|
|
38150
38796
|
}
|
|
38151
38797
|
}
|
|
38798
|
+
break loop;
|
|
38152
38799
|
}
|
|
38153
|
-
return
|
|
38800
|
+
return n;
|
|
38154
38801
|
}
|
|
38155
38802
|
*pushTag() {
|
|
38156
38803
|
if (this.charAt(1) === '<') {
|
|
@@ -38338,6 +38985,14 @@ function getFirstKeyStartProps(prev) {
|
|
|
38338
38985
|
}
|
|
38339
38986
|
return prev.splice(i, prev.length);
|
|
38340
38987
|
}
|
|
38988
|
+
function arrayPushArray(target, source) {
|
|
38989
|
+
// May exhaust call stack with large `source` array
|
|
38990
|
+
if (source.length < 1e5)
|
|
38991
|
+
Array.prototype.push.apply(target, source);
|
|
38992
|
+
else
|
|
38993
|
+
for (let i = 0; i < source.length; ++i)
|
|
38994
|
+
target.push(source[i]);
|
|
38995
|
+
}
|
|
38341
38996
|
function fixFlowSeqItems(fc) {
|
|
38342
38997
|
if (fc.start.type === 'flow-seq-start') {
|
|
38343
38998
|
for (const it of fc.items) {
|
|
@@ -38350,12 +39005,12 @@ function fixFlowSeqItems(fc) {
|
|
|
38350
39005
|
delete it.key;
|
|
38351
39006
|
if (isFlowToken(it.value)) {
|
|
38352
39007
|
if (it.value.end)
|
|
38353
|
-
|
|
39008
|
+
arrayPushArray(it.value.end, it.sep);
|
|
38354
39009
|
else
|
|
38355
39010
|
it.value.end = it.sep;
|
|
38356
39011
|
}
|
|
38357
39012
|
else
|
|
38358
|
-
|
|
39013
|
+
arrayPushArray(it.start, it.sep);
|
|
38359
39014
|
delete it.sep;
|
|
38360
39015
|
}
|
|
38361
39016
|
}
|
|
@@ -38499,7 +39154,7 @@ class Parser {
|
|
|
38499
39154
|
}
|
|
38500
39155
|
*step() {
|
|
38501
39156
|
const top = this.peek(1);
|
|
38502
|
-
if (this.type === 'doc-end' &&
|
|
39157
|
+
if (this.type === 'doc-end' && top?.type !== 'doc-end') {
|
|
38503
39158
|
while (this.stack.length > 0)
|
|
38504
39159
|
yield* this.pop();
|
|
38505
39160
|
this.stack.push({
|
|
@@ -38775,7 +39430,7 @@ class Parser {
|
|
|
38775
39430
|
const prev = map.items[map.items.length - 2];
|
|
38776
39431
|
const end = prev?.value?.end;
|
|
38777
39432
|
if (Array.isArray(end)) {
|
|
38778
|
-
|
|
39433
|
+
arrayPushArray(end, it.start);
|
|
38779
39434
|
end.push(this.sourceToken);
|
|
38780
39435
|
map.items.pop();
|
|
38781
39436
|
return;
|
|
@@ -38990,7 +39645,7 @@ class Parser {
|
|
|
38990
39645
|
const prev = seq.items[seq.items.length - 2];
|
|
38991
39646
|
const end = prev?.value?.end;
|
|
38992
39647
|
if (Array.isArray(end)) {
|
|
38993
|
-
|
|
39648
|
+
arrayPushArray(end, it.start);
|
|
38994
39649
|
end.push(this.sourceToken);
|
|
38995
39650
|
seq.items.pop();
|
|
38996
39651
|
return;
|
|
@@ -39031,7 +39686,7 @@ class Parser {
|
|
|
39031
39686
|
do {
|
|
39032
39687
|
yield* this.pop();
|
|
39033
39688
|
top = this.peek(1);
|
|
39034
|
-
} while (top
|
|
39689
|
+
} while (top?.type === 'flow-collection');
|
|
39035
39690
|
}
|
|
39036
39691
|
else if (fc.end.length === 0) {
|
|
39037
39692
|
switch (this.type) {
|
|
@@ -40144,18 +40799,18 @@ const isMergeKey = (ctx, key) => (merge.identify(key) ||
|
|
|
40144
40799
|
merge.identify(key.value))) &&
|
|
40145
40800
|
ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
|
|
40146
40801
|
function addMergeToJSMap(ctx, map, value) {
|
|
40147
|
-
|
|
40148
|
-
if (identity.isSeq(
|
|
40149
|
-
for (const it of
|
|
40802
|
+
const source = resolveAliasValue(ctx, value);
|
|
40803
|
+
if (identity.isSeq(source))
|
|
40804
|
+
for (const it of source.items)
|
|
40150
40805
|
mergeValue(ctx, map, it);
|
|
40151
|
-
else if (Array.isArray(
|
|
40152
|
-
for (const it of
|
|
40806
|
+
else if (Array.isArray(source))
|
|
40807
|
+
for (const it of source)
|
|
40153
40808
|
mergeValue(ctx, map, it);
|
|
40154
40809
|
else
|
|
40155
|
-
mergeValue(ctx, map,
|
|
40810
|
+
mergeValue(ctx, map, source);
|
|
40156
40811
|
}
|
|
40157
40812
|
function mergeValue(ctx, map, value) {
|
|
40158
|
-
const source = ctx
|
|
40813
|
+
const source = resolveAliasValue(ctx, value);
|
|
40159
40814
|
if (!identity.isMap(source))
|
|
40160
40815
|
throw new Error('Merge sources must be maps or map aliases');
|
|
40161
40816
|
const srcMap = source.toJSON(null, ctx, Map);
|
|
@@ -40178,6 +40833,9 @@ function mergeValue(ctx, map, value) {
|
|
|
40178
40833
|
}
|
|
40179
40834
|
return map;
|
|
40180
40835
|
}
|
|
40836
|
+
function resolveAliasValue(ctx, value) {
|
|
40837
|
+
return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
|
|
40838
|
+
}
|
|
40181
40839
|
|
|
40182
40840
|
exports.addMergeToJSMap = addMergeToJSMap;
|
|
40183
40841
|
exports.isMergeKey = isMergeKey;
|
|
@@ -40814,6 +41472,7 @@ function createStringifyContext(doc, options) {
|
|
|
40814
41472
|
nullStr: 'null',
|
|
40815
41473
|
simpleKeys: false,
|
|
40816
41474
|
singleQuote: null,
|
|
41475
|
+
trailingComma: false,
|
|
40817
41476
|
trueStr: 'true',
|
|
40818
41477
|
verifyAliasOrder: true
|
|
40819
41478
|
}, doc.schema.toStringOptions, options);
|
|
@@ -41035,12 +41694,22 @@ function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
|
|
|
41035
41694
|
if (comment)
|
|
41036
41695
|
reqNewline = true;
|
|
41037
41696
|
let str = stringify.stringify(item, itemCtx, () => (comment = null));
|
|
41038
|
-
|
|
41697
|
+
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes('\n'));
|
|
41698
|
+
if (i < items.length - 1) {
|
|
41039
41699
|
str += ',';
|
|
41700
|
+
}
|
|
41701
|
+
else if (ctx.options.trailingComma) {
|
|
41702
|
+
if (ctx.options.lineWidth > 0) {
|
|
41703
|
+
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) +
|
|
41704
|
+
(str.length + 2) >
|
|
41705
|
+
ctx.options.lineWidth);
|
|
41706
|
+
}
|
|
41707
|
+
if (reqNewline) {
|
|
41708
|
+
str += ',';
|
|
41709
|
+
}
|
|
41710
|
+
}
|
|
41040
41711
|
if (comment)
|
|
41041
41712
|
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
|
41042
|
-
if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
|
|
41043
|
-
reqNewline = true;
|
|
41044
41713
|
lines.push(str);
|
|
41045
41714
|
linesAtValue = lines.length;
|
|
41046
41715
|
}
|
|
@@ -41217,11 +41886,12 @@ function stringifyNumber({ format, minFractionDigits, tag, value }) {
|
|
|
41217
41886
|
const num = typeof value === 'number' ? value : Number(value);
|
|
41218
41887
|
if (!isFinite(num))
|
|
41219
41888
|
return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf';
|
|
41220
|
-
let n = JSON.stringify(value);
|
|
41889
|
+
let n = Object.is(value, -0) ? '-0' : JSON.stringify(value);
|
|
41221
41890
|
if (!format &&
|
|
41222
41891
|
minFractionDigits &&
|
|
41223
41892
|
(!tag || tag === 'tag:yaml.org,2002:float') &&
|
|
41224
|
-
|
|
41893
|
+
/^-?\d/.test(n) &&
|
|
41894
|
+
!n.includes('e')) {
|
|
41225
41895
|
let i = n.indexOf('.');
|
|
41226
41896
|
if (i < 0) {
|
|
41227
41897
|
i = n.length;
|
|
@@ -41348,7 +42018,7 @@ function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
|
|
|
41348
42018
|
ws += `\n${stringifyComment.indentComment(cs, ctx.indent)}`;
|
|
41349
42019
|
}
|
|
41350
42020
|
if (valueStr === '' && !ctx.inFlow) {
|
|
41351
|
-
if (ws === '\n')
|
|
42021
|
+
if (ws === '\n' && valueComment)
|
|
41352
42022
|
ws = '\n\n';
|
|
41353
42023
|
}
|
|
41354
42024
|
else {
|