receiver.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. 'use strict';
  2. const { Writable } = require('stream');
  3. const PerMessageDeflate = require('./permessage-deflate');
  4. const {
  5. BINARY_TYPES,
  6. EMPTY_BUFFER,
  7. kStatusCode,
  8. kWebSocket
  9. } = require('./constants');
  10. const { concat, toArrayBuffer, unmask } = require('./buffer-util');
  11. const { isValidStatusCode, isValidUTF8 } = require('./validation');
  12. const FastBuffer = Buffer[Symbol.species];
  13. const GET_INFO = 0;
  14. const GET_PAYLOAD_LENGTH_16 = 1;
  15. const GET_PAYLOAD_LENGTH_64 = 2;
  16. const GET_MASK = 3;
  17. const GET_DATA = 4;
  18. const INFLATING = 5;
  19. const DEFER_EVENT = 6;
  20. /**
  21. * HyBi Receiver implementation.
  22. *
  23. * @extends Writable
  24. */
  25. class Receiver extends Writable {
  26. /**
  27. * Creates a Receiver instance.
  28. *
  29. * @param {Object} [options] Options object
  30. * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether
  31. * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
  32. * multiple times in the same tick
  33. * @param {String} [options.binaryType=nodebuffer] The type for binary data
  34. * @param {Object} [options.extensions] An object containing the negotiated
  35. * extensions
  36. * @param {Boolean} [options.isServer=false] Specifies whether to operate in
  37. * client or server mode
  38. * @param {Number} [options.maxBufferedChunks=0] The maximum number of
  39. * buffered data chunks
  40. * @param {Number} [options.maxFragments=0] The maximum number of message
  41. * fragments
  42. * @param {Number} [options.maxPayload=0] The maximum allowed message length
  43. * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
  44. * not to skip UTF-8 validation for text and close messages
  45. */
  46. constructor(options = {}) {
  47. super();
  48. this._allowSynchronousEvents =
  49. options.allowSynchronousEvents !== undefined
  50. ? options.allowSynchronousEvents
  51. : true;
  52. this._binaryType = options.binaryType || BINARY_TYPES[0];
  53. this._extensions = options.extensions || {};
  54. this._isServer = !!options.isServer;
  55. this._maxBufferedChunks = options.maxBufferedChunks | 0;
  56. this._maxFragments = options.maxFragments | 0;
  57. this._maxPayload = options.maxPayload | 0;
  58. this._skipUTF8Validation = !!options.skipUTF8Validation;
  59. this[kWebSocket] = undefined;
  60. this._bufferedBytes = 0;
  61. this._buffers = [];
  62. this._compressed = false;
  63. this._payloadLength = 0;
  64. this._mask = undefined;
  65. this._fragmented = 0;
  66. this._masked = false;
  67. this._fin = false;
  68. this._opcode = 0;
  69. this._totalPayloadLength = 0;
  70. this._messageLength = 0;
  71. this._numFragments = 0;
  72. this._fragments = [];
  73. this._errored = false;
  74. this._loop = false;
  75. this._state = GET_INFO;
  76. }
  77. /**
  78. * Implements `Writable.prototype._write()`.
  79. *
  80. * @param {Buffer} chunk The chunk of data to write
  81. * @param {String} encoding The character encoding of `chunk`
  82. * @param {Function} cb Callback
  83. * @private
  84. */
  85. _write(chunk, encoding, cb) {
  86. if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
  87. if (
  88. this._maxBufferedChunks > 0 &&
  89. this._buffers.length >= this._maxBufferedChunks
  90. ) {
  91. cb(
  92. this.createError(
  93. RangeError,
  94. 'Too many buffered chunks',
  95. false,
  96. 1008,
  97. 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
  98. )
  99. );
  100. return;
  101. }
  102. this._bufferedBytes += chunk.length;
  103. this._buffers.push(chunk);
  104. this.startLoop(cb);
  105. }
  106. /**
  107. * Consumes `n` bytes from the buffered data.
  108. *
  109. * @param {Number} n The number of bytes to consume
  110. * @return {Buffer} The consumed bytes
  111. * @private
  112. */
  113. consume(n) {
  114. this._bufferedBytes -= n;
  115. if (n === this._buffers[0].length) return this._buffers.shift();
  116. if (n < this._buffers[0].length) {
  117. const buf = this._buffers[0];
  118. this._buffers[0] = new FastBuffer(
  119. buf.buffer,
  120. buf.byteOffset + n,
  121. buf.length - n
  122. );
  123. return new FastBuffer(buf.buffer, buf.byteOffset, n);
  124. }
  125. const dst = Buffer.allocUnsafe(n);
  126. do {
  127. const buf = this._buffers[0];
  128. const offset = dst.length - n;
  129. if (n >= buf.length) {
  130. dst.set(this._buffers.shift(), offset);
  131. } else {
  132. dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
  133. this._buffers[0] = new FastBuffer(
  134. buf.buffer,
  135. buf.byteOffset + n,
  136. buf.length - n
  137. );
  138. }
  139. n -= buf.length;
  140. } while (n > 0);
  141. return dst;
  142. }
  143. /**
  144. * Starts the parsing loop.
  145. *
  146. * @param {Function} cb Callback
  147. * @private
  148. */
  149. startLoop(cb) {
  150. this._loop = true;
  151. do {
  152. switch (this._state) {
  153. case GET_INFO:
  154. this.getInfo(cb);
  155. break;
  156. case GET_PAYLOAD_LENGTH_16:
  157. this.getPayloadLength16(cb);
  158. break;
  159. case GET_PAYLOAD_LENGTH_64:
  160. this.getPayloadLength64(cb);
  161. break;
  162. case GET_MASK:
  163. this.getMask();
  164. break;
  165. case GET_DATA:
  166. this.getData(cb);
  167. break;
  168. case INFLATING:
  169. case DEFER_EVENT:
  170. this._loop = false;
  171. return;
  172. }
  173. } while (this._loop);
  174. if (!this._errored) cb();
  175. }
  176. /**
  177. * Reads the first two bytes of a frame.
  178. *
  179. * @param {Function} cb Callback
  180. * @private
  181. */
  182. getInfo(cb) {
  183. if (this._bufferedBytes < 2) {
  184. this._loop = false;
  185. return;
  186. }
  187. const buf = this.consume(2);
  188. if ((buf[0] & 0x30) !== 0x00) {
  189. const error = this.createError(
  190. RangeError,
  191. 'RSV2 and RSV3 must be clear',
  192. true,
  193. 1002,
  194. 'WS_ERR_UNEXPECTED_RSV_2_3'
  195. );
  196. cb(error);
  197. return;
  198. }
  199. const compressed = (buf[0] & 0x40) === 0x40;
  200. if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
  201. const error = this.createError(
  202. RangeError,
  203. 'RSV1 must be clear',
  204. true,
  205. 1002,
  206. 'WS_ERR_UNEXPECTED_RSV_1'
  207. );
  208. cb(error);
  209. return;
  210. }
  211. this._fin = (buf[0] & 0x80) === 0x80;
  212. this._opcode = buf[0] & 0x0f;
  213. this._payloadLength = buf[1] & 0x7f;
  214. if (this._opcode === 0x00) {
  215. if (compressed) {
  216. const error = this.createError(
  217. RangeError,
  218. 'RSV1 must be clear',
  219. true,
  220. 1002,
  221. 'WS_ERR_UNEXPECTED_RSV_1'
  222. );
  223. cb(error);
  224. return;
  225. }
  226. if (!this._fragmented) {
  227. const error = this.createError(
  228. RangeError,
  229. 'invalid opcode 0',
  230. true,
  231. 1002,
  232. 'WS_ERR_INVALID_OPCODE'
  233. );
  234. cb(error);
  235. return;
  236. }
  237. this._opcode = this._fragmented;
  238. } else if (this._opcode === 0x01 || this._opcode === 0x02) {
  239. if (this._fragmented) {
  240. const error = this.createError(
  241. RangeError,
  242. `invalid opcode ${this._opcode}`,
  243. true,
  244. 1002,
  245. 'WS_ERR_INVALID_OPCODE'
  246. );
  247. cb(error);
  248. return;
  249. }
  250. this._compressed = compressed;
  251. } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
  252. if (!this._fin) {
  253. const error = this.createError(
  254. RangeError,
  255. 'FIN must be set',
  256. true,
  257. 1002,
  258. 'WS_ERR_EXPECTED_FIN'
  259. );
  260. cb(error);
  261. return;
  262. }
  263. if (compressed) {
  264. const error = this.createError(
  265. RangeError,
  266. 'RSV1 must be clear',
  267. true,
  268. 1002,
  269. 'WS_ERR_UNEXPECTED_RSV_1'
  270. );
  271. cb(error);
  272. return;
  273. }
  274. if (
  275. this._payloadLength > 0x7d ||
  276. (this._opcode === 0x08 && this._payloadLength === 1)
  277. ) {
  278. const error = this.createError(
  279. RangeError,
  280. `invalid payload length ${this._payloadLength}`,
  281. true,
  282. 1002,
  283. 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
  284. );
  285. cb(error);
  286. return;
  287. }
  288. } else {
  289. const error = this.createError(
  290. RangeError,
  291. `invalid opcode ${this._opcode}`,
  292. true,
  293. 1002,
  294. 'WS_ERR_INVALID_OPCODE'
  295. );
  296. cb(error);
  297. return;
  298. }
  299. if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
  300. this._masked = (buf[1] & 0x80) === 0x80;
  301. if (this._isServer) {
  302. if (!this._masked) {
  303. const error = this.createError(
  304. RangeError,
  305. 'MASK must be set',
  306. true,
  307. 1002,
  308. 'WS_ERR_EXPECTED_MASK'
  309. );
  310. cb(error);
  311. return;
  312. }
  313. } else if (this._masked) {
  314. const error = this.createError(
  315. RangeError,
  316. 'MASK must be clear',
  317. true,
  318. 1002,
  319. 'WS_ERR_UNEXPECTED_MASK'
  320. );
  321. cb(error);
  322. return;
  323. }
  324. if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
  325. else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
  326. else this.haveLength(cb);
  327. }
  328. /**
  329. * Gets extended payload length (7+16).
  330. *
  331. * @param {Function} cb Callback
  332. * @private
  333. */
  334. getPayloadLength16(cb) {
  335. if (this._bufferedBytes < 2) {
  336. this._loop = false;
  337. return;
  338. }
  339. this._payloadLength = this.consume(2).readUInt16BE(0);
  340. this.haveLength(cb);
  341. }
  342. /**
  343. * Gets extended payload length (7+64).
  344. *
  345. * @param {Function} cb Callback
  346. * @private
  347. */
  348. getPayloadLength64(cb) {
  349. if (this._bufferedBytes < 8) {
  350. this._loop = false;
  351. return;
  352. }
  353. const buf = this.consume(8);
  354. const num = buf.readUInt32BE(0);
  355. //
  356. // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
  357. // if payload length is greater than this number.
  358. //
  359. if (num > Math.pow(2, 53 - 32) - 1) {
  360. const error = this.createError(
  361. RangeError,
  362. 'Unsupported WebSocket frame: payload length > 2^53 - 1',
  363. false,
  364. 1009,
  365. 'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
  366. );
  367. cb(error);
  368. return;
  369. }
  370. this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
  371. this.haveLength(cb);
  372. }
  373. /**
  374. * Payload length has been read.
  375. *
  376. * @param {Function} cb Callback
  377. * @private
  378. */
  379. haveLength(cb) {
  380. if (this._payloadLength && this._opcode < 0x08) {
  381. this._totalPayloadLength += this._payloadLength;
  382. if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
  383. const error = this.createError(
  384. RangeError,
  385. 'Max payload size exceeded',
  386. false,
  387. 1009,
  388. 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
  389. );
  390. cb(error);
  391. return;
  392. }
  393. }
  394. if (this._masked) this._state = GET_MASK;
  395. else this._state = GET_DATA;
  396. }
  397. /**
  398. * Reads mask bytes.
  399. *
  400. * @private
  401. */
  402. getMask() {
  403. if (this._bufferedBytes < 4) {
  404. this._loop = false;
  405. return;
  406. }
  407. this._mask = this.consume(4);
  408. this._state = GET_DATA;
  409. }
  410. /**
  411. * Reads data bytes.
  412. *
  413. * @param {Function} cb Callback
  414. * @private
  415. */
  416. getData(cb) {
  417. let data = EMPTY_BUFFER;
  418. if (this._payloadLength) {
  419. if (this._bufferedBytes < this._payloadLength) {
  420. this._loop = false;
  421. return;
  422. }
  423. data = this.consume(this._payloadLength);
  424. if (
  425. this._masked &&
  426. (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0
  427. ) {
  428. unmask(data, this._mask);
  429. }
  430. }
  431. if (this._opcode > 0x07) {
  432. this.controlMessage(data, cb);
  433. return;
  434. }
  435. if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
  436. const error = this.createError(
  437. RangeError,
  438. 'Too many message fragments',
  439. false,
  440. 1008,
  441. 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
  442. );
  443. cb(error);
  444. return;
  445. }
  446. if (this._compressed) {
  447. this._state = INFLATING;
  448. this.decompress(data, cb);
  449. return;
  450. }
  451. if (data.length) {
  452. //
  453. // This message is not compressed so its length is the sum of the payload
  454. // length of all fragments.
  455. //
  456. this._messageLength = this._totalPayloadLength;
  457. this._fragments.push(data);
  458. }
  459. this.dataMessage(cb);
  460. }
  461. /**
  462. * Decompresses data.
  463. *
  464. * @param {Buffer} data Compressed data
  465. * @param {Function} cb Callback
  466. * @private
  467. */
  468. decompress(data, cb) {
  469. const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
  470. perMessageDeflate.decompress(data, this._fin, (err, buf) => {
  471. if (err) return cb(err);
  472. if (buf.length) {
  473. this._messageLength += buf.length;
  474. if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
  475. const error = this.createError(
  476. RangeError,
  477. 'Max payload size exceeded',
  478. false,
  479. 1009,
  480. 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
  481. );
  482. cb(error);
  483. return;
  484. }
  485. this._fragments.push(buf);
  486. }
  487. this.dataMessage(cb);
  488. if (this._state === GET_INFO) this.startLoop(cb);
  489. });
  490. }
  491. /**
  492. * Handles a data message.
  493. *
  494. * @param {Function} cb Callback
  495. * @private
  496. */
  497. dataMessage(cb) {
  498. if (!this._fin) {
  499. this._state = GET_INFO;
  500. return;
  501. }
  502. const messageLength = this._messageLength;
  503. const fragments = this._fragments;
  504. this._totalPayloadLength = 0;
  505. this._messageLength = 0;
  506. this._fragmented = 0;
  507. this._numFragments = 0;
  508. this._fragments = [];
  509. if (this._opcode === 2) {
  510. let data;
  511. if (this._binaryType === 'nodebuffer') {
  512. data = concat(fragments, messageLength);
  513. } else if (this._binaryType === 'arraybuffer') {
  514. data = toArrayBuffer(concat(fragments, messageLength));
  515. } else if (this._binaryType === 'blob') {
  516. data = new Blob(fragments);
  517. } else {
  518. data = fragments;
  519. }
  520. if (this._allowSynchronousEvents) {
  521. this.emit('message', data, true);
  522. this._state = GET_INFO;
  523. } else {
  524. this._state = DEFER_EVENT;
  525. setImmediate(() => {
  526. this.emit('message', data, true);
  527. this._state = GET_INFO;
  528. this.startLoop(cb);
  529. });
  530. }
  531. } else {
  532. const buf = concat(fragments, messageLength);
  533. if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
  534. const error = this.createError(
  535. Error,
  536. 'invalid UTF-8 sequence',
  537. true,
  538. 1007,
  539. 'WS_ERR_INVALID_UTF8'
  540. );
  541. cb(error);
  542. return;
  543. }
  544. if (this._state === INFLATING || this._allowSynchronousEvents) {
  545. this.emit('message', buf, false);
  546. this._state = GET_INFO;
  547. } else {
  548. this._state = DEFER_EVENT;
  549. setImmediate(() => {
  550. this.emit('message', buf, false);
  551. this._state = GET_INFO;
  552. this.startLoop(cb);
  553. });
  554. }
  555. }
  556. }
  557. /**
  558. * Handles a control message.
  559. *
  560. * @param {Buffer} data Data to handle
  561. * @return {(Error|RangeError|undefined)} A possible error
  562. * @private
  563. */
  564. controlMessage(data, cb) {
  565. if (this._opcode === 0x08) {
  566. if (data.length === 0) {
  567. this._loop = false;
  568. this.emit('conclude', 1005, EMPTY_BUFFER);
  569. this.end();
  570. } else {
  571. const code = data.readUInt16BE(0);
  572. if (!isValidStatusCode(code)) {
  573. const error = this.createError(
  574. RangeError,
  575. `invalid status code ${code}`,
  576. true,
  577. 1002,
  578. 'WS_ERR_INVALID_CLOSE_CODE'
  579. );
  580. cb(error);
  581. return;
  582. }
  583. const buf = new FastBuffer(
  584. data.buffer,
  585. data.byteOffset + 2,
  586. data.length - 2
  587. );
  588. if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
  589. const error = this.createError(
  590. Error,
  591. 'invalid UTF-8 sequence',
  592. true,
  593. 1007,
  594. 'WS_ERR_INVALID_UTF8'
  595. );
  596. cb(error);
  597. return;
  598. }
  599. this._loop = false;
  600. this.emit('conclude', code, buf);
  601. this.end();
  602. }
  603. this._state = GET_INFO;
  604. return;
  605. }
  606. if (this._allowSynchronousEvents) {
  607. this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
  608. this._state = GET_INFO;
  609. } else {
  610. this._state = DEFER_EVENT;
  611. setImmediate(() => {
  612. this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
  613. this._state = GET_INFO;
  614. this.startLoop(cb);
  615. });
  616. }
  617. }
  618. /**
  619. * Builds an error object.
  620. *
  621. * @param {function(new:Error|RangeError)} ErrorCtor The error constructor
  622. * @param {String} message The error message
  623. * @param {Boolean} prefix Specifies whether or not to add a default prefix to
  624. * `message`
  625. * @param {Number} statusCode The status code
  626. * @param {String} errorCode The exposed error code
  627. * @return {(Error|RangeError)} The error
  628. * @private
  629. */
  630. createError(ErrorCtor, message, prefix, statusCode, errorCode) {
  631. this._loop = false;
  632. this._errored = true;
  633. const err = new ErrorCtor(
  634. prefix ? `Invalid WebSocket frame: ${message}` : message
  635. );
  636. Error.captureStackTrace(err, this.createError);
  637. err.code = errorCode;
  638. err[kStatusCode] = statusCode;
  639. return err;
  640. }
  641. }
  642. module.exports = Receiver;