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.

412 lines
7.9 KiB

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