You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

507 lines
12 KiB

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