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.

492 lines
12 KiB

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