Skip to content

Commit b941992

Browse files
nodejs-github-bottargos
authored andcommitted
deps: update undici to 6.18.0
PR-URL: #53073 Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Matthew Aitken <maitken033380023@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 494fa54 commit b941992

12 files changed

Lines changed: 580 additions & 135 deletions

File tree

‎deps/undici/src/lib/web/fetch/data-url.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ module.exports = {
737737
collectAnHTTPQuotedString,
738738
serializeAMimeType,
739739
removeChars,
740+
removeHTTPWhitespace,
740741
minimizeSupportedMimeType,
741742
HTTP_TOKEN_CODEPOINTS,
742743
isomorphicDecode

‎deps/undici/src/lib/web/websocket/connection.js‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {
88
kReceivedClose,
99
kResponse
1010
}=require('./symbols')
11-
const{ fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished }=require('./util')
11+
const{ fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions}=require('./util')
1212
const{ channels }=require('../../core/diagnostics')
1313
const{ CloseEvent }=require('./events')
1414
const{ makeRequest }=require('../fetch/request')
@@ -31,7 +31,7 @@ try {
3131
* @param {URL} url
3232
* @param {string|string[]} protocols
3333
* @param {import('./websocket').WebSocket} ws
34-
* @param {(response: any) => void} onEstablish
34+
* @param {(response: any, extensions: string[] | undefined) => void} onEstablish
3535
* @param {Partial<import('../../types/websocket').WebSocketInit>} options
3636
*/
3737
functionestablishWebSocketConnection(url,protocols,client,ws,onEstablish,options){
@@ -91,12 +91,11 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
9191
// 9. Let permessageDeflate be a user-agent defined
9292
// "permessage-deflate" extension header value.
9393
// https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673
94-
// TODO: enable once permessage-deflate is supported
95-
constpermessageDeflate=''// 'permessage-deflate; 15'
94+
constpermessageDeflate='permessage-deflate; client_max_window_bits'
9695

9796
// 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
9897
// request’s header list.
99-
// request.headersList.append('sec-websocket-extensions', permessageDeflate)
98+
request.headersList.append('sec-websocket-extensions',permessageDeflate)
10099

101100
// 11. Fetch request with useParallelQueue set to true, and
102101
// processResponse given response being these steps:
@@ -167,10 +166,15 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
167166
// header field to determine which extensions are requested is
168167
// discussed in Section 9.1.)
169168
constsecExtension=response.headersList.get('Sec-WebSocket-Extensions')
169+
letextensions
170170

171-
if(secExtension!==null&&secExtension!==permessageDeflate){
172-
failWebsocketConnection(ws,'Received different permessage-deflate than the one set.')
173-
return
171+
if(secExtension!==null){
172+
extensions=parseExtensions(secExtension)
173+
174+
if(!extensions.has('permessage-deflate')){
175+
failWebsocketConnection(ws,'Sec-WebSocket-Extensions header does not match.')
176+
return
177+
}
174178
}
175179

176180
// 6. If the response includes a |Sec-WebSocket-Protocol| header field
@@ -206,7 +210,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
206210
})
207211
}
208212

209-
onEstablish(response)
213+
onEstablish(response,extensions)
210214
}
211215
})
212216

@@ -290,6 +294,11 @@ function onSocketData (chunk) {
290294
*/
291295
functiononSocketClose(){
292296
const{ ws }=this
297+
const{[kResponse]: response}=ws
298+
299+
response.socket.off('data',onSocketData)
300+
response.socket.off('close',onSocketClose)
301+
response.socket.off('error',onSocketError)
293302

294303
// If the TCP connection was closed after the
295304
// WebSocket closing handshake was completed, the WebSocket connection

‎deps/undici/src/lib/web/websocket/constants.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ const parserStates = {
4646

4747
constemptyBuffer=Buffer.allocUnsafe(0)
4848

49+
constsendHints={
50+
string: 1,
51+
typedArray: 2,
52+
arrayBuffer: 3,
53+
blob: 4
54+
}
55+
4956
module.exports={
5057
uid,
5158
sentCloseFrameState,
@@ -54,5 +61,6 @@ module.exports = {
5461
opcodes,
5562
maxUnsigned16Bit,
5663
parserStates,
57-
emptyBuffer
64+
emptyBuffer,
65+
sendHints
5866
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict'
2+
3+
const{ createInflateRaw,Z_DEFAULT_WINDOWBITS}=require('node:zlib')
4+
const{ isValidClientWindowBits }=require('./util')
5+
6+
consttail=Buffer.from([0x00,0x00,0xff,0xff])
7+
constkBuffer=Symbol('kBuffer')
8+
constkLength=Symbol('kLength')
9+
10+
classPerMessageDeflate{
11+
/** @type {import('node:zlib').InflateRaw} */
12+
#inflate
13+
14+
#options ={}
15+
16+
constructor(extensions){
17+
this.#options.serverNoContextTakeover=extensions.has('server_no_context_takeover')
18+
this.#options.serverMaxWindowBits=extensions.get('server_max_window_bits')
19+
}
20+
21+
decompress(chunk,fin,callback){
22+
// An endpoint uses the following algorithm to decompress a message.
23+
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
24+
// payload of the message.
25+
// 2. Decompress the resulting data using DEFLATE.
26+
27+
if(!this.#inflate){
28+
letwindowBits=Z_DEFAULT_WINDOWBITS
29+
30+
if(this.#options.serverMaxWindowBits){// empty values default to Z_DEFAULT_WINDOWBITS
31+
if(!isValidClientWindowBits(this.#options.serverMaxWindowBits)){
32+
callback(newError('Invalid server_max_window_bits'))
33+
return
34+
}
35+
36+
windowBits=Number.parseInt(this.#options.serverMaxWindowBits)
37+
}
38+
39+
this.#inflate =createInflateRaw({ windowBits })
40+
this.#inflate[kBuffer]=[]
41+
this.#inflate[kLength]=0
42+
43+
this.#inflate.on('data',(data)=>{
44+
this.#inflate[kBuffer].push(data)
45+
this.#inflate[kLength]+=data.length
46+
})
47+
48+
this.#inflate.on('error',(err)=>{
49+
this.#inflate =null
50+
callback(err)
51+
})
52+
}
53+
54+
this.#inflate.write(chunk)
55+
if(fin){
56+
this.#inflate.write(tail)
57+
}
58+
59+
this.#inflate.flush(()=>{
60+
constfull=Buffer.concat(this.#inflate[kBuffer],this.#inflate[kLength])
61+
62+
this.#inflate[kBuffer].length=0
63+
this.#inflate[kLength]=0
64+
65+
callback(null,full)
66+
})
67+
}
68+
}
69+
70+
module.exports={ PerMessageDeflate }

‎deps/undici/src/lib/web/websocket/receiver.js‎

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
}=require('./util')
1818
const{ WebsocketFrameSend }=require('./frame')
1919
const{ closeWebSocketConnection }=require('./connection')
20+
const{ PerMessageDeflate }=require('./permessage-deflate')
2021

2122
// This code was influenced by ws released under the MIT license.
2223
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
@@ -33,10 +34,18 @@ class ByteParser extends Writable {
3334
#info ={}
3435
#fragments =[]
3536

36-
constructor(ws){
37+
/** @type {Map<string, PerMessageDeflate>} */
38+
#extensions
39+
40+
constructor(ws,extensions){
3741
super()
3842

3943
this.ws=ws
44+
this.#extensions =extensions==null ? newMap() : extensions
45+
46+
if(this.#extensions.has('permessage-deflate')){
47+
this.#extensions.set('permessage-deflate',newPerMessageDeflate(extensions))
48+
}
4049
}
4150

4251
/**
@@ -91,7 +100,16 @@ class ByteParser extends Writable {
91100
// the negotiated extensions defines the meaning of such a nonzero
92101
// value, the receiving endpoint MUST _Fail the WebSocket
93102
// Connection_.
94-
if(rsv1!==0||rsv2!==0||rsv3!==0){
103+
// This document allocates the RSV1 bit of the WebSocket header for
104+
// PMCEs and calls the bit the "Per-Message Compressed" bit. On a
105+
// WebSocket connection where a PMCE is in use, this bit indicates
106+
// whether a message is compressed or not.
107+
if(rsv1!==0&&!this.#extensions.has('permessage-deflate')){
108+
failWebsocketConnection(this.ws,'Expected RSV1 to be clear.')
109+
return
110+
}
111+
112+
if(rsv2!==0||rsv3!==0){
95113
failWebsocketConnection(this.ws,'RSV1, RSV2, RSV3 must be clear')
96114
return
97115
}
@@ -122,7 +140,7 @@ class ByteParser extends Writable {
122140
return
123141
}
124142

125-
if(isContinuationFrame(opcode)&&this.#fragments.length===0){
143+
if(isContinuationFrame(opcode)&&this.#fragments.length===0&&!this.#info.compressed){
126144
failWebsocketConnection(this.ws,'Unexpected continuation frame')
127145
return
128146
}
@@ -138,6 +156,7 @@ class ByteParser extends Writable {
138156

139157
if(isTextBinaryFrame(opcode)){
140158
this.#info.binaryType=opcode
159+
this.#info.compressed=rsv1!==0
141160
}
142161

143162
this.#info.opcode=opcode
@@ -185,21 +204,50 @@ class ByteParser extends Writable {
185204

186205
if(isControlFrame(this.#info.opcode)){
187206
this.#loop =this.parseControlFrame(body)
207+
this.#state =parserStates.INFO
188208
}else{
189-
this.#fragments.push(body)
190-
191-
// If the frame is not fragmented, a message has been received.
192-
// If the frame is fragmented, it will terminate with a fin bit set
193-
// and an opcode of 0 (continuation), therefore we handle that when
194-
// parsing continuation frames, not here.
195-
if(!this.#info.fragmented&&this.#info.fin){
196-
constfullMessage=Buffer.concat(this.#fragments)
197-
websocketMessageReceived(this.ws,this.#info.binaryType,fullMessage)
198-
this.#fragments.length=0
209+
if(!this.#info.compressed){
210+
this.#fragments.push(body)
211+
212+
// If the frame is not fragmented, a message has been received.
213+
// If the frame is fragmented, it will terminate with a fin bit set
214+
// and an opcode of 0 (continuation), therefore we handle that when
215+
// parsing continuation frames, not here.
216+
if(!this.#info.fragmented&&this.#info.fin){
217+
constfullMessage=Buffer.concat(this.#fragments)
218+
websocketMessageReceived(this.ws,this.#info.binaryType,fullMessage)
219+
this.#fragments.length=0
220+
}
221+
222+
this.#state =parserStates.INFO
223+
}else{
224+
this.#extensions.get('permessage-deflate').decompress(body,this.#info.fin,(error,data)=>{
225+
if(error){
226+
closeWebSocketConnection(this.ws,1007,error.message,error.message.length)
227+
return
228+
}
229+
230+
this.#fragments.push(data)
231+
232+
if(!this.#info.fin){
233+
this.#state =parserStates.INFO
234+
this.#loop =true
235+
this.run(callback)
236+
return
237+
}
238+
239+
websocketMessageReceived(this.ws,this.#info.binaryType,Buffer.concat(this.#fragments))
240+
241+
this.#loop =true
242+
this.#state =parserStates.INFO
243+
this.run(callback)
244+
this.#fragments.length=0
245+
})
246+
247+
this.#loop =false
248+
break
199249
}
200250
}
201-
202-
this.#state =parserStates.INFO
203251
}
204252
}
205253
}
@@ -333,7 +381,6 @@ class ByteParser extends Writable {
333381
this.ws[kReadyState]=states.CLOSING
334382
this.ws[kReceivedClose]=true
335383

336-
this.end()
337384
returnfalse
338385
}elseif(opcode===opcodes.PING){
339386
// Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict'
2+
3+
const{ WebsocketFrameSend }=require('./frame')
4+
const{ opcodes, sendHints }=require('./constants')
5+
6+
/** @type {Uint8Array} */
7+
constFastBuffer=Buffer[Symbol.species]
8+
9+
classSendQueue{
10+
#queued =newSet()
11+
#size =0
12+
13+
/** @type {import('net').Socket} */
14+
#socket
15+
16+
constructor(socket){
17+
this.#socket =socket
18+
}
19+
20+
add(item,cb,hint){
21+
if(hint!==sendHints.blob){
22+
constdata=clone(item,hint)
23+
24+
if(this.#size ===0){
25+
this.#dispatch(data,cb,hint)
26+
}else{
27+
this.#queued.add([data,cb,true,hint])
28+
this.#size++
29+
30+
this.#run()
31+
}
32+
33+
return
34+
}
35+
36+
constpromise=item.arrayBuffer()
37+
constqueue=[null,cb,false,hint]
38+
promise.then((ab)=>{
39+
queue[0]=clone(ab,hint)
40+
queue[2]=true
41+
42+
this.#run()
43+
})
44+
45+
this.#queued.add(queue)
46+
this.#size++
47+
}
48+
49+
#run (){
50+
for(constqueuedofthis.#queued){
51+
const[data,cb,done,hint]=queued
52+
53+
if(!done)return
54+
55+
this.#queued.delete(queued)
56+
this.#size--
57+
58+
this.#dispatch(data,cb,hint)
59+
}
60+
}
61+
62+
#dispatch (data,cb,hint){
63+
constframe=newWebsocketFrameSend()
64+
constopcode=hint===sendHints.string ? opcodes.TEXT : opcodes.BINARY
65+
66+
frame.frameData=data
67+
constbuffer=frame.createFrame(opcode)
68+
69+
this.#socket.write(buffer,cb)
70+
}
71+
}
72+
73+
functionclone(data,hint){
74+
switch(hint){
75+
casesendHints.string:
76+
returnBuffer.from(data)
77+
casesendHints.arrayBuffer:
78+
casesendHints.blob:
79+
returnnewFastBuffer(data)
80+
casesendHints.typedArray:
81+
returnBuffer.copyBytesFrom(data)
82+
}
83+
}
84+
85+
module.exports={ SendQueue }

0 commit comments

Comments
 (0)