index.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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",
  11. "connect_error",
  12. "disconnect",
  13. "disconnecting",
  14. "newListener",
  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. // see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
  108. function isObject(value) {
  109. return Object.prototype.toString.call(value) === "[object Object]";
  110. }
  111. /**
  112. * A socket.io Decoder instance
  113. *
  114. * @return {Object} decoder
  115. */
  116. export class Decoder extends Emitter {
  117. /**
  118. * Decoder constructor
  119. *
  120. * @param {function} reviver - custom reviver to pass down to JSON.stringify
  121. */
  122. constructor(reviver) {
  123. super();
  124. this.reviver = reviver;
  125. }
  126. /**
  127. * Decodes an encoded packet string into packet JSON.
  128. *
  129. * @param {String} obj - encoded packet
  130. */
  131. add(obj) {
  132. let packet;
  133. if (typeof obj === "string") {
  134. if (this.reconstructor) {
  135. throw new Error("got plaintext data when reconstructing a packet");
  136. }
  137. packet = this.decodeString(obj);
  138. const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
  139. if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
  140. packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
  141. // binary packet's json
  142. this.reconstructor = new BinaryReconstructor(packet);
  143. // no attachments, labeled binary but no binary data to follow
  144. if (packet.attachments === 0) {
  145. super.emitReserved("decoded", packet);
  146. }
  147. }
  148. else {
  149. // non-binary full packet
  150. super.emitReserved("decoded", packet);
  151. }
  152. }
  153. else if (isBinary(obj) || obj.base64) {
  154. // raw binary data
  155. if (!this.reconstructor) {
  156. throw new Error("got binary data when not reconstructing a packet");
  157. }
  158. else {
  159. packet = this.reconstructor.takeBinaryData(obj);
  160. if (packet) {
  161. // received final buffer
  162. this.reconstructor = null;
  163. super.emitReserved("decoded", packet);
  164. }
  165. }
  166. }
  167. else {
  168. throw new Error("Unknown type: " + obj);
  169. }
  170. }
  171. /**
  172. * Decode a packet String (JSON data)
  173. *
  174. * @param {String} str
  175. * @return {Object} packet
  176. */
  177. decodeString(str) {
  178. let i = 0;
  179. // look up type
  180. const p = {
  181. type: Number(str.charAt(0)),
  182. };
  183. if (PacketType[p.type] === undefined) {
  184. throw new Error("unknown packet type " + p.type);
  185. }
  186. // look up attachments if type binary
  187. if (p.type === PacketType.BINARY_EVENT ||
  188. p.type === PacketType.BINARY_ACK) {
  189. const start = i + 1;
  190. while (str.charAt(++i) !== "-" && i != str.length) { }
  191. const buf = str.substring(start, i);
  192. if (buf != Number(buf) || str.charAt(i) !== "-") {
  193. throw new Error("Illegal attachments");
  194. }
  195. p.attachments = Number(buf);
  196. }
  197. // look up namespace (if any)
  198. if ("/" === str.charAt(i + 1)) {
  199. const start = i + 1;
  200. while (++i) {
  201. const c = str.charAt(i);
  202. if ("," === c)
  203. break;
  204. if (i === str.length)
  205. break;
  206. }
  207. p.nsp = str.substring(start, i);
  208. }
  209. else {
  210. p.nsp = "/";
  211. }
  212. // look up id
  213. const next = str.charAt(i + 1);
  214. if ("" !== next && Number(next) == next) {
  215. const start = i + 1;
  216. while (++i) {
  217. const c = str.charAt(i);
  218. if (null == c || Number(c) != c) {
  219. --i;
  220. break;
  221. }
  222. if (i === str.length)
  223. break;
  224. }
  225. p.id = Number(str.substring(start, i + 1));
  226. }
  227. // look up json data
  228. if (str.charAt(++i)) {
  229. const payload = this.tryParse(str.substr(i));
  230. if (Decoder.isPayloadValid(p.type, payload)) {
  231. p.data = payload;
  232. }
  233. else {
  234. throw new Error("invalid payload");
  235. }
  236. }
  237. debug("decoded %s as %j", str, p);
  238. return p;
  239. }
  240. tryParse(str) {
  241. try {
  242. return JSON.parse(str, this.reviver);
  243. }
  244. catch (e) {
  245. return false;
  246. }
  247. }
  248. static isPayloadValid(type, payload) {
  249. switch (type) {
  250. case PacketType.CONNECT:
  251. return isObject(payload);
  252. case PacketType.DISCONNECT:
  253. return payload === undefined;
  254. case PacketType.CONNECT_ERROR:
  255. return typeof payload === "string" || isObject(payload);
  256. case PacketType.EVENT:
  257. case PacketType.BINARY_EVENT:
  258. return (Array.isArray(payload) &&
  259. (typeof payload[0] === "number" ||
  260. (typeof payload[0] === "string" &&
  261. RESERVED_EVENTS.indexOf(payload[0]) === -1)));
  262. case PacketType.ACK:
  263. case PacketType.BINARY_ACK:
  264. return Array.isArray(payload);
  265. }
  266. }
  267. /**
  268. * Deallocates a parser's resources
  269. */
  270. destroy() {
  271. if (this.reconstructor) {
  272. this.reconstructor.finishedReconstruction();
  273. this.reconstructor = null;
  274. }
  275. }
  276. }
  277. /**
  278. * A manager of a binary event's 'buffer sequence'. Should
  279. * be constructed whenever a packet of type BINARY_EVENT is
  280. * decoded.
  281. *
  282. * @param {Object} packet
  283. * @return {BinaryReconstructor} initialized reconstructor
  284. */
  285. class BinaryReconstructor {
  286. constructor(packet) {
  287. this.packet = packet;
  288. this.buffers = [];
  289. this.reconPack = packet;
  290. }
  291. /**
  292. * Method to be called when binary data received from connection
  293. * after a BINARY_EVENT packet.
  294. *
  295. * @param {Buffer | ArrayBuffer} binData - the raw binary data received
  296. * @return {null | Object} returns null if more binary data is expected or
  297. * a reconstructed packet object if all buffers have been received.
  298. */
  299. takeBinaryData(binData) {
  300. this.buffers.push(binData);
  301. if (this.buffers.length === this.reconPack.attachments) {
  302. // done with buffer list
  303. const packet = reconstructPacket(this.reconPack, this.buffers);
  304. this.finishedReconstruction();
  305. return packet;
  306. }
  307. return null;
  308. }
  309. /**
  310. * Cleans up binary packet reconstruction variables.
  311. */
  312. finishedReconstruction() {
  313. this.reconPack = null;
  314. this.buffers = [];
  315. }
  316. }