index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Decoder = exports.Encoder = exports.PacketType = exports.protocol = void 0;
  4. exports.isPacketValid = isPacketValid;
  5. const component_emitter_1 = require("@socket.io/component-emitter");
  6. const binary_js_1 = require("./binary.js");
  7. const is_binary_js_1 = require("./is-binary.js");
  8. const debug_1 = require("debug"); // debug()
  9. const debug = (0, debug_1.default)("socket.io-parser"); // debug()
  10. /**
  11. * These strings must not be used as event names, as they have a special meaning.
  12. */
  13. const RESERVED_EVENTS = [
  14. "connect", // used on the client side
  15. "connect_error", // used on the client side
  16. "disconnect", // used on both sides
  17. "disconnecting", // used on the server side
  18. "newListener", // used by the Node.js EventEmitter
  19. "removeListener", // used by the Node.js EventEmitter
  20. ];
  21. /**
  22. * Protocol version.
  23. *
  24. * @public
  25. */
  26. exports.protocol = 5;
  27. var PacketType;
  28. (function (PacketType) {
  29. PacketType[PacketType["CONNECT"] = 0] = "CONNECT";
  30. PacketType[PacketType["DISCONNECT"] = 1] = "DISCONNECT";
  31. PacketType[PacketType["EVENT"] = 2] = "EVENT";
  32. PacketType[PacketType["ACK"] = 3] = "ACK";
  33. PacketType[PacketType["CONNECT_ERROR"] = 4] = "CONNECT_ERROR";
  34. PacketType[PacketType["BINARY_EVENT"] = 5] = "BINARY_EVENT";
  35. PacketType[PacketType["BINARY_ACK"] = 6] = "BINARY_ACK";
  36. })(PacketType || (exports.PacketType = PacketType = {}));
  37. /**
  38. * A socket.io Encoder instance
  39. */
  40. class Encoder {
  41. /**
  42. * Encoder constructor
  43. *
  44. * @param {function} replacer - custom replacer to pass down to JSON.parse
  45. */
  46. constructor(replacer) {
  47. this.replacer = replacer;
  48. }
  49. /**
  50. * Encode a packet as a single string if non-binary, or as a
  51. * buffer sequence, depending on packet type.
  52. *
  53. * @param {Object} obj - packet object
  54. */
  55. encode(obj) {
  56. debug("encoding packet %j", obj);
  57. if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
  58. if ((0, is_binary_js_1.hasBinary)(obj)) {
  59. return this.encodeAsBinary({
  60. type: obj.type === PacketType.EVENT
  61. ? PacketType.BINARY_EVENT
  62. : PacketType.BINARY_ACK,
  63. nsp: obj.nsp,
  64. data: obj.data,
  65. id: obj.id,
  66. });
  67. }
  68. }
  69. return [this.encodeAsString(obj)];
  70. }
  71. /**
  72. * Encode packet as string.
  73. */
  74. encodeAsString(obj) {
  75. // first is type
  76. let str = "" + obj.type;
  77. // attachments if we have them
  78. if (obj.type === PacketType.BINARY_EVENT ||
  79. obj.type === PacketType.BINARY_ACK) {
  80. str += obj.attachments + "-";
  81. }
  82. // if we have a namespace other than `/`
  83. // we append it followed by a comma `,`
  84. if (obj.nsp && "/" !== obj.nsp) {
  85. str += obj.nsp + ",";
  86. }
  87. // immediately followed by the id
  88. if (null != obj.id) {
  89. str += obj.id;
  90. }
  91. // json data
  92. if (null != obj.data) {
  93. str += JSON.stringify(obj.data, this.replacer);
  94. }
  95. debug("encoded %j as %s", obj, str);
  96. return str;
  97. }
  98. /**
  99. * Encode packet as 'buffer sequence' by removing blobs, and
  100. * deconstructing packet into object with placeholders and
  101. * a list of buffers.
  102. */
  103. encodeAsBinary(obj) {
  104. const deconstruction = (0, binary_js_1.deconstructPacket)(obj);
  105. const pack = this.encodeAsString(deconstruction.packet);
  106. const buffers = deconstruction.buffers;
  107. buffers.unshift(pack); // add packet info to beginning of data list
  108. return buffers; // write all the buffers
  109. }
  110. }
  111. exports.Encoder = Encoder;
  112. /**
  113. * A socket.io Decoder instance
  114. *
  115. * @return {Object} decoder
  116. */
  117. class Decoder extends component_emitter_1.Emitter {
  118. /**
  119. * Decoder constructor
  120. */
  121. constructor(opts) {
  122. super();
  123. this.opts = Object.assign({
  124. reviver: undefined,
  125. maxAttachments: 10,
  126. }, typeof opts === "function" ? { reviver: opts } : opts);
  127. }
  128. /**
  129. * Decodes an encoded packet string into packet JSON.
  130. *
  131. * @param {String} obj - encoded packet
  132. */
  133. add(obj) {
  134. let packet;
  135. if (typeof obj === "string") {
  136. if (this.reconstructor) {
  137. throw new Error("got plaintext data when reconstructing a packet");
  138. }
  139. packet = this.decodeString(obj);
  140. const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
  141. if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
  142. packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
  143. // binary packet's json
  144. this.reconstructor = new BinaryReconstructor(packet);
  145. // no attachments, labeled binary but no binary data to follow
  146. if (packet.attachments === 0) {
  147. super.emitReserved("decoded", packet);
  148. }
  149. }
  150. else {
  151. // non-binary full packet
  152. super.emitReserved("decoded", packet);
  153. }
  154. }
  155. else if ((0, is_binary_js_1.isBinary)(obj) || obj.base64) {
  156. // raw binary data
  157. if (!this.reconstructor) {
  158. throw new Error("got binary data when not reconstructing a packet");
  159. }
  160. else {
  161. packet = this.reconstructor.takeBinaryData(obj);
  162. if (packet) {
  163. // received final buffer
  164. this.reconstructor = null;
  165. super.emitReserved("decoded", packet);
  166. }
  167. }
  168. }
  169. else {
  170. throw new Error("Unknown type: " + obj);
  171. }
  172. }
  173. /**
  174. * Decode a packet String (JSON data)
  175. *
  176. * @param {String} str
  177. * @return {Object} packet
  178. */
  179. decodeString(str) {
  180. let i = 0;
  181. // look up type
  182. const p = {
  183. type: Number(str.charAt(0)),
  184. };
  185. if (PacketType[p.type] === undefined) {
  186. throw new Error("unknown packet type " + p.type);
  187. }
  188. // look up attachments if type binary
  189. if (p.type === PacketType.BINARY_EVENT ||
  190. p.type === PacketType.BINARY_ACK) {
  191. const start = i + 1;
  192. while (str.charAt(++i) !== "-" && i != str.length) { }
  193. const buf = str.substring(start, i);
  194. if (buf != Number(buf) || str.charAt(i) !== "-") {
  195. throw new Error("Illegal attachments");
  196. }
  197. const n = Number(buf);
  198. if (!isInteger(n) || n < 0) {
  199. throw new Error("Illegal attachments");
  200. }
  201. else if (n > this.opts.maxAttachments) {
  202. throw new Error("too many attachments");
  203. }
  204. p.attachments = n;
  205. }
  206. // look up namespace (if any)
  207. if ("/" === str.charAt(i + 1)) {
  208. const start = i + 1;
  209. while (++i) {
  210. const c = str.charAt(i);
  211. if ("," === c)
  212. break;
  213. if (i === str.length)
  214. break;
  215. }
  216. p.nsp = str.substring(start, i);
  217. }
  218. else {
  219. p.nsp = "/";
  220. }
  221. // look up id
  222. const next = str.charAt(i + 1);
  223. if ("" !== next && Number(next) == next) {
  224. const start = i + 1;
  225. while (++i) {
  226. const c = str.charAt(i);
  227. if (null == c || Number(c) != c) {
  228. --i;
  229. break;
  230. }
  231. if (i === str.length)
  232. break;
  233. }
  234. p.id = Number(str.substring(start, i + 1));
  235. }
  236. // look up json data
  237. if (str.charAt(++i)) {
  238. const payload = this.tryParse(str.substr(i));
  239. if (Decoder.isPayloadValid(p.type, payload)) {
  240. p.data = payload;
  241. }
  242. else {
  243. throw new Error("invalid payload");
  244. }
  245. }
  246. debug("decoded %s as %j", str, p);
  247. return p;
  248. }
  249. tryParse(str) {
  250. try {
  251. return JSON.parse(str, this.opts.reviver);
  252. }
  253. catch (e) {
  254. return false;
  255. }
  256. }
  257. static isPayloadValid(type, payload) {
  258. switch (type) {
  259. case PacketType.CONNECT:
  260. return isObject(payload);
  261. case PacketType.DISCONNECT:
  262. return payload === undefined;
  263. case PacketType.CONNECT_ERROR:
  264. return typeof payload === "string" || isObject(payload);
  265. case PacketType.EVENT:
  266. case PacketType.BINARY_EVENT:
  267. return (Array.isArray(payload) &&
  268. (typeof payload[0] === "number" ||
  269. (typeof payload[0] === "string" &&
  270. RESERVED_EVENTS.indexOf(payload[0]) === -1)));
  271. case PacketType.ACK:
  272. case PacketType.BINARY_ACK:
  273. return Array.isArray(payload);
  274. }
  275. }
  276. /**
  277. * Deallocates a parser's resources
  278. */
  279. destroy() {
  280. if (this.reconstructor) {
  281. this.reconstructor.finishedReconstruction();
  282. this.reconstructor = null;
  283. }
  284. }
  285. }
  286. exports.Decoder = Decoder;
  287. /**
  288. * A manager of a binary event's 'buffer sequence'. Should
  289. * be constructed whenever a packet of type BINARY_EVENT is
  290. * decoded.
  291. *
  292. * @param {Object} packet
  293. * @return {BinaryReconstructor} initialized reconstructor
  294. */
  295. class BinaryReconstructor {
  296. constructor(packet) {
  297. this.packet = packet;
  298. this.buffers = [];
  299. this.reconPack = packet;
  300. }
  301. /**
  302. * Method to be called when binary data received from connection
  303. * after a BINARY_EVENT packet.
  304. *
  305. * @param {Buffer | ArrayBuffer} binData - the raw binary data received
  306. * @return {null | Object} returns null if more binary data is expected or
  307. * a reconstructed packet object if all buffers have been received.
  308. */
  309. takeBinaryData(binData) {
  310. this.buffers.push(binData);
  311. if (this.buffers.length === this.reconPack.attachments) {
  312. // done with buffer list
  313. const packet = (0, binary_js_1.reconstructPacket)(this.reconPack, this.buffers);
  314. this.finishedReconstruction();
  315. return packet;
  316. }
  317. return null;
  318. }
  319. /**
  320. * Cleans up binary packet reconstruction variables.
  321. */
  322. finishedReconstruction() {
  323. this.reconPack = null;
  324. this.buffers = [];
  325. }
  326. }
  327. function isNamespaceValid(nsp) {
  328. return typeof nsp === "string";
  329. }
  330. // see https://caniuse.com/mdn-javascript_builtins_number_isinteger
  331. const isInteger = Number.isInteger ||
  332. function (value) {
  333. return (typeof value === "number" &&
  334. isFinite(value) &&
  335. Math.floor(value) === value);
  336. };
  337. function isAckIdValid(id) {
  338. return id === undefined || isInteger(id);
  339. }
  340. // see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
  341. function isObject(value) {
  342. return Object.prototype.toString.call(value) === "[object Object]";
  343. }
  344. function isDataValid(type, payload) {
  345. switch (type) {
  346. case PacketType.CONNECT:
  347. return payload === undefined || isObject(payload);
  348. case PacketType.DISCONNECT:
  349. return payload === undefined;
  350. case PacketType.EVENT:
  351. return (Array.isArray(payload) &&
  352. (typeof payload[0] === "number" ||
  353. (typeof payload[0] === "string" &&
  354. RESERVED_EVENTS.indexOf(payload[0]) === -1)));
  355. case PacketType.ACK:
  356. return Array.isArray(payload);
  357. case PacketType.CONNECT_ERROR:
  358. return typeof payload === "string" || isObject(payload);
  359. default:
  360. return false;
  361. }
  362. }
  363. function isPacketValid(packet) {
  364. return (isNamespaceValid(packet.nsp) &&
  365. isAckIdValid(packet.id) &&
  366. isDataValid(packet.type, packet.data));
  367. }