@@ -5603,6 +5603,9 @@ const EMPTY_BUF = Buffer.alloc(0)
56035603const FastBuffer = Buffer[Symbol.species]
56045604const addListener = util.addListener
56055605const removeAllListeners = util.removeAllListeners
5606+ const kIdleSocketValidation = Symbol('kIdleSocketValidation')
5607+ const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
5608+ const kSocketUsed = Symbol('kSocketUsed')
56065609
56075610let extractBody
56085611
@@ -5917,6 +5920,11 @@ class Parser {
59175920 return -1
59185921 }
59195922
5923+ if (client[kRunning] === 0) {
5924+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
5925+ return -1
5926+ }
5927+
59205928 const request = client[kQueue][client[kRunningIdx]]
59215929 if (!request) {
59225930 return -1
@@ -6020,6 +6028,11 @@ class Parser {
60206028 return -1
60216029 }
60226030
6031+ if (client[kRunning] === 0) {
6032+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
6033+ return -1
6034+ }
6035+
60236036 const request = client[kQueue][client[kRunningIdx]]
60246037
60256038 /* istanbul ignore next: difficult to make a test case for */
@@ -6193,6 +6206,7 @@ class Parser {
61936206 request.onComplete(headers)
61946207
61956208 client[kQueue][client[kRunningIdx]++] = null
6209+ socket[kSocketUsed] = true
61966210
61976211 if (socket[kWriting]) {
61986212 assert(client[kRunning] === 0)
@@ -6251,6 +6265,9 @@ async function connectH1 (client, socket) {
62516265 socket[kWriting] = false
62526266 socket[kReset] = false
62536267 socket[kBlocking] = false
6268+ socket[kIdleSocketValidation] = 0
6269+ socket[kIdleSocketValidationTimeout] = null
6270+ socket[kSocketUsed] = false
62546271 socket[kParser] = new Parser(client, socket, llhttpInstance)
62556272
62566273 addListener(socket, 'error', function (err) {
@@ -6297,6 +6314,8 @@ async function connectH1 (client, socket) {
62976314 const client = this[kClient]
62986315 const parser = this[kParser]
62996316
6317+ clearIdleSocketValidation(this)
6318+
63006319 if (parser) {
63016320 if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
63026321 this[kError] = parser.finish() || this[kError]
@@ -6362,7 +6381,7 @@ async function connectH1 (client, socket) {
63626381 return socket.destroyed
63636382 },
63646383 busy (request) {
6365- if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
6384+ if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1 ) {
63666385 return true
63676386 }
63686387
@@ -6400,6 +6419,31 @@ async function connectH1 (client, socket) {
64006419 }
64016420}
64026421
6422+ function clearIdleSocketValidation (socket) {
6423+ if (socket[kIdleSocketValidationTimeout]) {
6424+ clearTimeout(socket[kIdleSocketValidationTimeout])
6425+ socket[kIdleSocketValidationTimeout] = null
6426+ }
6427+
6428+ socket[kIdleSocketValidation] = 0
6429+ }
6430+
6431+ function scheduleIdleSocketValidation (client, socket) {
6432+ socket[kIdleSocketValidation] = 1
6433+ socket[kIdleSocketValidationTimeout] = setTimeout(() => {
6434+ socket[kIdleSocketValidationTimeout] = null
6435+ socket[kIdleSocketValidation] = 2
6436+
6437+ if (client[kSocket] === socket && !socket.destroyed) {
6438+ client[kResume]()
6439+ }
6440+ }, 0)
6441+ socket[kIdleSocketValidationTimeout].unref?.()
6442+ }
6443+
6444+ /**
6445+ * @param {import('./client.js')} client
6446+ */
64036447function resumeH1 (client) {
64046448 const socket = client[kSocket]
64056449
@@ -6414,6 +6458,32 @@ function resumeH1 (client) {
64146458 socket[kNoRef] = false
64156459 }
64166460
6461+ if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
6462+ if (socket[kIdleSocketValidation] === 0) {
6463+ scheduleIdleSocketValidation(client, socket)
6464+ socket[kParser].readMore()
6465+ if (socket.destroyed) {
6466+ return
6467+ }
6468+ return
6469+ }
6470+
6471+ if (socket[kIdleSocketValidation] === 1) {
6472+ socket[kParser].readMore()
6473+ if (socket.destroyed) {
6474+ return
6475+ }
6476+ return
6477+ }
6478+ }
6479+
6480+ if (client[kRunning] === 0) {
6481+ socket[kParser].readMore()
6482+ if (socket.destroyed) {
6483+ return
6484+ }
6485+ }
6486+
64176487 if (client[kSize] === 0) {
64186488 if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
64196489 socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
@@ -6507,6 +6577,7 @@ function writeH1 (client, request) {
65076577 }
65086578
65096579 const socket = client[kSocket]
6580+ clearIdleSocketValidation(socket)
65106581
65116582 const abort = (err) => {
65126583 if (request.aborted || request.completed) {
@@ -8376,6 +8447,7 @@ class DispatcherBase extends Dispatcher {
83768447
83778448 get webSocketOptions () {
83788449 return {
8450+ maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
83798451 maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
83808452 }
83818453 }
@@ -14275,32 +14347,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
1427514347 // If the attribute-name case-insensitively matches the string
1427614348 // "SameSite", the user agent MUST process the cookie-av as follows:
1427714349
14278- // 1. Let enforcement be "Default".
14279- let enforcement = 'Default'
14280-
1428114350 const attributeValueLowercase = attributeValue.toLowerCase()
14282- // 2. If cookie-av's attribute-value is a case-insensitive match for
14283- // "None", set enforcement to "None".
14284- if (attributeValueLowercase.includes('none')) {
14285- enforcement = 'None'
14286- }
1428714351
14288- // 3. If cookie-av's attribute-value is a case-insensitive match for
14289- // "Strict", set enforcement to "Strict".
14290- if (attributeValueLowercase.includes('strict')) {
14291- enforcement = 'Strict'
14352+ // 1. If cookie-av's attribute-value is a case-insensitive match for
14353+ // "None", append an attribute to the cookie-attribute-list with an
14354+ // attribute-name of "SameSite" and an attribute-value of "None".
14355+ if (attributeValueLowercase === 'none') {
14356+ cookieAttributeList.sameSite = 'None'
14357+ } else if (attributeValueLowercase === 'strict') {
14358+ // 2. If cookie-av's attribute-value is a case-insensitive match for
14359+ // "Strict", append an attribute to the cookie-attribute-list with
14360+ // an attribute-name of "SameSite" and an attribute-value of
14361+ // "Strict".
14362+ cookieAttributeList.sameSite = 'Strict'
14363+ } else if (attributeValueLowercase === 'lax') {
14364+ // 3. If cookie-av's attribute-value is a case-insensitive match for
14365+ // "Lax", append an attribute to the cookie-attribute-list with an
14366+ // attribute-name of "SameSite" and an attribute-value of "Lax".
14367+ cookieAttributeList.sameSite = 'Lax'
1429214368 }
14293-
14294- // 4. If cookie-av's attribute-value is a case-insensitive match for
14295- // "Lax", set enforcement to "Lax".
14296- if (attributeValueLowercase.includes('lax')) {
14297- enforcement = 'Lax'
14298- }
14299-
14300- // 5. Append an attribute to the cookie-attribute-list with an
14301- // attribute-name of "SameSite" and an attribute-value of
14302- // enforcement.
14303- cookieAttributeList.sameSite = enforcement
1430414369 } else {
1430514370 cookieAttributeList.unparsed ??= []
1430614371
@@ -27096,6 +27161,11 @@ const { closeWebSocketConnection } = __nccwpck_require__(6897)
2709627161const { PerMessageDeflate } = __nccwpck_require__(9469)
2709727162const { MessageSizeExceededError } = __nccwpck_require__(8707)
2709827163
27164+ function failWebsocketConnectionWithCode (ws, code, reason) {
27165+ closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
27166+ failWebsocketConnection(ws, reason)
27167+ }
27168+
2709927169// This code was influenced by ws released under the MIT license.
2710027170// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
2710127171// Copyright (c) 2013 Arnout Kazemier and contributors
@@ -27115,19 +27185,23 @@ class ByteParser extends Writable {
2711527185 /** @type {Map<string, PerMessageDeflate>} */
2711627186 #extensions
2711727187
27188+ /** @type {number} */
27189+ #maxFragments
27190+
2711827191 /** @type {number} */
2711927192 #maxPayloadSize
2712027193
2712127194 /**
2712227195 * @param {import('./websocket').WebSocket} ws
2712327196 * @param {Map<string, string>|null} extensions
27124- * @param {{ maxPayloadSize?: number }} [options]
27197+ * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
2712527198 */
2712627199 constructor (ws, extensions, options = {}) {
2712727200 super()
2712827201
2712927202 this.ws = ws
2713027203 this.#extensions = extensions == null ? new Map() : extensions
27204+ this.#maxFragments = options.maxFragments ?? 0
2713127205 this.#maxPayloadSize = options.maxPayloadSize ?? 0
2713227206
2713327207 if (this.#extensions.has('permessage-deflate')) {
@@ -27151,9 +27225,9 @@ class ByteParser extends Writable {
2715127225 if (
2715227226 this.#maxPayloadSize > 0 &&
2715327227 !isControlFrame(this.#info.opcode) &&
27154- this.#info.payloadLength > this.#maxPayloadSize
27228+ this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
2715527229 ) {
27156- failWebsocketConnection (this.ws, 'Payload size exceeds maximum allowed size')
27230+ failWebsocketConnectionWithCode (this.ws, 1009 , 'Payload size exceeds maximum allowed size')
2715727231 return false
2715827232 }
2715927233
@@ -27318,10 +27392,12 @@ class ByteParser extends Writable {
2731827392 this.#state = parserStates.INFO
2731927393 } else {
2732027394 if (!this.#info.compressed) {
27321- this.writeFragments(body)
27395+ if (!this.writeFragments(body)) {
27396+ return
27397+ }
2732227398
2732327399 if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
27324- failWebsocketConnection (this.ws, new MessageSizeExceededError().message)
27400+ failWebsocketConnectionWithCode (this.ws, 1009 , new MessageSizeExceededError().message)
2732527401 return
2732627402 }
2732727403
@@ -27340,14 +27416,17 @@ class ByteParser extends Writable {
2734027416 this.#info.fin,
2734127417 (error, data) => {
2734227418 if (error) {
27343- failWebsocketConnection(this.ws, error.message)
27419+ const code = error instanceof MessageSizeExceededError ? 1009 : 1007
27420+ failWebsocketConnectionWithCode(this.ws, code, error.message)
2734427421 return
2734527422 }
2734627423
27347- this.writeFragments(data)
27424+ if (!this.writeFragments(data)) {
27425+ return
27426+ }
2734827427
2734927428 if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
27350- failWebsocketConnection (this.ws, new MessageSizeExceededError().message)
27429+ failWebsocketConnectionWithCode (this.ws, 1009 , new MessageSizeExceededError().message)
2735127430 return
2735227431 }
2735327432
@@ -27417,8 +27496,17 @@ class ByteParser extends Writable {
2741727496 }
2741827497
2741927498 writeFragments (fragment) {
27499+ if (
27500+ this.#maxFragments > 0 &&
27501+ this.#fragments.length === this.#maxFragments
27502+ ) {
27503+ failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
27504+ return false
27505+ }
27506+
2742027507 this.#fragmentsBytes += fragment.length
2742127508 this.#fragments.push(fragment)
27509+ return true
2742227510 }
2742327511
2742427512 consumeFragments () {
@@ -28467,9 +28555,12 @@ class WebSocket extends EventTarget {
2846728555 // once this happens, the connection is open
2846828556 this[kResponse] = response
2846928557
28470- const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
28558+ const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
28559+ const maxFragments = webSocketOptions?.maxFragments
28560+ const maxPayloadSize = webSocketOptions?.maxPayloadSize
2847128561
2847228562 const parser = new ByteParser(this, parsedExtensions, {
28563+ maxFragments,
2847328564 maxPayloadSize
2847428565 })
2847528566 parser.on('drain', onParserDrain)
0 commit comments