index.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 buf = '';
  235. while (str.charAt(++i) !== '-') {
  236. buf += str.charAt(i);
  237. if (i == str.length) break;
  238. }
  239. if (buf != Number(buf) || str.charAt(i) !== '-') {
  240. throw new Error('Illegal attachments');
  241. }
  242. p.attachments = Number(buf);
  243. }
  244. // look up namespace (if any)
  245. if ('/' === str.charAt(i + 1)) {
  246. p.nsp = '';
  247. while (++i) {
  248. var c = str.charAt(i);
  249. if (',' === c) break;
  250. p.nsp += c;
  251. if (i === str.length) break;
  252. }
  253. } else {
  254. p.nsp = '/';
  255. }
  256. // look up id
  257. var next = str.charAt(i + 1);
  258. if ('' !== next && Number(next) == next) {
  259. p.id = '';
  260. while (++i) {
  261. var c = str.charAt(i);
  262. if (null == c || Number(c) != c) {
  263. --i;
  264. break;
  265. }
  266. p.id += str.charAt(i);
  267. if (i === str.length) break;
  268. }
  269. p.id = Number(p.id);
  270. }
  271. // look up json data
  272. if (str.charAt(++i)) {
  273. var payload = tryParse(str.substr(i));
  274. var isPayloadValid = payload !== false && (p.type === exports.ERROR || isArray(payload));
  275. if (isPayloadValid) {
  276. p.data = payload;
  277. } else {
  278. return error('invalid payload');
  279. }
  280. }
  281. debug('decoded %s as %j', str, p);
  282. return p;
  283. }
  284. function tryParse(str) {
  285. try {
  286. return JSON.parse(str);
  287. } catch(e){
  288. return false;
  289. }
  290. }
  291. /**
  292. * Deallocates a parser's resources
  293. *
  294. * @api public
  295. */
  296. Decoder.prototype.destroy = function() {
  297. if (this.reconstructor) {
  298. this.reconstructor.finishedReconstruction();
  299. }
  300. };
  301. /**
  302. * A manager of a binary event's 'buffer sequence'. Should
  303. * be constructed whenever a packet of type BINARY_EVENT is
  304. * decoded.
  305. *
  306. * @param {Object} packet
  307. * @return {BinaryReconstructor} initialized reconstructor
  308. * @api private
  309. */
  310. function BinaryReconstructor(packet) {
  311. this.reconPack = packet;
  312. this.buffers = [];
  313. }
  314. /**
  315. * Method to be called when binary data received from connection
  316. * after a BINARY_EVENT packet.
  317. *
  318. * @param {Buffer | ArrayBuffer} binData - the raw binary data received
  319. * @return {null | Object} returns null if more binary data is expected or
  320. * a reconstructed packet object if all buffers have been received.
  321. * @api private
  322. */
  323. BinaryReconstructor.prototype.takeBinaryData = function(binData) {
  324. this.buffers.push(binData);
  325. if (this.buffers.length === this.reconPack.attachments) { // done with buffer list
  326. var packet = binary.reconstructPacket(this.reconPack, this.buffers);
  327. this.finishedReconstruction();
  328. return packet;
  329. }
  330. return null;
  331. };
  332. /**
  333. * Cleans up binary packet reconstruction variables.
  334. *
  335. * @api private
  336. */
  337. BinaryReconstructor.prototype.finishedReconstruction = function() {
  338. this.reconPack = null;
  339. this.buffers = [];
  340. };
  341. function error(msg) {
  342. return {
  343. type: exports.ERROR,
  344. data: 'parser error: ' + msg
  345. };
  346. }