index.js 7.5 KB

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