| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0d6ecc5 commit 32dbf0b
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ Returns: `Client` | |||
| 25 | 25 | * **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB. | |
| 26 | 26 | * **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable. | |
| 27 | 27 | * **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options. | |
| 28 | + * **maxFragments** `number` (optional) - Defailt: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit. | ||
| 28 | 29 | * **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit. | |
| 29 | 30 | * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections. This option has no effect once HTTP/2 is negotiated — see `maxConcurrentStreams` for the h2 dispatch ceiling. | |
| 30 | 31 | * **connect** `ConnectOptions | Function | null` (optional) - Default: `null` - Configures how undici establishes TCP/TLS connections. Accepts two forms: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -38,6 +38,7 @@ class DispatcherBase extends Dispatcher { | |||
| 38 | 38 | */ | |
| 39 | 39 | get webSocketOptions () { | |
| 40 | 40 | return { | |
| 41 | + maxFragments: this[kWebSocketOptions].maxFragments ?? 131072, | ||
| 41 | 42 | maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 // 128 MB default | |
| 42 | 43 | } | |
| 43 | 44 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -39,6 +39,10 @@ class ByteParser extends Writable { | |||
| 39 | 39 | /** @type {import('./websocket').Handler} */ | |
| 40 | 40 | #handler | |
| 41 | 41 | ||
| 42 | + | ||
| 43 | + /** @type {number} */ | ||
| 44 | + #maxFragments | ||
| 45 | + | ||
| 42 | 46 | /** @type {number} */ | |
| 43 | 47 | #maxPayloadSize | |
| 44 | 48 | ||
@@ -52,6 +56,7 @@ class ByteParser extends Writable { | |||
| 52 | 56 | ||
| 53 | 57 | this.#handler = handler | |
| 54 | 58 | this.#extensions = extensions == null ? new Map() : extensions | |
| 59 | + this.#maxFragments = options.maxFragments ?? 0 | ||
| 55 | 60 | this.#maxPayloadSize = options.maxPayloadSize ?? 0 | |
| 56 | 61 | ||
| 57 | 62 | if (this.#extensions.has('permessage-deflate')) { | |
@@ -242,7 +247,9 @@ class ByteParser extends Writable { | |||
| 242 | 247 | this.#state = parserStates.INFO | |
| 243 | 248 | } else { | |
| 244 | 249 | if (!this.#info.compressed) { | |
| 245 | - this.writeFragments(body) | ||
| 250 | + if (body.length && !this.writeFragments(body)) { | ||
| 251 | + return | ||
| 252 | + } | ||
| 246 | 253 | ||
| 247 | 254 | // If the frame is not fragmented, a message has been received. | |
| 248 | 255 | // If the frame is fragmented, it will terminate with a fin bit set | |
@@ -264,7 +271,9 @@ class ByteParser extends Writable { | |||
| 264 | 271 | return | |
| 265 | 272 | } | |
| 266 | 273 | ||
| 267 | - this.writeFragments(data) | ||
| 274 | + if (data.length && !this.writeFragments(data)) { | ||
| 275 | + return | ||
| 276 | + } | ||
| 268 | 277 | ||
| 269 | 278 | // Check cumulative fragment size | |
| 270 | 279 | if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { | |
@@ -345,8 +354,17 @@ class ByteParser extends Writable { | |||
| 345 | 354 | } | |
| 346 | 355 | ||
| 347 | 356 | writeFragments (fragment) { | |
| 357 | + if ( | ||
| 358 | + this.#maxFragments > 0 && | ||
| 359 | + this.#fragments.length === this.#maxFragments | ||
| 360 | + ) { | ||
| 361 | + failWebsocketConnection(this.#handler, 1008, 'Too many message fragments') | ||
| 362 | + return false | ||
| 363 | + } | ||
| 364 | + | ||
| 348 | 365 | this.#fragmentsBytes += fragment.length | |
| 349 | 366 | this.#fragments.push(fragment) | |
| 367 | + return true | ||
| 350 | 368 | } | |
| 351 | 369 | ||
| 352 | 370 | consumeFragments () { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -468,10 +468,12 @@ class WebSocket extends EventTarget { | |||
| 468 | 468 | // once this happens, the connection is open | |
| 469 | 469 | this.#handler.socket = response.socket | |
| 470 | 470 | ||
| 471 | - // Get maxPayloadSize from dispatcher options | ||
| 471 | + // Get options from dispatcher options | ||
| 472 | + const maxFragments = this.#handler.controller.dispatcher?.webSocketOptions?.maxFragments | ||
| 472 | 473 | const maxPayloadSize = this.#handler.controller.dispatcher?.webSocketOptions?.maxPayloadSize | |
| 473 | 474 | ||
| 474 | 475 | const parser = new ByteParser(this.#handler, parsedExtensions, { | |
| 476 | + maxFragments, | ||
| 475 | 477 | maxPayloadSize | |
| 476 | 478 | }) | |
| 477 | 479 | parser.on('drain', () => this.#handler.onParserDrain()) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { test, after } = require('node:test') | |
| 4 | 4 | const { WebSocketServer } = require('ws') | |
| 5 | - const { WebSocket } = require('../..') | ||
| 5 | + const { Agent, WebSocket } = require('../..') | ||
| 6 | 6 | const diagnosticsChannel = require('node:diagnostics_channel') | |
| 7 | 7 | ||
| 8 | 8 | test('Fragmented frame with a ping frame in the middle of it', (t) => { | |
@@ -39,3 +39,90 @@ test('Fragmented frame with a ping frame in the middle of it', (t) => { | |||
| 39 | 39 | }) | |
| 40 | 40 | }) | |
| 41 | 41 | }) | |
| 42 | + | ||
| 43 | + test('Too many fragments (uncompressed)', (t, done) => { | ||
| 44 | + t.plan(4) | ||
| 45 | + | ||
| 46 | + const agent = new Agent({ | ||
| 47 | + webSocket: { | ||
| 48 | + maxFragments: 3 | ||
| 49 | + } | ||
| 50 | + }) | ||
| 51 | + | ||
| 52 | + const server = new WebSocketServer({ port: 0 }, () => { | ||
| 53 | + const { port } = server.address() | ||
| 54 | + const client = new WebSocket(`ws://127.0.0.1:${port}`, { | ||
| 55 | + dispatcher: agent | ||
| 56 | + }) | ||
| 57 | + | ||
| 58 | + client.addEventListener('error', (event) => { | ||
| 59 | + t.assert.ok(true) | ||
| 60 | + }) | ||
| 61 | + | ||
| 62 | + client.addEventListener('close', (event) => { | ||
| 63 | + t.assert.deepStrictEqual(event.code, 1006) | ||
| 64 | + }) | ||
| 65 | + }) | ||
| 66 | + | ||
| 67 | + server.on('connection', (ws) => { | ||
| 68 | + ws.on('close', (code, reason) => { | ||
| 69 | + t.assert.deepStrictEqual(code, 1008) | ||
| 70 | + t.assert.deepStrictEqual(reason.toString(), 'Too many message fragments') | ||
| 71 | + agent.close() | ||
| 72 | + server.close(done) | ||
| 73 | + }) | ||
| 74 | + | ||
| 75 | + const fragment = Buffer.from('a') | ||
| 76 | + const options = { fin: false } | ||
| 77 | + | ||
| 78 | + ws.send(fragment, options) | ||
| 79 | + ws.send(fragment, options) | ||
| 80 | + ws.send(fragment, options) | ||
| 81 | + ws.send(fragment, options) | ||
| 82 | + }) | ||
| 83 | + }) | ||
| 84 | + | ||
| 85 | + test('Too many fragments (compressed)', (t, done) => { | ||
| 86 | + t.plan(4) | ||
| 87 | + | ||
| 88 | + const agent = new Agent({ | ||
| 89 | + webSocket: { | ||
| 90 | + maxFragments: 3 | ||
| 91 | + } | ||
| 92 | + }) | ||
| 93 | + | ||
| 94 | + const server = new WebSocketServer({ | ||
| 95 | + perMessageDeflate: { threshold: 0 }, | ||
| 96 | + port: 0 | ||
| 97 | + }, () => { | ||
| 98 | + const { port } = server.address() | ||
| 99 | + const client = new WebSocket(`ws://127.0.0.1:${port}`, { | ||
| 100 | + dispatcher: agent | ||
| 101 | + }) | ||
| 102 | + | ||
| 103 | + client.addEventListener('error', (event) => { | ||
| 104 | + t.assert.ok(true) | ||
| 105 | + }) | ||
| 106 | + | ||
| 107 | + client.addEventListener('close', (event) => { | ||
| 108 | + t.assert.deepStrictEqual(event.code, 1006) | ||
| 109 | + }) | ||
| 110 | + }) | ||
| 111 | + | ||
| 112 | + server.on('connection', (ws) => { | ||
| 113 | + ws.on('close', (code, reason) => { | ||
| 114 | + t.assert.deepStrictEqual(code, 1008) | ||
| 115 | + t.assert.deepStrictEqual(reason.toString(), 'Too many message fragments') | ||
| 116 | + agent.close() | ||
| 117 | + server.close(done) | ||
| 118 | + }) | ||
| 119 | + | ||
| 120 | + const fragment = Buffer.from('a') | ||
| 121 | + const options = { fin: false } | ||
| 122 | + | ||
| 123 | + ws.send(fragment, options) | ||
| 124 | + ws.send(fragment, options) | ||
| 125 | + ws.send(fragment, options) | ||
| 126 | + ws.send(fragment, options) | ||
| 127 | + }) | ||
| 128 | + }) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -116,6 +116,11 @@ export declare namespace Client { | |||
| 116 | 116 | bytesRead?: number | |
| 117 | 117 | } | |
| 118 | 118 | export interface WebSocketOptions { | |
| 119 | + /** | ||
| 120 | + * Maximum number of fragments in a message. Set to 0 to disable the limit. | ||
| 121 | + * @default 131072 | ||
| 122 | + */ | ||
| 123 | + maxFragments?: number; | ||
| 119 | 124 | /** | |
| 120 | 125 | * Maximum allowed payload size in bytes for WebSocket messages. | |
| 121 | 126 | * Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. | |
| Back | FazBrowse Home | New Git URL |
0 commit comments