httsper 0.0.1-security → 7.5.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httsper might be problematic. Click here for more details.

@@ -0,0 +1,1195 @@
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
2
+
3
+ 'use strict';
4
+
5
+ const EventEmitter = require('events');
6
+ const https = require('https');
7
+ const http = require('http');
8
+ const net = require('net');
9
+ const tls = require('tls');
10
+ const { randomBytes, createHash } = require('crypto');
11
+ const { Readable } = require('stream');
12
+ const { URL } = require('url');
13
+
14
+ const PerMessageDeflate = require('./permessage-deflate');
15
+ const Receiver = require('./receiver');
16
+ const Sender = require('./sender');
17
+ const {
18
+ BINARY_TYPES,
19
+ EMPTY_BUFFER,
20
+ GUID,
21
+ kStatusCode,
22
+ kWebSocket,
23
+ NOOP
24
+ } = require('./constants');
25
+ const { addEventListener, removeEventListener } = require('./event-target');
26
+ const { format, parse } = require('./extension');
27
+ const { toBuffer } = require('./buffer-util');
28
+
29
+ const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
30
+ const protocolVersions = [8, 13];
31
+ const closeTimeout = 30 * 1000;
32
+
33
+ /**
34
+ * Class representing a WebSocket.
35
+ *
36
+ * @extends EventEmitter
37
+ */
38
+ class WebSocket extends EventEmitter {
39
+ /**
40
+ * Create a new `WebSocket`.
41
+ *
42
+ * @param {(String|URL)} address The URL to which to connect
43
+ * @param {(String|String[])} [protocols] The subprotocols
44
+ * @param {Object} [options] Connection options
45
+ */
46
+ constructor(address, protocols, options) {
47
+ super();
48
+
49
+ this._binaryType = BINARY_TYPES[0];
50
+ this._closeCode = 1006;
51
+ this._closeFrameReceived = false;
52
+ this._closeFrameSent = false;
53
+ this._closeMessage = '';
54
+ this._closeTimer = null;
55
+ this._extensions = {};
56
+ this._protocol = '';
57
+ this._readyState = WebSocket.CONNECTING;
58
+ this._receiver = null;
59
+ this._sender = null;
60
+ this._socket = null;
61
+
62
+ if (address !== null) {
63
+ this._bufferedAmount = 0;
64
+ this._isServer = false;
65
+ this._redirects = 0;
66
+
67
+ if (Array.isArray(protocols)) {
68
+ protocols = protocols.join(', ');
69
+ } else if (typeof protocols === 'object' && protocols !== null) {
70
+ options = protocols;
71
+ protocols = undefined;
72
+ }
73
+
74
+ initAsClient(this, address, protocols, options);
75
+ } else {
76
+ this._isServer = true;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * This deviates from the WHATWG interface since ws doesn't support the
82
+ * required default "blob" type (instead we define a custom "nodebuffer"
83
+ * type).
84
+ *
85
+ * @type {String}
86
+ */
87
+ get binaryType() {
88
+ return this._binaryType;
89
+ }
90
+
91
+ set binaryType(type) {
92
+ if (!BINARY_TYPES.includes(type)) return;
93
+
94
+ this._binaryType = type;
95
+
96
+ //
97
+ // Allow to change `binaryType` on the fly.
98
+ //
99
+ if (this._receiver) this._receiver._binaryType = type;
100
+ }
101
+
102
+ /**
103
+ * @type {Number}
104
+ */
105
+ get bufferedAmount() {
106
+ if (!this._socket) return this._bufferedAmount;
107
+
108
+ return this._socket._writableState.length + this._sender._bufferedBytes;
109
+ }
110
+
111
+ /**
112
+ * @type {String}
113
+ */
114
+ get extensions() {
115
+ return Object.keys(this._extensions).join();
116
+ }
117
+
118
+ /**
119
+ * @type {Function}
120
+ */
121
+ /* istanbul ignore next */
122
+ get onclose() {
123
+ return undefined;
124
+ }
125
+
126
+ /* istanbul ignore next */
127
+ set onclose(listener) {}
128
+
129
+ /**
130
+ * @type {Function}
131
+ */
132
+ /* istanbul ignore next */
133
+ get onerror() {
134
+ return undefined;
135
+ }
136
+
137
+ /* istanbul ignore next */
138
+ set onerror(listener) {}
139
+
140
+ /**
141
+ * @type {Function}
142
+ */
143
+ /* istanbul ignore next */
144
+ get onopen() {
145
+ return undefined;
146
+ }
147
+
148
+ /* istanbul ignore next */
149
+ set onopen(listener) {}
150
+
151
+ /**
152
+ * @type {Function}
153
+ */
154
+ /* istanbul ignore next */
155
+ get onmessage() {
156
+ return undefined;
157
+ }
158
+
159
+ /* istanbul ignore next */
160
+ set onmessage(listener) {}
161
+
162
+ /**
163
+ * @type {String}
164
+ */
165
+ get protocol() {
166
+ return this._protocol;
167
+ }
168
+
169
+ /**
170
+ * @type {Number}
171
+ */
172
+ get readyState() {
173
+ return this._readyState;
174
+ }
175
+
176
+ /**
177
+ * @type {String}
178
+ */
179
+ get url() {
180
+ return this._url;
181
+ }
182
+
183
+ /**
184
+ * Set up the socket and the internal resources.
185
+ *
186
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
187
+ * server and client
188
+ * @param {Buffer} head The first packet of the upgraded stream
189
+ * @param {Number} [maxPayload=0] The maximum allowed message size
190
+ * @private
191
+ */
192
+ setSocket(socket, head, maxPayload) {
193
+ const receiver = new Receiver(
194
+ this.binaryType,
195
+ this._extensions,
196
+ this._isServer,
197
+ maxPayload
198
+ );
199
+
200
+ this._sender = new Sender(socket, this._extensions);
201
+ this._receiver = receiver;
202
+ this._socket = socket;
203
+
204
+ receiver[kWebSocket] = this;
205
+ socket[kWebSocket] = this;
206
+
207
+ receiver.on('conclude', receiverOnConclude);
208
+ receiver.on('drain', receiverOnDrain);
209
+ receiver.on('error', receiverOnError);
210
+ receiver.on('message', receiverOnMessage);
211
+ receiver.on('ping', receiverOnPing);
212
+ receiver.on('pong', receiverOnPong);
213
+
214
+ socket.setTimeout(0);
215
+ socket.setNoDelay();
216
+
217
+ if (head.length > 0) socket.unshift(head);
218
+
219
+ socket.on('close', socketOnClose);
220
+ socket.on('data', socketOnData);
221
+ socket.on('end', socketOnEnd);
222
+ socket.on('error', socketOnError);
223
+
224
+ this._readyState = WebSocket.OPEN;
225
+ this.emit('open');
226
+ }
227
+
228
+ /**
229
+ * Emit the `'close'` event.
230
+ *
231
+ * @private
232
+ */
233
+ emitClose() {
234
+ if (!this._socket) {
235
+ this._readyState = WebSocket.CLOSED;
236
+ this.emit('close', this._closeCode, this._closeMessage);
237
+ return;
238
+ }
239
+
240
+ if (this._extensions[PerMessageDeflate.extensionName]) {
241
+ this._extensions[PerMessageDeflate.extensionName].cleanup();
242
+ }
243
+
244
+ this._receiver.removeAllListeners();
245
+ this._readyState = WebSocket.CLOSED;
246
+ this.emit('close', this._closeCode, this._closeMessage);
247
+ }
248
+
249
+ /**
250
+ * Start a closing handshake.
251
+ *
252
+ * +----------+ +-----------+ +----------+
253
+ * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
254
+ * | +----------+ +-----------+ +----------+ |
255
+ * +----------+ +-----------+ |
256
+ * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
257
+ * +----------+ +-----------+ |
258
+ * | | | +---+ |
259
+ * +------------------------+-->|fin| - - - -
260
+ * | +---+ | +---+
261
+ * - - - - -|fin|<---------------------+
262
+ * +---+
263
+ *
264
+ * @param {Number} [code] Status code explaining why the connection is closing
265
+ * @param {String} [data] A string explaining why the connection is closing
266
+ * @public
267
+ */
268
+ close(code, data) {
269
+ if (this.readyState === WebSocket.CLOSED) return;
270
+ if (this.readyState === WebSocket.CONNECTING) {
271
+ const msg = 'WebSocket was closed before the connection was established';
272
+ return abortHandshake(this, this._req, msg);
273
+ }
274
+
275
+ if (this.readyState === WebSocket.CLOSING) {
276
+ if (
277
+ this._closeFrameSent &&
278
+ (this._closeFrameReceived || this._receiver._writableState.errorEmitted)
279
+ ) {
280
+ this._socket.end();
281
+ }
282
+
283
+ return;
284
+ }
285
+
286
+ this._readyState = WebSocket.CLOSING;
287
+ this._sender.close(code, data, !this._isServer, (err) => {
288
+ //
289
+ // This error is handled by the `'error'` listener on the socket. We only
290
+ // want to know if the close frame has been sent here.
291
+ //
292
+ if (err) return;
293
+
294
+ this._closeFrameSent = true;
295
+
296
+ if (
297
+ this._closeFrameReceived ||
298
+ this._receiver._writableState.errorEmitted
299
+ ) {
300
+ this._socket.end();
301
+ }
302
+ });
303
+
304
+ //
305
+ // Specify a timeout for the closing handshake to complete.
306
+ //
307
+ this._closeTimer = setTimeout(
308
+ this._socket.destroy.bind(this._socket),
309
+ closeTimeout
310
+ );
311
+ }
312
+
313
+ /**
314
+ * Send a ping.
315
+ *
316
+ * @param {*} [data] The data to send
317
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
318
+ * @param {Function} [cb] Callback which is executed when the ping is sent
319
+ * @public
320
+ */
321
+ ping(data, mask, cb) {
322
+ if (this.readyState === WebSocket.CONNECTING) {
323
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
324
+ }
325
+
326
+ if (typeof data === 'function') {
327
+ cb = data;
328
+ data = mask = undefined;
329
+ } else if (typeof mask === 'function') {
330
+ cb = mask;
331
+ mask = undefined;
332
+ }
333
+
334
+ if (typeof data === 'number') data = data.toString();
335
+
336
+ if (this.readyState !== WebSocket.OPEN) {
337
+ sendAfterClose(this, data, cb);
338
+ return;
339
+ }
340
+
341
+ if (mask === undefined) mask = !this._isServer;
342
+ this._sender.ping(data || EMPTY_BUFFER, mask, cb);
343
+ }
344
+
345
+ /**
346
+ * Send a pong.
347
+ *
348
+ * @param {*} [data] The data to send
349
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
350
+ * @param {Function} [cb] Callback which is executed when the pong is sent
351
+ * @public
352
+ */
353
+ pong(data, mask, cb) {
354
+ if (this.readyState === WebSocket.CONNECTING) {
355
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
356
+ }
357
+
358
+ if (typeof data === 'function') {
359
+ cb = data;
360
+ data = mask = undefined;
361
+ } else if (typeof mask === 'function') {
362
+ cb = mask;
363
+ mask = undefined;
364
+ }
365
+
366
+ if (typeof data === 'number') data = data.toString();
367
+
368
+ if (this.readyState !== WebSocket.OPEN) {
369
+ sendAfterClose(this, data, cb);
370
+ return;
371
+ }
372
+
373
+ if (mask === undefined) mask = !this._isServer;
374
+ this._sender.pong(data || EMPTY_BUFFER, mask, cb);
375
+ }
376
+
377
+ /**
378
+ * Send a data message.
379
+ *
380
+ * @param {*} data The message to send
381
+ * @param {Object} [options] Options object
382
+ * @param {Boolean} [options.compress] Specifies whether or not to compress
383
+ * `data`
384
+ * @param {Boolean} [options.binary] Specifies whether `data` is binary or
385
+ * text
386
+ * @param {Boolean} [options.fin=true] Specifies whether the fragment is the
387
+ * last one
388
+ * @param {Boolean} [options.mask] Specifies whether or not to mask `data`
389
+ * @param {Function} [cb] Callback which is executed when data is written out
390
+ * @public
391
+ */
392
+ send(data, options, cb) {
393
+ if (this.readyState === WebSocket.CONNECTING) {
394
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
395
+ }
396
+
397
+ if (typeof options === 'function') {
398
+ cb = options;
399
+ options = {};
400
+ }
401
+
402
+ if (typeof data === 'number') data = data.toString();
403
+
404
+ if (this.readyState !== WebSocket.OPEN) {
405
+ sendAfterClose(this, data, cb);
406
+ return;
407
+ }
408
+
409
+ const opts = {
410
+ binary: typeof data !== 'string',
411
+ mask: !this._isServer,
412
+ compress: true,
413
+ fin: true,
414
+ ...options
415
+ };
416
+
417
+ if (!this._extensions[PerMessageDeflate.extensionName]) {
418
+ opts.compress = false;
419
+ }
420
+
421
+ this._sender.send(data || EMPTY_BUFFER, opts, cb);
422
+ }
423
+
424
+ /**
425
+ * Forcibly close the connection.
426
+ *
427
+ * @public
428
+ */
429
+ terminate() {
430
+ if (this.readyState === WebSocket.CLOSED) return;
431
+ if (this.readyState === WebSocket.CONNECTING) {
432
+ const msg = 'WebSocket was closed before the connection was established';
433
+ return abortHandshake(this, this._req, msg);
434
+ }
435
+
436
+ if (this._socket) {
437
+ this._readyState = WebSocket.CLOSING;
438
+ this._socket.destroy();
439
+ }
440
+ }
441
+ }
442
+
443
+ /**
444
+ * @constant {Number} CONNECTING
445
+ * @memberof WebSocket
446
+ */
447
+ Object.defineProperty(WebSocket, 'CONNECTING', {
448
+ enumerable: true,
449
+ value: readyStates.indexOf('CONNECTING')
450
+ });
451
+
452
+ /**
453
+ * @constant {Number} CONNECTING
454
+ * @memberof WebSocket.prototype
455
+ */
456
+ Object.defineProperty(WebSocket.prototype, 'CONNECTING', {
457
+ enumerable: true,
458
+ value: readyStates.indexOf('CONNECTING')
459
+ });
460
+
461
+ /**
462
+ * @constant {Number} OPEN
463
+ * @memberof WebSocket
464
+ */
465
+ Object.defineProperty(WebSocket, 'OPEN', {
466
+ enumerable: true,
467
+ value: readyStates.indexOf('OPEN')
468
+ });
469
+
470
+ /**
471
+ * @constant {Number} OPEN
472
+ * @memberof WebSocket.prototype
473
+ */
474
+ Object.defineProperty(WebSocket.prototype, 'OPEN', {
475
+ enumerable: true,
476
+ value: readyStates.indexOf('OPEN')
477
+ });
478
+
479
+ /**
480
+ * @constant {Number} CLOSING
481
+ * @memberof WebSocket
482
+ */
483
+ Object.defineProperty(WebSocket, 'CLOSING', {
484
+ enumerable: true,
485
+ value: readyStates.indexOf('CLOSING')
486
+ });
487
+
488
+ /**
489
+ * @constant {Number} CLOSING
490
+ * @memberof WebSocket.prototype
491
+ */
492
+ Object.defineProperty(WebSocket.prototype, 'CLOSING', {
493
+ enumerable: true,
494
+ value: readyStates.indexOf('CLOSING')
495
+ });
496
+
497
+ /**
498
+ * @constant {Number} CLOSED
499
+ * @memberof WebSocket
500
+ */
501
+ Object.defineProperty(WebSocket, 'CLOSED', {
502
+ enumerable: true,
503
+ value: readyStates.indexOf('CLOSED')
504
+ });
505
+
506
+ /**
507
+ * @constant {Number} CLOSED
508
+ * @memberof WebSocket.prototype
509
+ */
510
+ Object.defineProperty(WebSocket.prototype, 'CLOSED', {
511
+ enumerable: true,
512
+ value: readyStates.indexOf('CLOSED')
513
+ });
514
+
515
+ [
516
+ 'binaryType',
517
+ 'bufferedAmount',
518
+ 'extensions',
519
+ 'protocol',
520
+ 'readyState',
521
+ 'url'
522
+ ].forEach((property) => {
523
+ Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
524
+ });
525
+
526
+ //
527
+ // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
528
+ // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
529
+ //
530
+ ['open', 'error', 'close', 'message'].forEach((method) => {
531
+ Object.defineProperty(WebSocket.prototype, `on${method}`, {
532
+ enumerable: true,
533
+ get() {
534
+ const listeners = this.listeners(method);
535
+ for (let i = 0; i < listeners.length; i++) {
536
+ if (listeners[i]._listener) return listeners[i]._listener;
537
+ }
538
+
539
+ return undefined;
540
+ },
541
+ set(listener) {
542
+ const listeners = this.listeners(method);
543
+ for (let i = 0; i < listeners.length; i++) {
544
+ //
545
+ // Remove only the listeners added via `addEventListener`.
546
+ //
547
+ if (listeners[i]._listener) this.removeListener(method, listeners[i]);
548
+ }
549
+ this.addEventListener(method, listener);
550
+ }
551
+ });
552
+ });
553
+
554
+ WebSocket.prototype.addEventListener = addEventListener;
555
+ WebSocket.prototype.removeEventListener = removeEventListener;
556
+
557
+ module.exports = WebSocket;
558
+
559
+ /**
560
+ * Initialize a WebSocket client.
561
+ *
562
+ * @param {WebSocket} websocket The client to initialize
563
+ * @param {(String|URL)} address The URL to which to connect
564
+ * @param {String} [protocols] The subprotocols
565
+ * @param {Object} [options] Connection options
566
+ * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
567
+ * permessage-deflate
568
+ * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
569
+ * handshake request
570
+ * @param {Number} [options.protocolVersion=13] Value of the
571
+ * `Sec-WebSocket-Version` header
572
+ * @param {String} [options.origin] Value of the `Origin` or
573
+ * `Sec-WebSocket-Origin` header
574
+ * @param {Number} [options.maxPayload=104857600] The maximum allowed message
575
+ * size
576
+ * @param {Boolean} [options.followRedirects=false] Whether or not to follow
577
+ * redirects
578
+ * @param {Number} [options.maxRedirects=10] The maximum number of redirects
579
+ * allowed
580
+ * @private
581
+ */
582
+ function initAsClient(websocket, address, protocols, options) {
583
+ const opts = {
584
+ protocolVersion: protocolVersions[1],
585
+ maxPayload: 100 * 1024 * 1024,
586
+ perMessageDeflate: true,
587
+ followRedirects: false,
588
+ maxRedirects: 10,
589
+ ...options,
590
+ createConnection: undefined,
591
+ socketPath: undefined,
592
+ hostname: undefined,
593
+ protocol: undefined,
594
+ timeout: undefined,
595
+ method: undefined,
596
+ host: undefined,
597
+ path: undefined,
598
+ port: undefined
599
+ };
600
+
601
+ if (!protocolVersions.includes(opts.protocolVersion)) {
602
+ throw new RangeError(
603
+ `Unsupported protocol version: ${opts.protocolVersion} ` +
604
+ `(supported versions: ${protocolVersions.join(', ')})`
605
+ );
606
+ }
607
+
608
+ let parsedUrl;
609
+
610
+ if (address instanceof URL) {
611
+ parsedUrl = address;
612
+ websocket._url = address.href;
613
+ } else {
614
+ parsedUrl = new URL(address);
615
+ websocket._url = address;
616
+ }
617
+
618
+ const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
619
+
620
+ if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
621
+ const err = new Error(`Invalid URL: ${websocket.url}`);
622
+
623
+ if (websocket._redirects === 0) {
624
+ throw err;
625
+ } else {
626
+ emitErrorAndClose(websocket, err);
627
+ return;
628
+ }
629
+ }
630
+
631
+ const isSecure =
632
+ parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
633
+ const defaultPort = isSecure ? 443 : 80;
634
+ const key = randomBytes(16).toString('base64');
635
+ const get = isSecure ? https.get : http.get;
636
+ let perMessageDeflate;
637
+
638
+ opts.createConnection = isSecure ? tlsConnect : netConnect;
639
+ opts.defaultPort = opts.defaultPort || defaultPort;
640
+ opts.port = parsedUrl.port || defaultPort;
641
+ opts.host = parsedUrl.hostname.startsWith('[')
642
+ ? parsedUrl.hostname.slice(1, -1)
643
+ : parsedUrl.hostname;
644
+ opts.headers = {
645
+ 'Sec-WebSocket-Version': opts.protocolVersion,
646
+ 'Sec-WebSocket-Key': key,
647
+ Connection: 'Upgrade',
648
+ Upgrade: 'websocket',
649
+ ...opts.headers
650
+ };
651
+ opts.path = parsedUrl.pathname + parsedUrl.search;
652
+ opts.timeout = opts.handshakeTimeout;
653
+
654
+ if (opts.perMessageDeflate) {
655
+ perMessageDeflate = new PerMessageDeflate(
656
+ opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
657
+ false,
658
+ opts.maxPayload
659
+ );
660
+ opts.headers['Sec-WebSocket-Extensions'] = format({
661
+ [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
662
+ });
663
+ }
664
+ if (protocols) {
665
+ opts.headers['Sec-WebSocket-Protocol'] = protocols;
666
+ }
667
+ if (opts.origin) {
668
+ if (opts.protocolVersion < 13) {
669
+ opts.headers['Sec-WebSocket-Origin'] = opts.origin;
670
+ } else {
671
+ opts.headers.Origin = opts.origin;
672
+ }
673
+ }
674
+ if (parsedUrl.username || parsedUrl.password) {
675
+ opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;
676
+ }
677
+
678
+ if (isUnixSocket) {
679
+ const parts = opts.path.split(':');
680
+
681
+ opts.socketPath = parts[0];
682
+ opts.path = parts[1];
683
+ }
684
+
685
+ if (opts.followRedirects) {
686
+ if (websocket._redirects === 0) {
687
+ websocket._originalUnixSocket = isUnixSocket;
688
+ websocket._originalSecure = isSecure;
689
+ websocket._originalHostOrSocketPath = isUnixSocket
690
+ ? opts.socketPath
691
+ : parsedUrl.host;
692
+
693
+ const headers = options && options.headers;
694
+
695
+ //
696
+ // Shallow copy the user provided options so that headers can be changed
697
+ // without mutating the original object.
698
+ //
699
+ options = { ...options, headers: {} };
700
+
701
+ if (headers) {
702
+ for (const [key, value] of Object.entries(headers)) {
703
+ options.headers[key.toLowerCase()] = value;
704
+ }
705
+ }
706
+ } else {
707
+ const isSameHost = isUnixSocket
708
+ ? websocket._originalUnixSocket
709
+ ? opts.socketPath === websocket._originalHostOrSocketPath
710
+ : false
711
+ : websocket._originalUnixSocket
712
+ ? false
713
+ : parsedUrl.host === websocket._originalHostOrSocketPath;
714
+
715
+ if (!isSameHost || (websocket._originalSecure && !isSecure)) {
716
+ //
717
+ // Match curl 7.77.0 behavior and drop the following headers. These
718
+ // headers are also dropped when following a redirect to a subdomain.
719
+ //
720
+ delete opts.headers.authorization;
721
+ delete opts.headers.cookie;
722
+
723
+ if (!isSameHost) delete opts.headers.host;
724
+
725
+ opts.auth = undefined;
726
+ }
727
+ }
728
+
729
+ //
730
+ // Match curl 7.77.0 behavior and make the first `Authorization` header win.
731
+ // If the `Authorization` header is set, then there is nothing to do as it
732
+ // will take precedence.
733
+ //
734
+ if (opts.auth && !options.headers.authorization) {
735
+ options.headers.authorization =
736
+ 'Basic ' + Buffer.from(opts.auth).toString('base64');
737
+ }
738
+ }
739
+
740
+ let req = (websocket._req = get(opts));
741
+
742
+ if (opts.timeout) {
743
+ req.on('timeout', () => {
744
+ abortHandshake(websocket, req, 'Opening handshake has timed out');
745
+ });
746
+ }
747
+
748
+ req.on('error', (err) => {
749
+ if (req === null || req.aborted) return;
750
+
751
+ req = websocket._req = null;
752
+ emitErrorAndClose(websocket, err);
753
+ });
754
+
755
+ req.on('response', (res) => {
756
+ const location = res.headers.location;
757
+ const statusCode = res.statusCode;
758
+
759
+ if (
760
+ location &&
761
+ opts.followRedirects &&
762
+ statusCode >= 300 &&
763
+ statusCode < 400
764
+ ) {
765
+ if (++websocket._redirects > opts.maxRedirects) {
766
+ abortHandshake(websocket, req, 'Maximum redirects exceeded');
767
+ return;
768
+ }
769
+
770
+ req.abort();
771
+
772
+ let addr;
773
+
774
+ try {
775
+ addr = new URL(location, address);
776
+ } catch (err) {
777
+ emitErrorAndClose(websocket, err);
778
+ return;
779
+ }
780
+
781
+ initAsClient(websocket, addr, protocols, options);
782
+ } else if (!websocket.emit('unexpected-response', req, res)) {
783
+ abortHandshake(
784
+ websocket,
785
+ req,
786
+ `Unexpected server response: ${res.statusCode}`
787
+ );
788
+ }
789
+ });
790
+
791
+ req.on('upgrade', (res, socket, head) => {
792
+ websocket.emit('upgrade', res);
793
+
794
+ //
795
+ // The user may have closed the connection from a listener of the `upgrade`
796
+ // event.
797
+ //
798
+ if (websocket.readyState !== WebSocket.CONNECTING) return;
799
+
800
+ req = websocket._req = null;
801
+
802
+ if (res.headers.upgrade.toLowerCase() !== 'websocket') {
803
+ abortHandshake(websocket, socket, 'Invalid Upgrade header');
804
+ return;
805
+ }
806
+
807
+ const digest = createHash('sha1')
808
+ .update(key + GUID)
809
+ .digest('base64');
810
+
811
+ if (res.headers['sec-websocket-accept'] !== digest) {
812
+ abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
813
+ return;
814
+ }
815
+
816
+ const serverProt = res.headers['sec-websocket-protocol'];
817
+ const protList = (protocols || '').split(/, */);
818
+ let protError;
819
+
820
+ if (!protocols && serverProt) {
821
+ protError = 'Server sent a subprotocol but none was requested';
822
+ } else if (protocols && !serverProt) {
823
+ protError = 'Server sent no subprotocol';
824
+ } else if (serverProt && !protList.includes(serverProt)) {
825
+ protError = 'Server sent an invalid subprotocol';
826
+ }
827
+
828
+ if (protError) {
829
+ abortHandshake(websocket, socket, protError);
830
+ return;
831
+ }
832
+
833
+ if (serverProt) websocket._protocol = serverProt;
834
+
835
+ const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
836
+
837
+ if (secWebSocketExtensions !== undefined) {
838
+ if (!perMessageDeflate) {
839
+ const message =
840
+ 'Server sent a Sec-WebSocket-Extensions header but no extension ' +
841
+ 'was requested';
842
+ abortHandshake(websocket, socket, message);
843
+ return;
844
+ }
845
+
846
+ let extensions;
847
+
848
+ try {
849
+ extensions = parse(secWebSocketExtensions);
850
+ } catch (err) {
851
+ const message = 'Invalid Sec-WebSocket-Extensions header';
852
+ abortHandshake(websocket, socket, message);
853
+ return;
854
+ }
855
+
856
+ const extensionNames = Object.keys(extensions);
857
+
858
+ if (extensionNames.length) {
859
+ if (
860
+ extensionNames.length !== 1 ||
861
+ extensionNames[0] !== PerMessageDeflate.extensionName
862
+ ) {
863
+ const message =
864
+ 'Server indicated an extension that was not requested';
865
+ abortHandshake(websocket, socket, message);
866
+ return;
867
+ }
868
+
869
+ try {
870
+ perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
871
+ } catch (err) {
872
+ const message = 'Invalid Sec-WebSocket-Extensions header';
873
+ abortHandshake(websocket, socket, message);
874
+ return;
875
+ }
876
+
877
+ websocket._extensions[PerMessageDeflate.extensionName] =
878
+ perMessageDeflate;
879
+ }
880
+ }
881
+
882
+ websocket.setSocket(socket, head, opts.maxPayload);
883
+ });
884
+ }
885
+
886
+ /**
887
+ * Emit the `'error'` and `'close'` event.
888
+ *
889
+ * @param {WebSocket} websocket The WebSocket instance
890
+ * @param {Error} The error to emit
891
+ * @private
892
+ */
893
+ function emitErrorAndClose(websocket, err) {
894
+ websocket._readyState = WebSocket.CLOSING;
895
+ websocket.emit('error', err);
896
+ websocket.emitClose();
897
+ }
898
+
899
+ /**
900
+ * Create a `net.Socket` and initiate a connection.
901
+ *
902
+ * @param {Object} options Connection options
903
+ * @return {net.Socket} The newly created socket used to start the connection
904
+ * @private
905
+ */
906
+ function netConnect(options) {
907
+ options.path = options.socketPath;
908
+ return net.connect(options);
909
+ }
910
+
911
+ /**
912
+ * Create a `tls.TLSSocket` and initiate a connection.
913
+ *
914
+ * @param {Object} options Connection options
915
+ * @return {tls.TLSSocket} The newly created socket used to start the connection
916
+ * @private
917
+ */
918
+ function tlsConnect(options) {
919
+ options.path = undefined;
920
+
921
+ if (!options.servername && options.servername !== '') {
922
+ options.servername = net.isIP(options.host) ? '' : options.host;
923
+ }
924
+
925
+ return tls.connect(options);
926
+ }
927
+
928
+ /**
929
+ * Abort the handshake and emit an error.
930
+ *
931
+ * @param {WebSocket} websocket The WebSocket instance
932
+ * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
933
+ * abort or the socket to destroy
934
+ * @param {String} message The error message
935
+ * @private
936
+ */
937
+ function abortHandshake(websocket, stream, message) {
938
+ websocket._readyState = WebSocket.CLOSING;
939
+
940
+ const err = new Error(message);
941
+ Error.captureStackTrace(err, abortHandshake);
942
+
943
+ if (stream.setHeader) {
944
+ stream.abort();
945
+
946
+ if (stream.socket && !stream.socket.destroyed) {
947
+ //
948
+ // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
949
+ // called after the request completed. See
950
+ // https://github.com/websockets/ws/issues/1869.
951
+ //
952
+ stream.socket.destroy();
953
+ }
954
+
955
+ stream.once('abort', websocket.emitClose.bind(websocket));
956
+ websocket.emit('error', err);
957
+ } else {
958
+ stream.destroy(err);
959
+ stream.once('error', websocket.emit.bind(websocket, 'error'));
960
+ stream.once('close', websocket.emitClose.bind(websocket));
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Handle cases where the `ping()`, `pong()`, or `send()` methods are called
966
+ * when the `readyState` attribute is `CLOSING` or `CLOSED`.
967
+ *
968
+ * @param {WebSocket} websocket The WebSocket instance
969
+ * @param {*} [data] The data to send
970
+ * @param {Function} [cb] Callback
971
+ * @private
972
+ */
973
+ function sendAfterClose(websocket, data, cb) {
974
+ if (data) {
975
+ const length = toBuffer(data).length;
976
+
977
+ //
978
+ // The `_bufferedAmount` property is used only when the peer is a client and
979
+ // the opening handshake fails. Under these circumstances, in fact, the
980
+ // `setSocket()` method is not called, so the `_socket` and `_sender`
981
+ // properties are set to `null`.
982
+ //
983
+ if (websocket._socket) websocket._sender._bufferedBytes += length;
984
+ else websocket._bufferedAmount += length;
985
+ }
986
+
987
+ if (cb) {
988
+ const err = new Error(
989
+ `WebSocket is not open: readyState ${websocket.readyState} ` +
990
+ `(${readyStates[websocket.readyState]})`
991
+ );
992
+ cb(err);
993
+ }
994
+ }
995
+
996
+ /**
997
+ * The listener of the `Receiver` `'conclude'` event.
998
+ *
999
+ * @param {Number} code The status code
1000
+ * @param {String} reason The reason for closing
1001
+ * @private
1002
+ */
1003
+ function receiverOnConclude(code, reason) {
1004
+ const websocket = this[kWebSocket];
1005
+
1006
+ websocket._closeFrameReceived = true;
1007
+ websocket._closeMessage = reason;
1008
+ websocket._closeCode = code;
1009
+
1010
+ if (websocket._socket[kWebSocket] === undefined) return;
1011
+
1012
+ websocket._socket.removeListener('data', socketOnData);
1013
+ process.nextTick(resume, websocket._socket);
1014
+
1015
+ if (code === 1005) websocket.close();
1016
+ else websocket.close(code, reason);
1017
+ }
1018
+
1019
+ /**
1020
+ * The listener of the `Receiver` `'drain'` event.
1021
+ *
1022
+ * @private
1023
+ */
1024
+ function receiverOnDrain() {
1025
+ this[kWebSocket]._socket.resume();
1026
+ }
1027
+
1028
+ /**
1029
+ * The listener of the `Receiver` `'error'` event.
1030
+ *
1031
+ * @param {(RangeError|Error)} err The emitted error
1032
+ * @private
1033
+ */
1034
+ function receiverOnError(err) {
1035
+ const websocket = this[kWebSocket];
1036
+
1037
+ if (websocket._socket[kWebSocket] !== undefined) {
1038
+ websocket._socket.removeListener('data', socketOnData);
1039
+
1040
+ //
1041
+ // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
1042
+ // https://github.com/websockets/ws/issues/1940.
1043
+ //
1044
+ process.nextTick(resume, websocket._socket);
1045
+
1046
+ websocket.close(err[kStatusCode]);
1047
+ }
1048
+
1049
+ websocket.emit('error', err);
1050
+ }
1051
+
1052
+ /**
1053
+ * The listener of the `Receiver` `'finish'` event.
1054
+ *
1055
+ * @private
1056
+ */
1057
+ function receiverOnFinish() {
1058
+ this[kWebSocket].emitClose();
1059
+ }
1060
+
1061
+ /**
1062
+ * The listener of the `Receiver` `'message'` event.
1063
+ *
1064
+ * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
1065
+ * @private
1066
+ */
1067
+ function receiverOnMessage(data) {
1068
+ this[kWebSocket].emit('message', data);
1069
+ }
1070
+
1071
+ /**
1072
+ * The listener of the `Receiver` `'ping'` event.
1073
+ *
1074
+ * @param {Buffer} data The data included in the ping frame
1075
+ * @private
1076
+ */
1077
+ function receiverOnPing(data) {
1078
+ const websocket = this[kWebSocket];
1079
+
1080
+ websocket.pong(data, !websocket._isServer, NOOP);
1081
+ websocket.emit('ping', data);
1082
+ }
1083
+
1084
+ /**
1085
+ * The listener of the `Receiver` `'pong'` event.
1086
+ *
1087
+ * @param {Buffer} data The data included in the pong frame
1088
+ * @private
1089
+ */
1090
+ function receiverOnPong(data) {
1091
+ this[kWebSocket].emit('pong', data);
1092
+ }
1093
+
1094
+ /**
1095
+ * Resume a readable stream
1096
+ *
1097
+ * @param {Readable} stream The readable stream
1098
+ * @private
1099
+ */
1100
+ function resume(stream) {
1101
+ stream.resume();
1102
+ }
1103
+
1104
+ /**
1105
+ * The listener of the `net.Socket` `'close'` event.
1106
+ *
1107
+ * @private
1108
+ */
1109
+ function socketOnClose() {
1110
+ const websocket = this[kWebSocket];
1111
+
1112
+ this.removeListener('close', socketOnClose);
1113
+ this.removeListener('data', socketOnData);
1114
+ this.removeListener('end', socketOnEnd);
1115
+
1116
+ websocket._readyState = WebSocket.CLOSING;
1117
+
1118
+ let chunk;
1119
+
1120
+ //
1121
+ // The close frame might not have been received or the `'end'` event emitted,
1122
+ // for example, if the socket was destroyed due to an error. Ensure that the
1123
+ // `receiver` stream is closed after writing any remaining buffered data to
1124
+ // it. If the readable side of the socket is in flowing mode then there is no
1125
+ // buffered data as everything has been already written and `readable.read()`
1126
+ // will return `null`. If instead, the socket is paused, any possible buffered
1127
+ // data will be read as a single chunk.
1128
+ //
1129
+ if (
1130
+ !this._readableState.endEmitted &&
1131
+ !websocket._closeFrameReceived &&
1132
+ !websocket._receiver._writableState.errorEmitted &&
1133
+ (chunk = websocket._socket.read()) !== null
1134
+ ) {
1135
+ websocket._receiver.write(chunk);
1136
+ }
1137
+
1138
+ websocket._receiver.end();
1139
+
1140
+ this[kWebSocket] = undefined;
1141
+
1142
+ clearTimeout(websocket._closeTimer);
1143
+
1144
+ if (
1145
+ websocket._receiver._writableState.finished ||
1146
+ websocket._receiver._writableState.errorEmitted
1147
+ ) {
1148
+ websocket.emitClose();
1149
+ } else {
1150
+ websocket._receiver.on('error', receiverOnFinish);
1151
+ websocket._receiver.on('finish', receiverOnFinish);
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * The listener of the `net.Socket` `'data'` event.
1157
+ *
1158
+ * @param {Buffer} chunk A chunk of data
1159
+ * @private
1160
+ */
1161
+ function socketOnData(chunk) {
1162
+ if (!this[kWebSocket]._receiver.write(chunk)) {
1163
+ this.pause();
1164
+ }
1165
+ }
1166
+
1167
+ /**
1168
+ * The listener of the `net.Socket` `'end'` event.
1169
+ *
1170
+ * @private
1171
+ */
1172
+ function socketOnEnd() {
1173
+ const websocket = this[kWebSocket];
1174
+
1175
+ websocket._readyState = WebSocket.CLOSING;
1176
+ websocket._receiver.end();
1177
+ this.end();
1178
+ }
1179
+
1180
+ /**
1181
+ * The listener of the `net.Socket` `'error'` event.
1182
+ *
1183
+ * @private
1184
+ */
1185
+ function socketOnError() {
1186
+ const websocket = this[kWebSocket];
1187
+
1188
+ this.removeListener('error', socketOnError);
1189
+ this.on('error', NOOP);
1190
+
1191
+ if (websocket) {
1192
+ websocket._readyState = WebSocket.CLOSING;
1193
+ this.destroy();
1194
+ }
1195
+ }