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.

417 lines
7.9 KiB

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