mirror of
https://github.com/hoverkraft-tech/compose-action.git
synced 2026-07-03 12:12:50 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ac4723199 | ||
|
|
9b4690b9b1 | ||
|
|
e56e65d179 | ||
|
|
e1257b4b9f | ||
|
|
68d3d674a0 |
@ -3,7 +3,7 @@
|
||||
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:2": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:3": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
},
|
||||
"remoteEnv": {
|
||||
|
||||
2
.github/workflows/__check-dist.yml
vendored
2
.github/workflows/__check-dist.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- id: setup-node
|
||||
uses: hoverkraft-tech/ci-github-nodejs/actions/setup-node@6b74a8f070140f5c120f78026d58e4c00d1b1e37 # 0.24.2
|
||||
uses: hoverkraft-tech/ci-github-nodejs/actions/setup-node@df348077afa4e79725151d50606e9dc63f86dcb6 # 0.24.4
|
||||
|
||||
- name: Build dist/ Directory
|
||||
id: package
|
||||
|
||||
2
.github/workflows/__check-nodejs.yml
vendored
2
.github/workflows/__check-nodejs.yml
vendored
@ -7,7 +7,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
test-nodejs:
|
||||
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/continuous-integration.yml@6b74a8f070140f5c120f78026d58e4c00d1b1e37 # 0.24.2
|
||||
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/continuous-integration.yml@df348077afa4e79725151d50606e9dc63f86dcb6 # 0.24.4
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
5
.github/workflows/__shared-ci.yml
vendored
5
.github/workflows/__shared-ci.yml
vendored
@ -7,10 +7,13 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
linter:
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/linter.yml@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/linter.yml@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
issues: write
|
||||
packages: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
statuses: write
|
||||
with:
|
||||
|
||||
2
.github/workflows/greetings.yml
vendored
2
.github/workflows/greetings.yml
vendored
@ -10,7 +10,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
greetings:
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/greetings.yml@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/greetings.yml@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
3
.github/workflows/main-ci.yml
vendored
3
.github/workflows/main-ci.yml
vendored
@ -23,6 +23,7 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
issues: write
|
||||
packages: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
@ -66,7 +67,7 @@ jobs:
|
||||
client-id: ${{ vars.CI_BOT_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.CI_BOT_APP_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] repository automation uses a dedicated app secret without untrusted code execution
|
||||
|
||||
- uses: hoverkraft-tech/ci-github-common/actions/create-and-merge-pull-request@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
- uses: hoverkraft-tech/ci-github-common/actions/create-and-merge-pull-request@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
branch: docs/actions-workflows-documentation-update
|
||||
|
||||
2
.github/workflows/need-fix-to-issue.yml
vendored
2
.github/workflows/need-fix-to-issue.yml
vendored
@ -20,7 +20,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/need-fix-to-issue.yml@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/need-fix-to-issue.yml@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
2
.github/workflows/prepare-release.yml
vendored
2
.github/workflows/prepare-release.yml
vendored
@ -15,7 +15,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: hoverkraft-tech/ci-github-publish/.github/workflows/prepare-release.yml@84d583ba7b357f9476707f54cf5419d630ae0145 # 0.26.2
|
||||
uses: hoverkraft-tech/ci-github-publish/.github/workflows/prepare-release.yml@b2562b46714e535a0113f90f554b55e1248212c1 # 0.26.3
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
1
.github/workflows/pull-request-ci.yml
vendored
1
.github/workflows/pull-request-ci.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
issues: write
|
||||
packages: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
|
||||
2
.github/workflows/semantic-pull-request.yml
vendored
2
.github/workflows/semantic-pull-request.yml
vendored
@ -11,7 +11,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/semantic-pull-request.yml@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/semantic-pull-request.yml@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -8,7 +8,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/stale.yml@4bb7594b1bf3696c54b2bbae970376056853f8ea # 0.36.0
|
||||
uses: hoverkraft-tech/ci-github-common/.github/workflows/stale.yml@624be17604ee0a7378488191aacb35851e7cf001 # 0.37.1
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
12
README.md
12
README.md
@ -50,7 +50,7 @@ Some extra options can be passed to the `docker compose down` command using the
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
- uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
- uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
# Additional options to pass to `docker` command.
|
||||
docker-flags: ""
|
||||
@ -140,7 +140,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Run docker compose
|
||||
uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
compose-file: "./docker/docker-compose.yml"
|
||||
|
||||
@ -154,7 +154,7 @@ jobs:
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
- uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
compose-file: "./docker/docker-compose.yml"
|
||||
env:
|
||||
@ -169,7 +169,7 @@ Perform `docker compose up` to some given service instead of all of them
|
||||
steps:
|
||||
# need checkout before using compose-action
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
- uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
compose-file: "./docker/docker-compose.yml"
|
||||
services: |
|
||||
@ -207,7 +207,7 @@ A full list of flags can be found in the [Docker compose documentation](https://
|
||||
steps:
|
||||
# need checkout before using compose-action
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
- uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
compose-file: "./docker/docker-compose.yml"
|
||||
compose-flags: "--profile profile-1"
|
||||
@ -221,7 +221,7 @@ This is useful when you have a base compose file and additional files for differ
|
||||
steps:
|
||||
# need checkout before using compose-action
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
|
||||
- uses: hoverkraft-tech/compose-action@11beaa1c2dae4e8ed7b1665aa074723b6cecb0e4 # v3.0.0
|
||||
with:
|
||||
compose-file: |
|
||||
./docker/docker-compose.yml
|
||||
|
||||
415
dist/index.js
generated
vendored
415
dist/index.js
generated
vendored
@ -8451,8 +8451,6 @@ function defaultFactory (origin, opts) {
|
||||
|
||||
class Agent extends DispatcherBase {
|
||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||
super()
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('factory must be a function.')
|
||||
}
|
||||
@ -8465,6 +8463,8 @@ class Agent extends DispatcherBase {
|
||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||
}
|
||||
|
||||
super(options)
|
||||
|
||||
if (connect && typeof connect !== 'function') {
|
||||
connect = { ...connect }
|
||||
}
|
||||
@ -8836,6 +8836,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
|
||||
|
||||
@ -9058,29 +9061,71 @@ class Parser {
|
||||
|
||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
||||
|
||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.onUpgrade(data.slice(offset))
|
||||
} else if (ret === constants.ERROR.PAUSED) {
|
||||
this.paused = true
|
||||
socket.unshift(data.slice(offset))
|
||||
} else if (ret !== constants.ERROR.OK) {
|
||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||
let message = ''
|
||||
/* istanbul ignore else: difficult to make a test case for */
|
||||
if (ptr) {
|
||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||
message =
|
||||
'Response does not match the HTTP/1.1 protocol (' +
|
||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||
')'
|
||||
if (ret !== constants.ERROR.OK) {
|
||||
const body = data.subarray(offset)
|
||||
|
||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.onUpgrade(body)
|
||||
} else if (ret === constants.ERROR.PAUSED) {
|
||||
this.paused = true
|
||||
socket.unshift(body)
|
||||
} else {
|
||||
throw this.createError(ret, body)
|
||||
}
|
||||
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
||||
}
|
||||
} catch (err) {
|
||||
util.destroy(socket, err)
|
||||
}
|
||||
}
|
||||
|
||||
finish () {
|
||||
assert(currentParser === null)
|
||||
assert(this.ptr != null)
|
||||
assert(!this.paused)
|
||||
|
||||
const { llhttp } = this
|
||||
|
||||
let ret
|
||||
|
||||
try {
|
||||
currentParser = this
|
||||
ret = llhttp.llhttp_finish(this.ptr)
|
||||
} finally {
|
||||
currentParser = null
|
||||
}
|
||||
|
||||
if (ret === constants.ERROR.OK) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.paused = true
|
||||
return null
|
||||
}
|
||||
|
||||
return this.createError(ret, EMPTY_BUF)
|
||||
}
|
||||
|
||||
createError (ret, data) {
|
||||
const { llhttp, contentLength, bytesRead } = this
|
||||
|
||||
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
||||
return new ResponseContentLengthMismatchError()
|
||||
}
|
||||
|
||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||
let message = ''
|
||||
if (ptr) {
|
||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||
message =
|
||||
'Response does not match the HTTP/1.1 protocol (' +
|
||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||
')'
|
||||
}
|
||||
|
||||
return new HTTPParserError(message, constants.ERROR[ret], data)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
assert(this.ptr != null)
|
||||
assert(currentParser == null)
|
||||
@ -9108,6 +9153,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
|
||||
@ -9211,6 +9261,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 */
|
||||
@ -9384,6 +9439,7 @@ class Parser {
|
||||
request.onComplete(headers)
|
||||
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
socket[kSocketUsed] = true
|
||||
|
||||
if (socket[kWriting]) {
|
||||
assert(client[kRunning] === 0)
|
||||
@ -9442,6 +9498,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) {
|
||||
@ -9452,8 +9511,11 @@ async function connectH1 (client, socket) {
|
||||
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
||||
// to the user.
|
||||
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so for as a valid response.
|
||||
parser.onMessageComplete()
|
||||
const parserErr = parser.finish()
|
||||
if (parserErr) {
|
||||
this[kError] = parserErr
|
||||
this[kClient][kOnError](parserErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -9472,8 +9534,10 @@ async function connectH1 (client, socket) {
|
||||
const parser = this[kParser]
|
||||
|
||||
if (parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so far as a valid response.
|
||||
parser.onMessageComplete()
|
||||
const parserErr = parser.finish()
|
||||
if (parserErr) {
|
||||
util.destroy(this, parserErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -9483,10 +9547,11 @@ async function connectH1 (client, socket) {
|
||||
const client = this[kClient]
|
||||
const parser = this[kParser]
|
||||
|
||||
clearIdleSocketValidation(this)
|
||||
|
||||
if (parser) {
|
||||
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so far as a valid response.
|
||||
parser.onMessageComplete()
|
||||
this[kError] = parser.finish() || this[kError]
|
||||
}
|
||||
|
||||
this[kParser].destroy()
|
||||
@ -9549,7 +9614,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
|
||||
}
|
||||
|
||||
@ -9587,6 +9652,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]
|
||||
|
||||
@ -9601,6 +9691,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)
|
||||
@ -9694,6 +9810,7 @@ function writeH1 (client, request) {
|
||||
}
|
||||
|
||||
const socket = client[kSocket]
|
||||
clearIdleSocketValidation(socket)
|
||||
|
||||
const abort = (err) => {
|
||||
if (request.aborted || request.completed) {
|
||||
@ -11013,9 +11130,10 @@ class Client extends DispatcherBase {
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
// h2
|
||||
maxConcurrentStreams,
|
||||
allowH2
|
||||
allowH2,
|
||||
webSocket
|
||||
} = {}) {
|
||||
super()
|
||||
super({ webSocket })
|
||||
|
||||
if (keepAlive !== undefined) {
|
||||
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
||||
@ -11547,15 +11665,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
|
||||
const kOnDestroyed = Symbol('onDestroyed')
|
||||
const kOnClosed = Symbol('onClosed')
|
||||
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
||||
const kWebSocketOptions = Symbol('webSocketOptions')
|
||||
|
||||
class DispatcherBase extends Dispatcher {
|
||||
constructor () {
|
||||
constructor (opts) {
|
||||
super()
|
||||
|
||||
this[kDestroyed] = false
|
||||
this[kOnDestroyed] = null
|
||||
this[kClosed] = false
|
||||
this[kOnClosed] = []
|
||||
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
||||
}
|
||||
|
||||
get webSocketOptions () {
|
||||
return {
|
||||
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
||||
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
get destroyed () {
|
||||
@ -12115,8 +12242,8 @@ const kRemoveClient = Symbol('remove client')
|
||||
const kStats = Symbol('stats')
|
||||
|
||||
class PoolBase extends DispatcherBase {
|
||||
constructor () {
|
||||
super()
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
|
||||
this[kQueue] = new FixedQueue()
|
||||
this[kClients] = []
|
||||
@ -12375,8 +12502,6 @@ class Pool extends PoolBase {
|
||||
allowH2,
|
||||
...options
|
||||
} = {}) {
|
||||
super()
|
||||
|
||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||
throw new InvalidArgumentError('invalid connections')
|
||||
}
|
||||
@ -12401,6 +12526,8 @@ class Pool extends PoolBase {
|
||||
})
|
||||
}
|
||||
|
||||
super(options)
|
||||
|
||||
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
||||
? options.interceptors.Pool
|
||||
: []
|
||||
@ -17453,32 +17580,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 ??= []
|
||||
|
||||
@ -30155,40 +30275,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
||||
const kBuffer = Symbol('kBuffer')
|
||||
const kLength = Symbol('kLength')
|
||||
|
||||
// Default maximum decompressed message size: 4 MB
|
||||
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
||||
|
||||
class PerMessageDeflate {
|
||||
/** @type {import('node:zlib').InflateRaw} */
|
||||
#inflate
|
||||
|
||||
#options = {}
|
||||
|
||||
/** @type {boolean} */
|
||||
#aborted = false
|
||||
|
||||
/** @type {Function|null} */
|
||||
#currentCallback = null
|
||||
#maxPayloadSize = 0
|
||||
|
||||
/**
|
||||
* @param {Map<string, string>} extensions
|
||||
*/
|
||||
constructor (extensions) {
|
||||
constructor (extensions, options) {
|
||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||
|
||||
this.#maxPayloadSize = options.maxPayloadSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress a compressed payload.
|
||||
* @param {Buffer} chunk Compressed data
|
||||
* @param {boolean} fin Final fragment flag
|
||||
* @param {Function} callback Callback function
|
||||
*/
|
||||
decompress (chunk, fin, callback) {
|
||||
// An endpoint uses the following algorithm to decompress a message.
|
||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
// payload of the message.
|
||||
// 2. Decompress the resulting data using DEFLATE.
|
||||
|
||||
if (this.#aborted) {
|
||||
callback(new MessageSizeExceededError())
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.#inflate) {
|
||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||
|
||||
@ -30211,23 +30326,12 @@ class PerMessageDeflate {
|
||||
this.#inflate[kLength] = 0
|
||||
|
||||
this.#inflate.on('data', (data) => {
|
||||
if (this.#aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#inflate[kLength] += data.length
|
||||
|
||||
if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
|
||||
this.#aborted = true
|
||||
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||
callback(new MessageSizeExceededError())
|
||||
this.#inflate.removeAllListeners()
|
||||
this.#inflate.destroy()
|
||||
this.#inflate = null
|
||||
|
||||
if (this.#currentCallback) {
|
||||
const cb = this.#currentCallback
|
||||
this.#currentCallback = null
|
||||
cb(new MessageSizeExceededError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -30240,14 +30344,13 @@ class PerMessageDeflate {
|
||||
})
|
||||
}
|
||||
|
||||
this.#currentCallback = callback
|
||||
this.#inflate.write(chunk)
|
||||
if (fin) {
|
||||
this.#inflate.write(tail)
|
||||
}
|
||||
|
||||
this.#inflate.flush(() => {
|
||||
if (this.#aborted || !this.#inflate) {
|
||||
if (!this.#inflate) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -30255,7 +30358,6 @@ class PerMessageDeflate {
|
||||
|
||||
this.#inflate[kBuffer].length = 0
|
||||
this.#inflate[kLength] = 0
|
||||
this.#currentCallback = null
|
||||
|
||||
callback(null, full)
|
||||
})
|
||||
@ -30290,6 +30392,12 @@ const {
|
||||
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
||||
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>
|
||||
@ -30298,6 +30406,7 @@ const { PerMessageDeflate } = __nccwpck_require__(9469)
|
||||
|
||||
class ByteParser extends Writable {
|
||||
#buffers = []
|
||||
#fragmentsBytes = 0
|
||||
#byteOffset = 0
|
||||
#loop = false
|
||||
|
||||
@ -30309,18 +30418,27 @@ 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 {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
||||
*/
|
||||
constructor (ws, extensions) {
|
||||
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')) {
|
||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
||||
}
|
||||
}
|
||||
|
||||
@ -30336,6 +30454,19 @@ class ByteParser extends Writable {
|
||||
this.run(callback)
|
||||
}
|
||||
|
||||
#validatePayloadLength () {
|
||||
if (
|
||||
this.#maxPayloadSize > 0 &&
|
||||
!isControlFrame(this.#info.opcode) &&
|
||||
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
||||
) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs whenever a new chunk is received.
|
||||
* Callback is called whenever there are no more chunks buffering,
|
||||
@ -30424,6 +30555,10 @@ class ByteParser extends Writable {
|
||||
if (payloadLength <= 125) {
|
||||
this.#info.payloadLength = payloadLength
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (payloadLength === 126) {
|
||||
this.#state = parserStates.PAYLOADLENGTH_16
|
||||
} else if (payloadLength === 127) {
|
||||
@ -30448,6 +30583,10 @@ class ByteParser extends Writable {
|
||||
|
||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||
if (this.#byteOffset < 8) {
|
||||
return callback()
|
||||
@ -30470,6 +30609,10 @@ class ByteParser extends Writable {
|
||||
|
||||
this.#info.payloadLength = lower
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (this.#state === parserStates.READ_DATA) {
|
||||
if (this.#byteOffset < this.#info.payloadLength) {
|
||||
return callback()
|
||||
@ -30482,42 +30625,58 @@ class ByteParser extends Writable {
|
||||
this.#state = parserStates.INFO
|
||||
} else {
|
||||
if (!this.#info.compressed) {
|
||||
this.#fragments.push(body)
|
||||
if (!this.writeFragments(body)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||
return
|
||||
}
|
||||
|
||||
// If the frame is not fragmented, a message has been received.
|
||||
// If the frame is fragmented, it will terminate with a fin bit set
|
||||
// and an opcode of 0 (continuation), therefore we handle that when
|
||||
// parsing continuation frames, not here.
|
||||
if (!this.#info.fragmented && this.#info.fin) {
|
||||
const fullMessage = Buffer.concat(this.#fragments)
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
||||
this.#fragments.length = 0
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||
}
|
||||
|
||||
this.#state = parserStates.INFO
|
||||
} else {
|
||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
||||
if (error) {
|
||||
failWebsocketConnection(this.ws, error.message)
|
||||
return
|
||||
}
|
||||
this.#extensions.get('permessage-deflate').decompress(
|
||||
body,
|
||||
this.#info.fin,
|
||||
(error, data) => {
|
||||
if (error) {
|
||||
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
||||
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
||||
return
|
||||
}
|
||||
|
||||
this.#fragments.push(data)
|
||||
if (!this.writeFragments(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = true
|
||||
this.run(callback)
|
||||
return
|
||||
}
|
||||
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = true
|
||||
this.#state = parserStates.INFO
|
||||
this.run(callback)
|
||||
return
|
||||
}
|
||||
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
||||
|
||||
this.#loop = true
|
||||
this.#state = parserStates.INFO
|
||||
this.#fragments.length = 0
|
||||
this.run(callback)
|
||||
})
|
||||
)
|
||||
|
||||
this.#loop = false
|
||||
break
|
||||
@ -30569,6 +30728,35 @@ class ByteParser extends Writable {
|
||||
return buffer
|
||||
}
|
||||
|
||||
writeFragments (fragment) {
|
||||
if (
|
||||
this.#maxFragments > 0 &&
|
||||
this.#fragments.length === this.#maxFragments
|
||||
) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
||||
return false
|
||||
}
|
||||
|
||||
this.#fragmentsBytes += fragment.length
|
||||
this.#fragments.push(fragment)
|
||||
return true
|
||||
}
|
||||
|
||||
consumeFragments () {
|
||||
const fragments = this.#fragments
|
||||
|
||||
if (fragments.length === 1) {
|
||||
this.#fragmentsBytes = 0
|
||||
return fragments.shift()
|
||||
}
|
||||
|
||||
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
||||
this.#fragments = []
|
||||
this.#fragmentsBytes = 0
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
parseCloseBody (data) {
|
||||
assert(data.length !== 1)
|
||||
|
||||
@ -31600,7 +31788,14 @@ class WebSocket extends EventTarget {
|
||||
// once this happens, the connection is open
|
||||
this[kResponse] = response
|
||||
|
||||
const parser = new ByteParser(this, parsedExtensions)
|
||||
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
||||
const maxFragments = webSocketOptions?.maxFragments
|
||||
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
||||
|
||||
const parser = new ByteParser(this, parsedExtensions, {
|
||||
maxFragments,
|
||||
maxPayloadSize
|
||||
})
|
||||
parser.on('drain', onParserDrain)
|
||||
parser.on('error', onParserError.bind(this))
|
||||
|
||||
|
||||
415
dist/post.js
generated
vendored
415
dist/post.js
generated
vendored
@ -4940,8 +4940,6 @@ function defaultFactory (origin, opts) {
|
||||
|
||||
class Agent extends DispatcherBase {
|
||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||
super()
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('factory must be a function.')
|
||||
}
|
||||
@ -4954,6 +4952,8 @@ class Agent extends DispatcherBase {
|
||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||
}
|
||||
|
||||
super(options)
|
||||
|
||||
if (connect && typeof connect !== 'function') {
|
||||
connect = { ...connect }
|
||||
}
|
||||
@ -5325,6 +5325,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
|
||||
|
||||
@ -5547,29 +5550,71 @@ class Parser {
|
||||
|
||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
||||
|
||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.onUpgrade(data.slice(offset))
|
||||
} else if (ret === constants.ERROR.PAUSED) {
|
||||
this.paused = true
|
||||
socket.unshift(data.slice(offset))
|
||||
} else if (ret !== constants.ERROR.OK) {
|
||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||
let message = ''
|
||||
/* istanbul ignore else: difficult to make a test case for */
|
||||
if (ptr) {
|
||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||
message =
|
||||
'Response does not match the HTTP/1.1 protocol (' +
|
||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||
')'
|
||||
if (ret !== constants.ERROR.OK) {
|
||||
const body = data.subarray(offset)
|
||||
|
||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.onUpgrade(body)
|
||||
} else if (ret === constants.ERROR.PAUSED) {
|
||||
this.paused = true
|
||||
socket.unshift(body)
|
||||
} else {
|
||||
throw this.createError(ret, body)
|
||||
}
|
||||
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
||||
}
|
||||
} catch (err) {
|
||||
util.destroy(socket, err)
|
||||
}
|
||||
}
|
||||
|
||||
finish () {
|
||||
assert(currentParser === null)
|
||||
assert(this.ptr != null)
|
||||
assert(!this.paused)
|
||||
|
||||
const { llhttp } = this
|
||||
|
||||
let ret
|
||||
|
||||
try {
|
||||
currentParser = this
|
||||
ret = llhttp.llhttp_finish(this.ptr)
|
||||
} finally {
|
||||
currentParser = null
|
||||
}
|
||||
|
||||
if (ret === constants.ERROR.OK) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||
this.paused = true
|
||||
return null
|
||||
}
|
||||
|
||||
return this.createError(ret, EMPTY_BUF)
|
||||
}
|
||||
|
||||
createError (ret, data) {
|
||||
const { llhttp, contentLength, bytesRead } = this
|
||||
|
||||
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
||||
return new ResponseContentLengthMismatchError()
|
||||
}
|
||||
|
||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||
let message = ''
|
||||
if (ptr) {
|
||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||
message =
|
||||
'Response does not match the HTTP/1.1 protocol (' +
|
||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||
')'
|
||||
}
|
||||
|
||||
return new HTTPParserError(message, constants.ERROR[ret], data)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
assert(this.ptr != null)
|
||||
assert(currentParser == null)
|
||||
@ -5597,6 +5642,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
|
||||
@ -5700,6 +5750,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 */
|
||||
@ -5873,6 +5928,7 @@ class Parser {
|
||||
request.onComplete(headers)
|
||||
|
||||
client[kQueue][client[kRunningIdx]++] = null
|
||||
socket[kSocketUsed] = true
|
||||
|
||||
if (socket[kWriting]) {
|
||||
assert(client[kRunning] === 0)
|
||||
@ -5931,6 +5987,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) {
|
||||
@ -5941,8 +6000,11 @@ async function connectH1 (client, socket) {
|
||||
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
||||
// to the user.
|
||||
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so for as a valid response.
|
||||
parser.onMessageComplete()
|
||||
const parserErr = parser.finish()
|
||||
if (parserErr) {
|
||||
this[kError] = parserErr
|
||||
this[kClient][kOnError](parserErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -5961,8 +6023,10 @@ async function connectH1 (client, socket) {
|
||||
const parser = this[kParser]
|
||||
|
||||
if (parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so far as a valid response.
|
||||
parser.onMessageComplete()
|
||||
const parserErr = parser.finish()
|
||||
if (parserErr) {
|
||||
util.destroy(this, parserErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -5972,10 +6036,11 @@ async function connectH1 (client, socket) {
|
||||
const client = this[kClient]
|
||||
const parser = this[kParser]
|
||||
|
||||
clearIdleSocketValidation(this)
|
||||
|
||||
if (parser) {
|
||||
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
||||
// We treat all incoming data so far as a valid response.
|
||||
parser.onMessageComplete()
|
||||
this[kError] = parser.finish() || this[kError]
|
||||
}
|
||||
|
||||
this[kParser].destroy()
|
||||
@ -6038,7 +6103,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
|
||||
}
|
||||
|
||||
@ -6076,6 +6141,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]
|
||||
|
||||
@ -6090,6 +6180,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)
|
||||
@ -6183,6 +6299,7 @@ function writeH1 (client, request) {
|
||||
}
|
||||
|
||||
const socket = client[kSocket]
|
||||
clearIdleSocketValidation(socket)
|
||||
|
||||
const abort = (err) => {
|
||||
if (request.aborted || request.completed) {
|
||||
@ -7502,9 +7619,10 @@ class Client extends DispatcherBase {
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
// h2
|
||||
maxConcurrentStreams,
|
||||
allowH2
|
||||
allowH2,
|
||||
webSocket
|
||||
} = {}) {
|
||||
super()
|
||||
super({ webSocket })
|
||||
|
||||
if (keepAlive !== undefined) {
|
||||
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
||||
@ -8036,15 +8154,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
|
||||
const kOnDestroyed = Symbol('onDestroyed')
|
||||
const kOnClosed = Symbol('onClosed')
|
||||
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
||||
const kWebSocketOptions = Symbol('webSocketOptions')
|
||||
|
||||
class DispatcherBase extends Dispatcher {
|
||||
constructor () {
|
||||
constructor (opts) {
|
||||
super()
|
||||
|
||||
this[kDestroyed] = false
|
||||
this[kOnDestroyed] = null
|
||||
this[kClosed] = false
|
||||
this[kOnClosed] = []
|
||||
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
||||
}
|
||||
|
||||
get webSocketOptions () {
|
||||
return {
|
||||
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
||||
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
get destroyed () {
|
||||
@ -8604,8 +8731,8 @@ const kRemoveClient = Symbol('remove client')
|
||||
const kStats = Symbol('stats')
|
||||
|
||||
class PoolBase extends DispatcherBase {
|
||||
constructor () {
|
||||
super()
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
|
||||
this[kQueue] = new FixedQueue()
|
||||
this[kClients] = []
|
||||
@ -8864,8 +8991,6 @@ class Pool extends PoolBase {
|
||||
allowH2,
|
||||
...options
|
||||
} = {}) {
|
||||
super()
|
||||
|
||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||
throw new InvalidArgumentError('invalid connections')
|
||||
}
|
||||
@ -8890,6 +9015,8 @@ class Pool extends PoolBase {
|
||||
})
|
||||
}
|
||||
|
||||
super(options)
|
||||
|
||||
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
||||
? options.interceptors.Pool
|
||||
: []
|
||||
@ -13942,32 +14069,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 ??= []
|
||||
|
||||
@ -26644,40 +26764,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
||||
const kBuffer = Symbol('kBuffer')
|
||||
const kLength = Symbol('kLength')
|
||||
|
||||
// Default maximum decompressed message size: 4 MB
|
||||
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
||||
|
||||
class PerMessageDeflate {
|
||||
/** @type {import('node:zlib').InflateRaw} */
|
||||
#inflate
|
||||
|
||||
#options = {}
|
||||
|
||||
/** @type {boolean} */
|
||||
#aborted = false
|
||||
|
||||
/** @type {Function|null} */
|
||||
#currentCallback = null
|
||||
#maxPayloadSize = 0
|
||||
|
||||
/**
|
||||
* @param {Map<string, string>} extensions
|
||||
*/
|
||||
constructor (extensions) {
|
||||
constructor (extensions, options) {
|
||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||
|
||||
this.#maxPayloadSize = options.maxPayloadSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress a compressed payload.
|
||||
* @param {Buffer} chunk Compressed data
|
||||
* @param {boolean} fin Final fragment flag
|
||||
* @param {Function} callback Callback function
|
||||
*/
|
||||
decompress (chunk, fin, callback) {
|
||||
// An endpoint uses the following algorithm to decompress a message.
|
||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
// payload of the message.
|
||||
// 2. Decompress the resulting data using DEFLATE.
|
||||
|
||||
if (this.#aborted) {
|
||||
callback(new MessageSizeExceededError())
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.#inflate) {
|
||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||
|
||||
@ -26700,23 +26815,12 @@ class PerMessageDeflate {
|
||||
this.#inflate[kLength] = 0
|
||||
|
||||
this.#inflate.on('data', (data) => {
|
||||
if (this.#aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#inflate[kLength] += data.length
|
||||
|
||||
if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
|
||||
this.#aborted = true
|
||||
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||
callback(new MessageSizeExceededError())
|
||||
this.#inflate.removeAllListeners()
|
||||
this.#inflate.destroy()
|
||||
this.#inflate = null
|
||||
|
||||
if (this.#currentCallback) {
|
||||
const cb = this.#currentCallback
|
||||
this.#currentCallback = null
|
||||
cb(new MessageSizeExceededError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -26729,14 +26833,13 @@ class PerMessageDeflate {
|
||||
})
|
||||
}
|
||||
|
||||
this.#currentCallback = callback
|
||||
this.#inflate.write(chunk)
|
||||
if (fin) {
|
||||
this.#inflate.write(tail)
|
||||
}
|
||||
|
||||
this.#inflate.flush(() => {
|
||||
if (this.#aborted || !this.#inflate) {
|
||||
if (!this.#inflate) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -26744,7 +26847,6 @@ class PerMessageDeflate {
|
||||
|
||||
this.#inflate[kBuffer].length = 0
|
||||
this.#inflate[kLength] = 0
|
||||
this.#currentCallback = null
|
||||
|
||||
callback(null, full)
|
||||
})
|
||||
@ -26779,6 +26881,12 @@ const {
|
||||
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
||||
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>
|
||||
@ -26787,6 +26895,7 @@ const { PerMessageDeflate } = __nccwpck_require__(9469)
|
||||
|
||||
class ByteParser extends Writable {
|
||||
#buffers = []
|
||||
#fragmentsBytes = 0
|
||||
#byteOffset = 0
|
||||
#loop = false
|
||||
|
||||
@ -26798,18 +26907,27 @@ 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 {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
||||
*/
|
||||
constructor (ws, extensions) {
|
||||
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')) {
|
||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
||||
}
|
||||
}
|
||||
|
||||
@ -26825,6 +26943,19 @@ class ByteParser extends Writable {
|
||||
this.run(callback)
|
||||
}
|
||||
|
||||
#validatePayloadLength () {
|
||||
if (
|
||||
this.#maxPayloadSize > 0 &&
|
||||
!isControlFrame(this.#info.opcode) &&
|
||||
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
||||
) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs whenever a new chunk is received.
|
||||
* Callback is called whenever there are no more chunks buffering,
|
||||
@ -26913,6 +27044,10 @@ class ByteParser extends Writable {
|
||||
if (payloadLength <= 125) {
|
||||
this.#info.payloadLength = payloadLength
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (payloadLength === 126) {
|
||||
this.#state = parserStates.PAYLOADLENGTH_16
|
||||
} else if (payloadLength === 127) {
|
||||
@ -26937,6 +27072,10 @@ class ByteParser extends Writable {
|
||||
|
||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||
if (this.#byteOffset < 8) {
|
||||
return callback()
|
||||
@ -26959,6 +27098,10 @@ class ByteParser extends Writable {
|
||||
|
||||
this.#info.payloadLength = lower
|
||||
this.#state = parserStates.READ_DATA
|
||||
|
||||
if (!this.#validatePayloadLength()) {
|
||||
return
|
||||
}
|
||||
} else if (this.#state === parserStates.READ_DATA) {
|
||||
if (this.#byteOffset < this.#info.payloadLength) {
|
||||
return callback()
|
||||
@ -26971,42 +27114,58 @@ class ByteParser extends Writable {
|
||||
this.#state = parserStates.INFO
|
||||
} else {
|
||||
if (!this.#info.compressed) {
|
||||
this.#fragments.push(body)
|
||||
if (!this.writeFragments(body)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||
return
|
||||
}
|
||||
|
||||
// If the frame is not fragmented, a message has been received.
|
||||
// If the frame is fragmented, it will terminate with a fin bit set
|
||||
// and an opcode of 0 (continuation), therefore we handle that when
|
||||
// parsing continuation frames, not here.
|
||||
if (!this.#info.fragmented && this.#info.fin) {
|
||||
const fullMessage = Buffer.concat(this.#fragments)
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
||||
this.#fragments.length = 0
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||
}
|
||||
|
||||
this.#state = parserStates.INFO
|
||||
} else {
|
||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
||||
if (error) {
|
||||
failWebsocketConnection(this.ws, error.message)
|
||||
return
|
||||
}
|
||||
this.#extensions.get('permessage-deflate').decompress(
|
||||
body,
|
||||
this.#info.fin,
|
||||
(error, data) => {
|
||||
if (error) {
|
||||
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
||||
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
||||
return
|
||||
}
|
||||
|
||||
this.#fragments.push(data)
|
||||
if (!this.writeFragments(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = true
|
||||
this.run(callback)
|
||||
return
|
||||
}
|
||||
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||
|
||||
if (!this.#info.fin) {
|
||||
this.#state = parserStates.INFO
|
||||
this.#loop = true
|
||||
this.#state = parserStates.INFO
|
||||
this.run(callback)
|
||||
return
|
||||
}
|
||||
|
||||
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
||||
|
||||
this.#loop = true
|
||||
this.#state = parserStates.INFO
|
||||
this.#fragments.length = 0
|
||||
this.run(callback)
|
||||
})
|
||||
)
|
||||
|
||||
this.#loop = false
|
||||
break
|
||||
@ -27058,6 +27217,35 @@ class ByteParser extends Writable {
|
||||
return buffer
|
||||
}
|
||||
|
||||
writeFragments (fragment) {
|
||||
if (
|
||||
this.#maxFragments > 0 &&
|
||||
this.#fragments.length === this.#maxFragments
|
||||
) {
|
||||
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
||||
return false
|
||||
}
|
||||
|
||||
this.#fragmentsBytes += fragment.length
|
||||
this.#fragments.push(fragment)
|
||||
return true
|
||||
}
|
||||
|
||||
consumeFragments () {
|
||||
const fragments = this.#fragments
|
||||
|
||||
if (fragments.length === 1) {
|
||||
this.#fragmentsBytes = 0
|
||||
return fragments.shift()
|
||||
}
|
||||
|
||||
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
||||
this.#fragments = []
|
||||
this.#fragmentsBytes = 0
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
parseCloseBody (data) {
|
||||
assert(data.length !== 1)
|
||||
|
||||
@ -28089,7 +28277,14 @@ class WebSocket extends EventTarget {
|
||||
// once this happens, the connection is open
|
||||
this[kResponse] = response
|
||||
|
||||
const parser = new ByteParser(this, parsedExtensions)
|
||||
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
||||
const maxFragments = webSocketOptions?.maxFragments
|
||||
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
||||
|
||||
const parser = new ByteParser(this, parsedExtensions, {
|
||||
maxFragments,
|
||||
maxPayloadSize
|
||||
})
|
||||
parser.on('drain', onParserDrain)
|
||||
parser.on('error', onParserError.bind(this))
|
||||
|
||||
|
||||
6074
package-lock.json
generated
6074
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ts-dev-tools/core": "^1.12.4",
|
||||
"@vercel/ncc": "^0.38.4"
|
||||
"@vercel/ncc": "^0.44.0"
|
||||
},
|
||||
"scripts": {
|
||||
"package": "npm run package:index && npm run package:post",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user