index.js 12 KB

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