source: trip-planner-front/node_modules/ws/lib/receiver.js@ 188ee53

Last change on this file since 188ee53 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 12.1 KB
RevLine 
[6a3a178]1'use strict';
2
3const { Writable } = require('stream');
4
5const PerMessageDeflate = require('./permessage-deflate');
6const {
7 BINARY_TYPES,
8 EMPTY_BUFFER,
9 kStatusCode,
10 kWebSocket
11} = require('./constants');
12const { concat, toArrayBuffer, unmask } = require('./buffer-util');
13const { isValidStatusCode, isValidUTF8 } = require('./validation');
14
15const GET_INFO = 0;
16const GET_PAYLOAD_LENGTH_16 = 1;
17const GET_PAYLOAD_LENGTH_64 = 2;
18const GET_MASK = 3;
19const GET_DATA = 4;
20const INFLATING = 5;
21
22/**
23 * HyBi Receiver implementation.
24 *
25 * @extends stream.Writable
26 */
27class 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
486module.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 */
499function 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}
Note: See TracBrowser for help on using the repository browser.