@@ -455,17 +455,21 @@ const replaceTildes = (comp, options) => {
455455
456456const replaceTilde = (comp, options) => {
457457 const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
458+ // if we're including prereleases in the match, then the lower bound is
459+ // -0, the lowest possible prerelease value, just like x-ranges and carets.
460+ // this keeps `~1.2` equivalent to the `1.2.x` x-range it's documented as.
461+ const z = options.includePrerelease ? '-0' : ''
458462 return comp.replace(r, (_, M, m, p, pr) => {
459463 debug('tilde', comp, _, M, m, p, pr)
460464 let ret
461465
462466 if (isX(M)) {
463467 ret = ''
464468 } else if (isX(m)) {
465- ret = `>=${M}.0.0 <${+M + 1}.0.0-0`
469+ ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`
466470 } else if (isX(p)) {
467471 // ~1.2 == >=1.2.0 <1.3.0-0
468- ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`
472+ ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`
469473 } else if (pr) {
470474 debug('replaceTilde pr', pr)
471475 ret = `>=${M}.${m}.${p}-${pr
@@ -7522,6 +7526,9 @@ const EMPTY_BUF = Buffer.alloc(0)
75227526const FastBuffer = Buffer[Symbol.species]
75237527const addListener = util.addListener
75247528const removeAllListeners = util.removeAllListeners
7529+ const kIdleSocketValidation = Symbol('kIdleSocketValidation')
7530+ const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
7531+ const kSocketUsed = Symbol('kSocketUsed')
75257532
75267533let extractBody
75277534
@@ -7836,6 +7843,11 @@ class Parser {
78367843 return -1
78377844 }
78387845
7846+ if (client[kRunning] === 0) {
7847+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
7848+ return -1
7849+ }
7850+
78397851 const request = client[kQueue][client[kRunningIdx]]
78407852 if (!request) {
78417853 return -1
@@ -7939,6 +7951,11 @@ class Parser {
79397951 return -1
79407952 }
79417953
7954+ if (client[kRunning] === 0) {
7955+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
7956+ return -1
7957+ }
7958+
79427959 const request = client[kQueue][client[kRunningIdx]]
79437960
79447961 /* istanbul ignore next: difficult to make a test case for */
@@ -8112,6 +8129,7 @@ class Parser {
81128129 request.onComplete(headers)
81138130
81148131 client[kQueue][client[kRunningIdx]++] = null
8132+ socket[kSocketUsed] = true
81158133
81168134 if (socket[kWriting]) {
81178135 assert(client[kRunning] === 0)
@@ -8170,6 +8188,9 @@ async function connectH1 (client, socket) {
81708188 socket[kWriting] = false
81718189 socket[kReset] = false
81728190 socket[kBlocking] = false
8191+ socket[kIdleSocketValidation] = 0
8192+ socket[kIdleSocketValidationTimeout] = null
8193+ socket[kSocketUsed] = false
81738194 socket[kParser] = new Parser(client, socket, llhttpInstance)
81748195
81758196 addListener(socket, 'error', function (err) {
@@ -8216,6 +8237,8 @@ async function connectH1 (client, socket) {
82168237 const client = this[kClient]
82178238 const parser = this[kParser]
82188239
8240+ clearIdleSocketValidation(this)
8241+
82198242 if (parser) {
82208243 if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
82218244 this[kError] = parser.finish() || this[kError]
@@ -8281,7 +8304,7 @@ async function connectH1 (client, socket) {
82818304 return socket.destroyed
82828305 },
82838306 busy (request) {
8284- if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
8307+ if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1 ) {
82858308 return true
82868309 }
82878310
@@ -8319,6 +8342,31 @@ async function connectH1 (client, socket) {
83198342 }
83208343}
83218344
8345+ function clearIdleSocketValidation (socket) {
8346+ if (socket[kIdleSocketValidationTimeout]) {
8347+ clearTimeout(socket[kIdleSocketValidationTimeout])
8348+ socket[kIdleSocketValidationTimeout] = null
8349+ }
8350+
8351+ socket[kIdleSocketValidation] = 0
8352+ }
8353+
8354+ function scheduleIdleSocketValidation (client, socket) {
8355+ socket[kIdleSocketValidation] = 1
8356+ socket[kIdleSocketValidationTimeout] = setTimeout(() => {
8357+ socket[kIdleSocketValidationTimeout] = null
8358+ socket[kIdleSocketValidation] = 2
8359+
8360+ if (client[kSocket] === socket && !socket.destroyed) {
8361+ client[kResume]()
8362+ }
8363+ }, 0)
8364+ socket[kIdleSocketValidationTimeout].unref?.()
8365+ }
8366+
8367+ /**
8368+ * @param {import('./client.js')} client
8369+ */
83228370function resumeH1 (client) {
83238371 const socket = client[kSocket]
83248372
@@ -8333,6 +8381,32 @@ function resumeH1 (client) {
83338381 socket[kNoRef] = false
83348382 }
83358383
8384+ if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
8385+ if (socket[kIdleSocketValidation] === 0) {
8386+ scheduleIdleSocketValidation(client, socket)
8387+ socket[kParser].readMore()
8388+ if (socket.destroyed) {
8389+ return
8390+ }
8391+ return
8392+ }
8393+
8394+ if (socket[kIdleSocketValidation] === 1) {
8395+ socket[kParser].readMore()
8396+ if (socket.destroyed) {
8397+ return
8398+ }
8399+ return
8400+ }
8401+ }
8402+
8403+ if (client[kRunning] === 0) {
8404+ socket[kParser].readMore()
8405+ if (socket.destroyed) {
8406+ return
8407+ }
8408+ }
8409+
83368410 if (client[kSize] === 0) {
83378411 if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
83388412 socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
@@ -8426,6 +8500,7 @@ function writeH1 (client, request) {
84268500 }
84278501
84288502 const socket = client[kSocket]
8503+ clearIdleSocketValidation(socket)
84298504
84308505 const abort = (err) => {
84318506 if (request.aborted || request.completed) {
@@ -10295,6 +10370,7 @@ class DispatcherBase extends Dispatcher {
1029510370
1029610371 get webSocketOptions () {
1029710372 return {
10373+ maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
1029810374 maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
1029910375 }
1030010376 }
@@ -16194,32 +16270,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
1619416270 // If the attribute-name case-insensitively matches the string
1619516271 // "SameSite", the user agent MUST process the cookie-av as follows:
1619616272
16197- // 1. Let enforcement be "Default".
16198- let enforcement = 'Default'
16199-
1620016273 const attributeValueLowercase = attributeValue.toLowerCase()
16201- // 2. If cookie-av's attribute-value is a case-insensitive match for
16202- // "None", set enforcement to "None".
16203- if (attributeValueLowercase.includes('none')) {
16204- enforcement = 'None'
16205- }
1620616274
16207- // 3. If cookie-av's attribute-value is a case-insensitive match for
16208- // "Strict", set enforcement to "Strict".
16209- if (attributeValueLowercase.includes('strict')) {
16210- enforcement = 'Strict'
16275+ // 1. If cookie-av's attribute-value is a case-insensitive match for
16276+ // "None", append an attribute to the cookie-attribute-list with an
16277+ // attribute-name of "SameSite" and an attribute-value of "None".
16278+ if (attributeValueLowercase === 'none') {
16279+ cookieAttributeList.sameSite = 'None'
16280+ } else if (attributeValueLowercase === 'strict') {
16281+ // 2. If cookie-av's attribute-value is a case-insensitive match for
16282+ // "Strict", append an attribute to the cookie-attribute-list with
16283+ // an attribute-name of "SameSite" and an attribute-value of
16284+ // "Strict".
16285+ cookieAttributeList.sameSite = 'Strict'
16286+ } else if (attributeValueLowercase === 'lax') {
16287+ // 3. If cookie-av's attribute-value is a case-insensitive match for
16288+ // "Lax", append an attribute to the cookie-attribute-list with an
16289+ // attribute-name of "SameSite" and an attribute-value of "Lax".
16290+ cookieAttributeList.sameSite = 'Lax'
1621116291 }
16212-
16213- // 4. If cookie-av's attribute-value is a case-insensitive match for
16214- // "Lax", set enforcement to "Lax".
16215- if (attributeValueLowercase.includes('lax')) {
16216- enforcement = 'Lax'
16217- }
16218-
16219- // 5. Append an attribute to the cookie-attribute-list with an
16220- // attribute-name of "SameSite" and an attribute-value of
16221- // enforcement.
16222- cookieAttributeList.sameSite = enforcement
1622316292 } else {
1622416293 cookieAttributeList.unparsed ??= []
1622516294
@@ -29015,6 +29084,11 @@ const { closeWebSocketConnection } = __nccwpck_require__(6897)
2901529084const { PerMessageDeflate } = __nccwpck_require__(9469)
2901629085const { MessageSizeExceededError } = __nccwpck_require__(8707)
2901729086
29087+ function failWebsocketConnectionWithCode (ws, code, reason) {
29088+ closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
29089+ failWebsocketConnection(ws, reason)
29090+ }
29091+
2901829092// This code was influenced by ws released under the MIT license.
2901929093// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
2902029094// Copyright (c) 2013 Arnout Kazemier and contributors
@@ -29034,19 +29108,23 @@ class ByteParser extends Writable {
2903429108 /** @type {Map<string, PerMessageDeflate>} */
2903529109 #extensions
2903629110
29111+ /** @type {number} */
29112+ #maxFragments
29113+
2903729114 /** @type {number} */
2903829115 #maxPayloadSize
2903929116
2904029117 /**
2904129118 * @param {import('./websocket').WebSocket} ws
2904229119 * @param {Map<string, string>|null} extensions
29043- * @param {{ maxPayloadSize?: number }} [options]
29120+ * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
2904429121 */
2904529122 constructor (ws, extensions, options = {}) {
2904629123 super()
2904729124
2904829125 this.ws = ws
2904929126 this.#extensions = extensions == null ? new Map() : extensions
29127+ this.#maxFragments = options.maxFragments ?? 0
2905029128 this.#maxPayloadSize = options.maxPayloadSize ?? 0
2905129129
2905229130 if (this.#extensions.has('permessage-deflate')) {
@@ -29070,9 +29148,9 @@ class ByteParser extends Writable {
2907029148 if (
2907129149 this.#maxPayloadSize > 0 &&
2907229150 !isControlFrame(this.#info.opcode) &&
29073- this.#info.payloadLength > this.#maxPayloadSize
29151+ this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
2907429152 ) {
29075- failWebsocketConnection (this.ws, 'Payload size exceeds maximum allowed size')
29153+ failWebsocketConnectionWithCode (this.ws, 1009 , 'Payload size exceeds maximum allowed size')
2907629154 return false
2907729155 }
2907829156
@@ -29237,10 +29315,12 @@ class ByteParser extends Writable {
2923729315 this.#state = parserStates.INFO
2923829316 } else {
2923929317 if (!this.#info.compressed) {
29240- this.writeFragments(body)
29318+ if (!this.writeFragments(body)) {
29319+ return
29320+ }
2924129321
2924229322 if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
29243- failWebsocketConnection (this.ws, new MessageSizeExceededError().message)
29323+ failWebsocketConnectionWithCode (this.ws, 1009 , new MessageSizeExceededError().message)
2924429324 return
2924529325 }
2924629326
@@ -29259,14 +29339,17 @@ class ByteParser extends Writable {
2925929339 this.#info.fin,
2926029340 (error, data) => {
2926129341 if (error) {
29262- failWebsocketConnection(this.ws, error.message)
29342+ const code = error instanceof MessageSizeExceededError ? 1009 : 1007
29343+ failWebsocketConnectionWithCode(this.ws, code, error.message)
2926329344 return
2926429345 }
2926529346
29266- this.writeFragments(data)
29347+ if (!this.writeFragments(data)) {
29348+ return
29349+ }
2926729350
2926829351 if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
29269- failWebsocketConnection (this.ws, new MessageSizeExceededError().message)
29352+ failWebsocketConnectionWithCode (this.ws, 1009 , new MessageSizeExceededError().message)
2927029353 return
2927129354 }
2927229355
@@ -29336,8 +29419,17 @@ class ByteParser extends Writable {
2933629419 }
2933729420
2933829421 writeFragments (fragment) {
29422+ if (
29423+ this.#maxFragments > 0 &&
29424+ this.#fragments.length === this.#maxFragments
29425+ ) {
29426+ failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
29427+ return false
29428+ }
29429+
2933929430 this.#fragmentsBytes += fragment.length
2934029431 this.#fragments.push(fragment)
29432+ return true
2934129433 }
2934229434
2934329435 consumeFragments () {
@@ -30386,9 +30478,12 @@ class WebSocket extends EventTarget {
3038630478 // once this happens, the connection is open
3038730479 this[kResponse] = response
3038830480
30389- const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
30481+ const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
30482+ const maxFragments = webSocketOptions?.maxFragments
30483+ const maxPayloadSize = webSocketOptions?.maxPayloadSize
3039030484
3039130485 const parser = new ByteParser(this, parsedExtensions, {
30486+ maxFragments,
3039230487 maxPayloadSize
3039330488 })
3039430489 parser.on('drain', onParserDrain)
0 commit comments