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.

396 lines
7.3 KiB

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