Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 124 additions & 33 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81457,6 +81457,9 @@ const EMPTY_BUF = Buffer.alloc(0)
const FastBuffer = Buffer[Symbol.species]
const addListener = util.addListener
const removeAllListeners = util.removeAllListeners
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
const kSocketUsed = Symbol('kSocketUsed')

let extractBody

Expand Down Expand Up @@ -81771,6 +81774,11 @@ class Parser {
return -1
}

if (client[kRunning] === 0) {
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
return -1
}

const request = client[kQueue][client[kRunningIdx]]
if (!request) {
return -1
Expand Down Expand Up @@ -81874,6 +81882,11 @@ class Parser {
return -1
}

if (client[kRunning] === 0) {
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
return -1
}

const request = client[kQueue][client[kRunningIdx]]

/* istanbul ignore next: difficult to make a test case for */
Expand Down Expand Up @@ -82047,6 +82060,7 @@ class Parser {
request.onComplete(headers)

client[kQueue][client[kRunningIdx]++] = null
socket[kSocketUsed] = true

if (socket[kWriting]) {
assert(client[kRunning] === 0)
Expand Down Expand Up @@ -82105,6 +82119,9 @@ async function connectH1 (client, socket) {
socket[kWriting] = false
socket[kReset] = false
socket[kBlocking] = false
socket[kIdleSocketValidation] = 0
socket[kIdleSocketValidationTimeout] = null
socket[kSocketUsed] = false
socket[kParser] = new Parser(client, socket, llhttpInstance)

addListener(socket, 'error', function (err) {
Expand Down Expand Up @@ -82151,6 +82168,8 @@ async function connectH1 (client, socket) {
const client = this[kClient]
const parser = this[kParser]

clearIdleSocketValidation(this)

if (parser) {
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
this[kError] = parser.finish() || this[kError]
Expand Down Expand Up @@ -82216,7 +82235,7 @@ async function connectH1 (client, socket) {
return socket.destroyed
},
busy (request) {
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
return true
}

Expand Down Expand Up @@ -82254,6 +82273,31 @@ async function connectH1 (client, socket) {
}
}

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

socket[kIdleSocketValidation] = 0
}

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

/**
* @param {import('./client.js')} client
*/
function resumeH1 (client) {
const socket = client[kSocket]

Expand All @@ -82268,6 +82312,32 @@ function resumeH1 (client) {
socket[kNoRef] = false
}

if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
if (socket[kIdleSocketValidation] === 0) {
scheduleIdleSocketValidation(client, socket)
socket[kParser].readMore()
if (socket.destroyed) {
return
}
return
}

if (socket[kIdleSocketValidation] === 1) {
socket[kParser].readMore()
if (socket.destroyed) {
return
}
return
}
}

if (client[kRunning] === 0) {
socket[kParser].readMore()
if (socket.destroyed) {
return
}
}

if (client[kSize] === 0) {
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
Expand Down Expand Up @@ -82361,6 +82431,7 @@ function writeH1 (client, request) {
}

const socket = client[kSocket]
clearIdleSocketValidation(socket)

const abort = (err) => {
if (request.aborted || request.completed) {
Expand Down Expand Up @@ -84233,6 +84304,7 @@ class DispatcherBase extends Dispatcher {

get webSocketOptions () {
return {
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
}
}
Expand Down Expand Up @@ -90169,32 +90241,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
// If the attribute-name case-insensitively matches the string
// "SameSite", the user agent MUST process the cookie-av as follows:

// 1. Let enforcement be "Default".
let enforcement = 'Default'

const attributeValueLowercase = attributeValue.toLowerCase()
// 2. If cookie-av's attribute-value is a case-insensitive match for
// "None", set enforcement to "None".
if (attributeValueLowercase.includes('none')) {
enforcement = 'None'
}

// 3. If cookie-av's attribute-value is a case-insensitive match for
// "Strict", set enforcement to "Strict".
if (attributeValueLowercase.includes('strict')) {
enforcement = 'Strict'
// 1. If cookie-av's attribute-value is a case-insensitive match for
// "None", append an attribute to the cookie-attribute-list with an
// attribute-name of "SameSite" and an attribute-value of "None".
if (attributeValueLowercase === 'none') {
cookieAttributeList.sameSite = 'None'
} else if (attributeValueLowercase === 'strict') {
// 2. If cookie-av's attribute-value is a case-insensitive match for
// "Strict", append an attribute to the cookie-attribute-list with
// an attribute-name of "SameSite" and an attribute-value of
// "Strict".
cookieAttributeList.sameSite = 'Strict'
} else if (attributeValueLowercase === 'lax') {
// 3. If cookie-av's attribute-value is a case-insensitive match for
// "Lax", append an attribute to the cookie-attribute-list with an
// attribute-name of "SameSite" and an attribute-value of "Lax".
cookieAttributeList.sameSite = 'Lax'
}

// 4. If cookie-av's attribute-value is a case-insensitive match for
// "Lax", set enforcement to "Lax".
if (attributeValueLowercase.includes('lax')) {
enforcement = 'Lax'
}

// 5. Append an attribute to the cookie-attribute-list with an
// attribute-name of "SameSite" and an attribute-value of
// enforcement.
cookieAttributeList.sameSite = enforcement
} else {
cookieAttributeList.unparsed ??= []

Expand Down Expand Up @@ -103020,6 +103085,11 @@ const { closeWebSocketConnection } = __nccwpck_require__(6897)
const { PerMessageDeflate } = __nccwpck_require__(9469)
const { MessageSizeExceededError } = __nccwpck_require__(8707)

function failWebsocketConnectionWithCode (ws, code, reason) {
closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
failWebsocketConnection(ws, reason)
}

// This code was influenced by ws released under the MIT license.
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
// Copyright (c) 2013 Arnout Kazemier and contributors
Expand All @@ -103039,19 +103109,23 @@ class ByteParser extends Writable {
/** @type {Map<string, PerMessageDeflate>} */
#extensions

/** @type {number} */
#maxFragments

/** @type {number} */
#maxPayloadSize

/**
* @param {import('./websocket').WebSocket} ws
* @param {Map<string, string>|null} extensions
* @param {{ maxPayloadSize?: number }} [options]
* @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
*/
constructor (ws, extensions, options = {}) {
super()

this.ws = ws
this.#extensions = extensions == null ? new Map() : extensions
this.#maxFragments = options.maxFragments ?? 0
this.#maxPayloadSize = options.maxPayloadSize ?? 0

if (this.#extensions.has('permessage-deflate')) {
Expand All @@ -103075,9 +103149,9 @@ class ByteParser extends Writable {
if (
this.#maxPayloadSize > 0 &&
!isControlFrame(this.#info.opcode) &&
this.#info.payloadLength > this.#maxPayloadSize
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
) {
failWebsocketConnection(this.ws, 'Payload size exceeds maximum allowed size')
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
return false
}

Expand Down Expand Up @@ -103242,10 +103316,12 @@ class ByteParser extends Writable {
this.#state = parserStates.INFO
} else {
if (!this.#info.compressed) {
this.writeFragments(body)
if (!this.writeFragments(body)) {
return
}

if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
return
}

Expand All @@ -103264,14 +103340,17 @@ class ByteParser extends Writable {
this.#info.fin,
(error, data) => {
if (error) {
failWebsocketConnection(this.ws, error.message)
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
failWebsocketConnectionWithCode(this.ws, code, error.message)
return
}

this.writeFragments(data)
if (!this.writeFragments(data)) {
return
}

if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
return
}

Expand Down Expand Up @@ -103341,8 +103420,17 @@ class ByteParser extends Writable {
}

writeFragments (fragment) {
if (
this.#maxFragments > 0 &&
this.#fragments.length === this.#maxFragments
) {
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
return false
}

this.#fragmentsBytes += fragment.length
this.#fragments.push(fragment)
return true
}

consumeFragments () {
Expand Down Expand Up @@ -104395,9 +104483,12 @@ class WebSocket extends EventTarget {
// once this happens, the connection is open
this[kResponse] = response

const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
const maxFragments = webSocketOptions?.maxFragments
const maxPayloadSize = webSocketOptions?.maxPayloadSize

const parser = new ByteParser(this, parsedExtensions, {
maxFragments,
maxPayloadSize
})
parser.on('drain', onParserDrain)
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"package": "ncc build ./src/index.js"
},
"overrides": {
"undici": "^6.26.0",
"undici": "^6.27.0",
"test-exclude": "^8.0.0"
},
"dependencies": {
Expand Down