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.

404 lines
7.5 KiB

7 years ago
  1. /**
  2. * Module dependencies.
  3. */
  4. var debug = require('debug')('socket.io-parser');
  5. var json = require('json3');
  6. var Emitter = require('component-emitter');
  7. var binary = require('./binary');
  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. /**
  90. * Encode a packet as a single string if non-binary, or as a
  91. * buffer sequence, depending on packet type.
  92. *
  93. * @param {Object} obj - packet object
  94. * @param {Function} callback - function to handle encodings (likely engine.write)
  95. * @return Calls callback with Array of encodings
  96. * @api public
  97. */
  98. Encoder.prototype.encode = function(obj, callback){
  99. debug('encoding packet %j', obj);
  100. if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
  101. encodeAsBinary(obj, callback);
  102. }
  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. var str = '';
  117. var nsp = false;
  118. // first is type
  119. str += obj.type;
  120. // attachments if we have them
  121. if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
  122. str += obj.attachments;
  123. str += '-';
  124. }
  125. // if we have a namespace other than `/`
  126. // we append it followed by a comma `,`
  127. if (obj.nsp && '/' != obj.nsp) {
  128. nsp = true;
  129. str += obj.nsp;
  130. }
  131. // immediately followed by the id
  132. if (null != obj.id) {
  133. if (nsp) {
  134. str += ',';
  135. nsp = false;
  136. }
  137. str += obj.id;
  138. }
  139. // json data
  140. if (null != obj.data) {
  141. if (nsp) str += ',';
  142. str += json.stringify(obj.data);
  143. }
  144. debug('encoded %j as %s', obj, str);
  145. return str;
  146. }
  147. /**
  148. * Encode packet as 'buffer sequence' by removing blobs, and
  149. * deconstructing packet into object with placeholders and
  150. * a list of buffers.
  151. *
  152. * @param {Object} packet
  153. * @return {Buffer} encoded
  154. * @api private
  155. */
  156. function encodeAsBinary(obj, callback) {
  157. function writeEncoding(bloblessData) {
  158. var deconstruction = binary.deconstructPacket(bloblessData);
  159. var pack = encodeAsString(deconstruction.packet);
  160. var buffers = deconstruction.buffers;
  161. buffers.unshift(pack); // add packet info to beginning of data list
  162. callback(buffers); // write all the buffers
  163. }
  164. binary.removeBlobs(obj, writeEncoding);
  165. }
  166. /**
  167. * A socket.io Decoder instance
  168. *
  169. * @return {Object} decoder
  170. * @api public
  171. */
  172. function Decoder() {
  173. this.reconstructor = null;
  174. }
  175. /**
  176. * Mix in `Emitter` with Decoder.
  177. */
  178. Emitter(Decoder.prototype);
  179. /**
  180. * Decodes an ecoded packet string into packet JSON.
  181. *
  182. * @param {String} obj - encoded packet
  183. * @return {Object} packet
  184. * @api public
  185. */
  186. Decoder.prototype.add = function(obj) {
  187. var packet;
  188. if ('string' == typeof obj) {
  189. packet = decodeString(obj);
  190. if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json
  191. this.reconstructor = new BinaryReconstructor(packet);
  192. // no attachments, labeled binary but no binary data to follow
  193. if (this.reconstructor.reconPack.attachments === 0) {
  194. this.emit('decoded', packet);
  195. }
  196. } else { // non-binary full packet
  197. this.emit('decoded', packet);
  198. }
  199. }
  200. else if (isBuf(obj) || obj.base64) { // raw binary data
  201. if (!this.reconstructor) {
  202. throw new Error('got binary data when not reconstructing a packet');
  203. } else {
  204. packet = this.reconstructor.takeBinaryData(obj);
  205. if (packet) { // received final buffer
  206. this.reconstructor = null;
  207. this.emit('decoded', packet);
  208. }
  209. }
  210. }
  211. else {
  212. throw new Error('Unknown type: ' + obj);
  213. }
  214. };
  215. /**
  216. * Decode a packet String (JSON data)
  217. *
  218. * @param {String} str
  219. * @return {Object} packet
  220. * @api private
  221. */
  222. function decodeString(str) {
  223. var p = {};
  224. var i = 0;
  225. // look up type
  226. p.type = Number(str.charAt(0));
  227. if (null == exports.types[p.type]) return error();
  228. // look up attachments if type binary
  229. if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) {
  230. var buf = '';
  231. while (str.charAt(++i) != '-') {
  232. buf += str.charAt(i);
  233. if (i == str.length) break;
  234. }
  235. if (buf != Number(buf) || str.charAt(i) != '-') {
  236. throw new Error('Illegal attachments');
  237. }
  238. p.attachments = Number(buf);
  239. }
  240. // look up namespace (if any)
  241. if ('/' == str.charAt(i + 1)) {
  242. p.nsp = '';
  243. while (++i) {
  244. var c = str.charAt(i);
  245. if (',' == c) break;
  246. p.nsp += c;
  247. if (i == str.length) break;
  248. }
  249. } else {
  250. p.nsp = '/';
  251. }
  252. // look up id
  253. var next = str.charAt(i + 1);
  254. if ('' !== next && Number(next) == next) {
  255. p.id = '';
  256. while (++i) {
  257. var c = str.charAt(i);
  258. if (null == c || Number(c) != c) {
  259. --i;
  260. break;
  261. }
  262. p.id += str.charAt(i);
  263. if (i == str.length) break;
  264. }
  265. p.id = Number(p.id);
  266. }
  267. // look up json data
  268. if (str.charAt(++i)) {
  269. p = tryParse(p, str.substr(i));
  270. }
  271. debug('decoded %s as %j', str, p);
  272. return p;
  273. }
  274. function tryParse(p, str) {
  275. try {
  276. p.data = json.parse(str);
  277. } catch(e){
  278. return error();
  279. }
  280. return p;
  281. };
  282. /**
  283. * Deallocates a parser's resources
  284. *
  285. * @api public
  286. */
  287. Decoder.prototype.destroy = function() {
  288. if (this.reconstructor) {
  289. this.reconstructor.finishedReconstruction();
  290. }
  291. };
  292. /**
  293. * A manager of a binary event's 'buffer sequence'. Should
  294. * be constructed whenever a packet of type BINARY_EVENT is
  295. * decoded.
  296. *
  297. * @param {Object} packet
  298. * @return {BinaryReconstructor} initialized reconstructor
  299. * @api private
  300. */
  301. function BinaryReconstructor(packet) {
  302. this.reconPack = packet;
  303. this.buffers = [];
  304. }
  305. /**
  306. * Method to be called when binary data received from connection
  307. * after a BINARY_EVENT packet.
  308. *
  309. * @param {Buffer | ArrayBuffer} binData - the raw binary data received
  310. * @return {null | Object} returns null if more binary data is expected or
  311. * a reconstructed packet object if all buffers have been received.
  312. * @api private
  313. */
  314. BinaryReconstructor.prototype.takeBinaryData = function(binData) {
  315. this.buffers.push(binData);
  316. if (this.buffers.length == this.reconPack.attachments) { // done with buffer list
  317. var packet = binary.reconstructPacket(this.reconPack, this.buffers);
  318. this.finishedReconstruction();
  319. return packet;
  320. }
  321. return null;
  322. };
  323. /**
  324. * Cleans up binary packet reconstruction variables.
  325. *
  326. * @api private
  327. */
  328. BinaryReconstructor.prototype.finishedReconstruction = function() {
  329. this.reconPack = null;
  330. this.buffers = [];
  331. };
  332. function error(data){
  333. return {
  334. type: exports.ERROR,
  335. data: 'parser error'
  336. };
  337. }