[SignalingServer] Optimize dependent modules
[platform/framework/web/wrtjs.git] / signaling_server / service / node_modules / engine.io-client / node_modules / ws / lib / receiver.js
1 'use strict';
2
3 const { Writable } = require('stream');
4
5 const PerMessageDeflate = require('./permessage-deflate');
6 const {
7   BINARY_TYPES,
8   EMPTY_BUFFER,
9   kStatusCode,
10   kWebSocket
11 } = require('./constants');
12 const { concat, toArrayBuffer, unmask } = require('./buffer-util');
13 const { isValidStatusCode, isValidUTF8 } = require('./validation');
14
15 const GET_INFO = 0;
16 const GET_PAYLOAD_LENGTH_16 = 1;
17 const GET_PAYLOAD_LENGTH_64 = 2;
18 const GET_MASK = 3;
19 const GET_DATA = 4;
20 const INFLATING = 5;
21
22 /**
23  * HyBi Receiver implementation.
24  *
25  * @extends stream.Writable
26  */
27 class Receiver extends Writable {
28   /**
29    * Creates a Receiver instance.
30    *
31    * @param {String} [binaryType=nodebuffer] The type for binary data
32    * @param {Object} [extensions] An object containing the negotiated extensions
33    * @param {Boolean} [isServer=false] Specifies whether to operate in client or
34    *     server mode
35    * @param {Number} [maxPayload=0] The maximum allowed message length
36    */
37   constructor(binaryType, extensions, isServer, maxPayload) {
38     super();
39
40     this._binaryType = binaryType || BINARY_TYPES[0];
41     this[kWebSocket] = undefined;
42     this._extensions = extensions || {};
43     this._isServer = !!isServer;
44     this._maxPayload = maxPayload | 0;
45
46     this._bufferedBytes = 0;
47     this._buffers = [];
48
49     this._compressed = false;
50     this._payloadLength = 0;
51     this._mask = undefined;
52     this._fragmented = 0;
53     this._masked = false;
54     this._fin = false;
55     this._opcode = 0;
56
57     this._totalPayloadLength = 0;
58     this._messageLength = 0;
59     this._fragments = [];
60
61     this._state = GET_INFO;
62     this._loop = false;
63   }
64
65   /**
66    * Implements `Writable.prototype._write()`.
67    *
68    * @param {Buffer} chunk The chunk of data to write
69    * @param {String} encoding The character encoding of `chunk`
70    * @param {Function} cb Callback
71    * @private
72    */
73   _write(chunk, encoding, cb) {
74     if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
75
76     this._bufferedBytes += chunk.length;
77     this._buffers.push(chunk);
78     this.startLoop(cb);
79   }
80
81   /**
82    * Consumes `n` bytes from the buffered data.
83    *
84    * @param {Number} n The number of bytes to consume
85    * @return {Buffer} The consumed bytes
86    * @private
87    */
88   consume(n) {
89     this._bufferedBytes -= n;
90
91     if (n === this._buffers[0].length) return this._buffers.shift();
92
93     if (n < this._buffers[0].length) {
94       const buf = this._buffers[0];
95       this._buffers[0] = buf.slice(n);
96       return buf.slice(0, n);
97     }
98
99     const dst = Buffer.allocUnsafe(n);
100
101     do {
102       const buf = this._buffers[0];
103       const offset = dst.length - n;
104
105       if (n >= buf.length) {
106         dst.set(this._buffers.shift(), offset);
107       } else {
108         dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
109         this._buffers[0] = buf.slice(n);
110       }
111
112       n -= buf.length;
113     } while (n > 0);
114
115     return dst;
116   }
117
118   /**
119    * Starts the parsing loop.
120    *
121    * @param {Function} cb Callback
122    * @private
123    */
124   startLoop(cb) {
125     let err;
126     this._loop = true;
127
128     do {
129       switch (this._state) {
130         case GET_INFO:
131           err = this.getInfo();
132           break;
133         case GET_PAYLOAD_LENGTH_16:
134           err = this.getPayloadLength16();
135           break;
136         case GET_PAYLOAD_LENGTH_64:
137           err = this.getPayloadLength64();
138           break;
139         case GET_MASK:
140           this.getMask();
141           break;
142         case GET_DATA:
143           err = this.getData(cb);
144           break;
145         default:
146           // `INFLATING`
147           this._loop = false;
148           return;
149       }
150     } while (this._loop);
151
152     cb(err);
153   }
154
155   /**
156    * Reads the first two bytes of a frame.
157    *
158    * @return {(RangeError|undefined)} A possible error
159    * @private
160    */
161   getInfo() {
162     if (this._bufferedBytes < 2) {
163       this._loop = false;
164       return;
165     }
166
167     const buf = this.consume(2);
168
169     if ((buf[0] & 0x30) !== 0x00) {
170       this._loop = false;
171       return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
172     }
173
174     const compressed = (buf[0] & 0x40) === 0x40;
175
176     if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
177       this._loop = false;
178       return error(RangeError, 'RSV1 must be clear', true, 1002);
179     }
180
181     this._fin = (buf[0] & 0x80) === 0x80;
182     this._opcode = buf[0] & 0x0f;
183     this._payloadLength = buf[1] & 0x7f;
184
185     if (this._opcode === 0x00) {
186       if (compressed) {
187         this._loop = false;
188         return error(RangeError, 'RSV1 must be clear', true, 1002);
189       }
190
191       if (!this._fragmented) {
192         this._loop = false;
193         return error(RangeError, 'invalid opcode 0', true, 1002);
194       }
195
196       this._opcode = this._fragmented;
197     } else if (this._opcode === 0x01 || this._opcode === 0x02) {
198       if (this._fragmented) {
199         this._loop = false;
200         return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
201       }
202
203       this._compressed = compressed;
204     } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
205       if (!this._fin) {
206         this._loop = false;
207         return error(RangeError, 'FIN must be set', true, 1002);
208       }
209
210       if (compressed) {
211         this._loop = false;
212         return error(RangeError, 'RSV1 must be clear', true, 1002);
213       }
214
215       if (this._payloadLength > 0x7d) {
216         this._loop = false;
217         return error(
218           RangeError,
219           `invalid payload length ${this._payloadLength}`,
220           true,
221           1002
222         );
223       }
224     } else {
225       this._loop = false;
226       return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
227     }
228
229     if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
230     this._masked = (buf[1] & 0x80) === 0x80;
231
232     if (this._isServer) {
233       if (!this._masked) {
234         this._loop = false;
235         return error(RangeError, 'MASK must be set', true, 1002);
236       }
237     } else if (this._masked) {
238       this._loop = false;
239       return error(RangeError, 'MASK must be clear', true, 1002);
240     }
241
242     if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
243     else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
244     else return this.haveLength();
245   }
246
247   /**
248    * Gets extended payload length (7+16).
249    *
250    * @return {(RangeError|undefined)} A possible error
251    * @private
252    */
253   getPayloadLength16() {
254     if (this._bufferedBytes < 2) {
255       this._loop = false;
256       return;
257     }
258
259     this._payloadLength = this.consume(2).readUInt16BE(0);
260     return this.haveLength();
261   }
262
263   /**
264    * Gets extended payload length (7+64).
265    *
266    * @return {(RangeError|undefined)} A possible error
267    * @private
268    */
269   getPayloadLength64() {
270     if (this._bufferedBytes < 8) {
271       this._loop = false;
272       return;
273     }
274
275     const buf = this.consume(8);
276     const num = buf.readUInt32BE(0);
277
278     //
279     // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
280     // if payload length is greater than this number.
281     //
282     if (num > Math.pow(2, 53 - 32) - 1) {
283       this._loop = false;
284       return error(
285         RangeError,
286         'Unsupported WebSocket frame: payload length > 2^53 - 1',
287         false,
288         1009
289       );
290     }
291
292     this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
293     return this.haveLength();
294   }
295
296   /**
297    * Payload length has been read.
298    *
299    * @return {(RangeError|undefined)} A possible error
300    * @private
301    */
302   haveLength() {
303     if (this._payloadLength && this._opcode < 0x08) {
304       this._totalPayloadLength += this._payloadLength;
305       if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
306         this._loop = false;
307         return error(RangeError, 'Max payload size exceeded', false, 1009);
308       }
309     }
310
311     if (this._masked) this._state = GET_MASK;
312     else this._state = GET_DATA;
313   }
314
315   /**
316    * Reads mask bytes.
317    *
318    * @private
319    */
320   getMask() {
321     if (this._bufferedBytes < 4) {
322       this._loop = false;
323       return;
324     }
325
326     this._mask = this.consume(4);
327     this._state = GET_DATA;
328   }
329
330   /**
331    * Reads data bytes.
332    *
333    * @param {Function} cb Callback
334    * @return {(Error|RangeError|undefined)} A possible error
335    * @private
336    */
337   getData(cb) {
338     let data = EMPTY_BUFFER;
339
340     if (this._payloadLength) {
341       if (this._bufferedBytes < this._payloadLength) {
342         this._loop = false;
343         return;
344       }
345
346       data = this.consume(this._payloadLength);
347       if (this._masked) unmask(data, this._mask);
348     }
349
350     if (this._opcode > 0x07) return this.controlMessage(data);
351
352     if (this._compressed) {
353       this._state = INFLATING;
354       this.decompress(data, cb);
355       return;
356     }
357
358     if (data.length) {
359       //
360       // This message is not compressed so its lenght is the sum of the payload
361       // length of all fragments.
362       //
363       this._messageLength = this._totalPayloadLength;
364       this._fragments.push(data);
365     }
366
367     return this.dataMessage();
368   }
369
370   /**
371    * Decompresses data.
372    *
373    * @param {Buffer} data Compressed data
374    * @param {Function} cb Callback
375    * @private
376    */
377   decompress(data, cb) {
378     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
379
380     perMessageDeflate.decompress(data, this._fin, (err, buf) => {
381       if (err) return cb(err);
382
383       if (buf.length) {
384         this._messageLength += buf.length;
385         if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
386           return cb(
387             error(RangeError, 'Max payload size exceeded', false, 1009)
388           );
389         }
390
391         this._fragments.push(buf);
392       }
393
394       const er = this.dataMessage();
395       if (er) return cb(er);
396
397       this.startLoop(cb);
398     });
399   }
400
401   /**
402    * Handles a data message.
403    *
404    * @return {(Error|undefined)} A possible error
405    * @private
406    */
407   dataMessage() {
408     if (this._fin) {
409       const messageLength = this._messageLength;
410       const fragments = this._fragments;
411
412       this._totalPayloadLength = 0;
413       this._messageLength = 0;
414       this._fragmented = 0;
415       this._fragments = [];
416
417       if (this._opcode === 2) {
418         let data;
419
420         if (this._binaryType === 'nodebuffer') {
421           data = concat(fragments, messageLength);
422         } else if (this._binaryType === 'arraybuffer') {
423           data = toArrayBuffer(concat(fragments, messageLength));
424         } else {
425           data = fragments;
426         }
427
428         this.emit('message', data);
429       } else {
430         const buf = concat(fragments, messageLength);
431
432         if (!isValidUTF8(buf)) {
433           this._loop = false;
434           return error(Error, 'invalid UTF-8 sequence', true, 1007);
435         }
436
437         this.emit('message', buf.toString());
438       }
439     }
440
441     this._state = GET_INFO;
442   }
443
444   /**
445    * Handles a control message.
446    *
447    * @param {Buffer} data Data to handle
448    * @return {(Error|RangeError|undefined)} A possible error
449    * @private
450    */
451   controlMessage(data) {
452     if (this._opcode === 0x08) {
453       this._loop = false;
454
455       if (data.length === 0) {
456         this.emit('conclude', 1005, '');
457         this.end();
458       } else if (data.length === 1) {
459         return error(RangeError, 'invalid payload length 1', true, 1002);
460       } else {
461         const code = data.readUInt16BE(0);
462
463         if (!isValidStatusCode(code)) {
464           return error(RangeError, `invalid status code ${code}`, true, 1002);
465         }
466
467         const buf = data.slice(2);
468
469         if (!isValidUTF8(buf)) {
470           return error(Error, 'invalid UTF-8 sequence', true, 1007);
471         }
472
473         this.emit('conclude', code, buf.toString());
474         this.end();
475       }
476     } else if (this._opcode === 0x09) {
477       this.emit('ping', data);
478     } else {
479       this.emit('pong', data);
480     }
481
482     this._state = GET_INFO;
483   }
484 }
485
486 module.exports = Receiver;
487
488 /**
489  * Builds an error object.
490  *
491  * @param {(Error|RangeError)} ErrorCtor The error constructor
492  * @param {String} message The error message
493  * @param {Boolean} prefix Specifies whether or not to add a default prefix to
494  *     `message`
495  * @param {Number} statusCode The status code
496  * @return {(Error|RangeError)} The error
497  * @private
498  */
499 function error(ErrorCtor, message, prefix, statusCode) {
500   const err = new ErrorCtor(
501     prefix ? `Invalid WebSocket frame: ${message}` : message
502   );
503
504   Error.captureStackTrace(err, error);
505   err[kStatusCode] = statusCode;
506   return err;
507 }