PageRenderTime 25ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/engine.io-parser/lib/index.js

https://gitlab.com/remynaps/Vexillarius
JavaScript | 460 lines | 262 code | 74 blank | 124 comment | 73 complexity | dae9abb301ba1f749077e16cc21bb17d MD5 | raw file
  1. /**
  2. * Module dependencies.
  3. */
  4. var utf8 = require('utf8');
  5. var after = require('after');
  6. var keys = require('./keys');
  7. /**
  8. * Current protocol version.
  9. */
  10. exports.protocol = 3;
  11. /**
  12. * Packet types.
  13. */
  14. var packets = exports.packets = {
  15. open: 0 // non-ws
  16. , close: 1 // non-ws
  17. , ping: 2
  18. , pong: 3
  19. , message: 4
  20. , upgrade: 5
  21. , noop: 6
  22. };
  23. var packetslist = keys(packets);
  24. /**
  25. * Premade error packet.
  26. */
  27. var err = { type: 'error', data: 'parser error' };
  28. /**
  29. * Encodes a packet.
  30. *
  31. * <packet type id> [ <data> ]
  32. *
  33. * Example:
  34. *
  35. * 5hello world
  36. * 3
  37. * 4
  38. *
  39. * Binary is encoded in an identical principle
  40. *
  41. * @api private
  42. */
  43. exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
  44. if ('function' == typeof supportsBinary) {
  45. callback = supportsBinary;
  46. supportsBinary = null;
  47. }
  48. if ('function' == typeof utf8encode ) {
  49. callback = utf8encode;
  50. utf8encode = null;
  51. }
  52. if (Buffer.isBuffer(packet.data)) {
  53. return encodeBuffer(packet, supportsBinary, callback);
  54. } else if (packet.data && (packet.data.buffer || packet.data) instanceof ArrayBuffer) {
  55. packet.data = arrayBufferToBuffer(packet.data);
  56. return encodeBuffer(packet, supportsBinary, callback);
  57. }
  58. // Sending data as a utf-8 string
  59. var encoded = packets[packet.type];
  60. // data fragment is optional
  61. if (undefined !== packet.data) {
  62. encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
  63. }
  64. return callback('' + encoded);
  65. };
  66. /**
  67. * Encode Buffer data
  68. */
  69. function encodeBuffer(packet, supportsBinary, callback) {
  70. var data = packet.data;
  71. if (!supportsBinary) {
  72. return exports.encodeBase64Packet(packet, callback);
  73. }
  74. var typeBuffer = new Buffer(1);
  75. typeBuffer[0] = packets[packet.type];
  76. return callback(Buffer.concat([typeBuffer, data]));
  77. }
  78. /**
  79. * Encodes a packet with binary data in a base64 string
  80. *
  81. * @param {Object} packet, has `type` and `data`
  82. * @return {String} base64 encoded message
  83. */
  84. exports.encodeBase64Packet = function(packet, callback){
  85. if (!Buffer.isBuffer(packet.data)) {
  86. packet.data = arrayBufferToBuffer(packet.data);
  87. }
  88. var message = 'b' + packets[packet.type];
  89. message += packet.data.toString('base64');
  90. return callback(message);
  91. };
  92. /**
  93. * Decodes a packet. Data also available as an ArrayBuffer if requested.
  94. *
  95. * @return {Object} with `type` and `data` (if any)
  96. * @api private
  97. */
  98. exports.decodePacket = function (data, binaryType, utf8decode) {
  99. // String data
  100. if (typeof data == 'string' || data === undefined) {
  101. if (data.charAt(0) == 'b') {
  102. return exports.decodeBase64Packet(data.substr(1), binaryType);
  103. }
  104. var type = data.charAt(0);
  105. if (utf8decode) {
  106. try {
  107. data = utf8.decode(data);
  108. } catch (e) {
  109. return err;
  110. }
  111. }
  112. if (Number(type) != type || !packetslist[type]) {
  113. return err;
  114. }
  115. if (data.length > 1) {
  116. return { type: packetslist[type], data: data.substring(1) };
  117. } else {
  118. return { type: packetslist[type] };
  119. }
  120. }
  121. // Binary data
  122. if (binaryType === 'arraybuffer') {
  123. var type = data[0];
  124. var intArray = new Uint8Array(data.length - 1);
  125. for (var i = 1; i < data.length; i++) {
  126. intArray[i - 1] = data[i];
  127. }
  128. return { type: packetslist[type], data: intArray.buffer };
  129. }
  130. var type = data[0];
  131. return { type: packetslist[type], data: data.slice(1) };
  132. };
  133. /**
  134. * Decodes a packet encoded in a base64 string.
  135. *
  136. * @param {String} base64 encoded message
  137. * @return {Object} with `type` and `data` (if any)
  138. */
  139. exports.decodeBase64Packet = function(msg, binaryType) {
  140. var type = packetslist[msg.charAt(0)];
  141. var data = new Buffer(msg.substr(1), 'base64');
  142. if (binaryType === 'arraybuffer') {
  143. var abv = new Uint8Array(data.length);
  144. for (var i = 0; i < abv.length; i++){
  145. abv[i] = data[i];
  146. }
  147. data = abv.buffer;
  148. }
  149. return { type: type, data: data };
  150. };
  151. /**
  152. * Encodes multiple messages (payload).
  153. *
  154. * <length>:data
  155. *
  156. * Example:
  157. *
  158. * 11:hello world2:hi
  159. *
  160. * If any contents are binary, they will be encoded as base64 strings. Base64
  161. * encoded strings are marked with a b before the length specifier
  162. *
  163. * @param {Array} packets
  164. * @api private
  165. */
  166. exports.encodePayload = function (packets, supportsBinary, callback) {
  167. if (typeof supportsBinary == 'function') {
  168. callback = supportsBinary;
  169. supportsBinary = null;
  170. }
  171. if (supportsBinary) {
  172. return exports.encodePayloadAsBinary(packets, callback);
  173. }
  174. if (!packets.length) {
  175. return callback('0:');
  176. }
  177. function setLengthHeader(message) {
  178. return message.length + ':' + message;
  179. }
  180. function encodeOne(packet, doneCallback) {
  181. exports.encodePacket(packet, supportsBinary, true, function(message) {
  182. doneCallback(null, setLengthHeader(message));
  183. });
  184. }
  185. map(packets, encodeOne, function(err, results) {
  186. return callback(results.join(''));
  187. });
  188. };
  189. /**
  190. * Async array map using after
  191. */
  192. function map(ary, each, done) {
  193. var result = new Array(ary.length);
  194. var next = after(ary.length, done);
  195. var eachWithIndex = function(i, el, cb) {
  196. each(el, function(error, msg) {
  197. result[i] = msg;
  198. cb(error, result);
  199. });
  200. };
  201. for (var i = 0; i < ary.length; i++) {
  202. eachWithIndex(i, ary[i], next);
  203. }
  204. }
  205. /*
  206. * Decodes data when a payload is maybe expected. Possible binary contents are
  207. * decoded from their base64 representation
  208. *
  209. * @param {String} data, callback method
  210. * @api public
  211. */
  212. exports.decodePayload = function (data, binaryType, callback) {
  213. if ('string' != typeof data) {
  214. return exports.decodePayloadAsBinary(data, binaryType, callback);
  215. }
  216. if (typeof binaryType === 'function') {
  217. callback = binaryType;
  218. binaryType = null;
  219. }
  220. var packet;
  221. if (data == '') {
  222. // parser error - ignoring payload
  223. return callback(err, 0, 1);
  224. }
  225. var length = ''
  226. , n, msg;
  227. for (var i = 0, l = data.length; i < l; i++) {
  228. var chr = data.charAt(i);
  229. if (':' != chr) {
  230. length += chr;
  231. } else {
  232. if ('' == length || (length != (n = Number(length)))) {
  233. // parser error - ignoring payload
  234. return callback(err, 0, 1);
  235. }
  236. msg = data.substr(i + 1, n);
  237. if (length != msg.length) {
  238. // parser error - ignoring payload
  239. return callback(err, 0, 1);
  240. }
  241. if (msg.length) {
  242. packet = exports.decodePacket(msg, binaryType, true);
  243. if (err.type == packet.type && err.data == packet.data) {
  244. // parser error in individual packet - ignoring payload
  245. return callback(err, 0, 1);
  246. }
  247. var ret = callback(packet, i + n, l);
  248. if (false === ret) return;
  249. }
  250. // advance cursor
  251. i += n;
  252. length = '';
  253. }
  254. }
  255. if (length != '') {
  256. // parser error - ignoring payload
  257. return callback(err, 0, 1);
  258. }
  259. };
  260. /**
  261. *
  262. * Converts a buffer to a utf8.js encoded string
  263. *
  264. * @api private
  265. */
  266. function bufferToString(buffer) {
  267. var str = '';
  268. for (var i = 0; i < buffer.length; i++) {
  269. str += String.fromCharCode(buffer[i]);
  270. }
  271. return str;
  272. }
  273. /**
  274. *
  275. * Converts a utf8.js encoded string to a buffer
  276. *
  277. * @api private
  278. */
  279. function stringToBuffer(string) {
  280. var buf = new Buffer(string.length);
  281. for (var i = 0; i < string.length; i++) {
  282. buf.writeUInt8(string.charCodeAt(i), i);
  283. }
  284. return buf;
  285. }
  286. /**
  287. *
  288. * Converts an ArrayBuffer to a Buffer
  289. *
  290. * @api private
  291. */
  292. function arrayBufferToBuffer(data) {
  293. // data is either an ArrayBuffer or ArrayBufferView.
  294. var array = new Uint8Array(data.buffer || data);
  295. var length = data.byteLength || data.length;
  296. var offset = data.byteOffset || 0;
  297. var buffer = new Buffer(length);
  298. for (var i = 0; i < length; i++) {
  299. buffer[i] = array[offset + i];
  300. }
  301. return buffer;
  302. }
  303. /**
  304. * Encodes multiple messages (payload) as binary.
  305. *
  306. * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
  307. * 255><data>
  308. *
  309. * Example:
  310. * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
  311. *
  312. * @param {Array} packets
  313. * @return {Buffer} encoded payload
  314. * @api private
  315. */
  316. exports.encodePayloadAsBinary = function (packets, callback) {
  317. if (!packets.length) {
  318. return callback(new Buffer(0));
  319. }
  320. function encodeOne(p, doneCallback) {
  321. exports.encodePacket(p, true, true, function(packet) {
  322. if (typeof packet === 'string') {
  323. var encodingLength = '' + packet.length;
  324. var sizeBuffer = new Buffer(encodingLength.length + 2);
  325. sizeBuffer[0] = 0; // is a string (not true binary = 0)
  326. for (var i = 0; i < encodingLength.length; i++) {
  327. sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
  328. }
  329. sizeBuffer[sizeBuffer.length - 1] = 255;
  330. return doneCallback(null, Buffer.concat([sizeBuffer, stringToBuffer(packet)]));
  331. }
  332. var encodingLength = '' + packet.length;
  333. var sizeBuffer = new Buffer(encodingLength.length + 2);
  334. sizeBuffer[0] = 1; // is binary (true binary = 1)
  335. for (var i = 0; i < encodingLength.length; i++) {
  336. sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
  337. }
  338. sizeBuffer[sizeBuffer.length - 1] = 255;
  339. doneCallback(null, Buffer.concat([sizeBuffer, packet]));
  340. });
  341. }
  342. map(packets, encodeOne, function(err, results) {
  343. return callback(Buffer.concat(results));
  344. });
  345. };
  346. /*
  347. * Decodes data when a payload is maybe expected. Strings are decoded by
  348. * interpreting each byte as a key code for entries marked to start with 0. See
  349. * description of encodePayloadAsBinary
  350. * @param {Buffer} data, callback method
  351. * @api public
  352. */
  353. exports.decodePayloadAsBinary = function (data, binaryType, callback) {
  354. if (typeof binaryType === 'function') {
  355. callback = binaryType;
  356. binaryType = null;
  357. }
  358. var bufferTail = data;
  359. var buffers = [];
  360. while (bufferTail.length > 0) {
  361. var strLen = '';
  362. var isString = bufferTail[0] === 0;
  363. var numberTooLong = false;
  364. for (var i = 1; ; i++) {
  365. if (bufferTail[i] == 255) break;
  366. // 310 = char length of Number.MAX_VALUE
  367. if (strLen.length > 310) {
  368. numberTooLong = true;
  369. break;
  370. }
  371. strLen += '' + bufferTail[i];
  372. }
  373. if(numberTooLong) return callback(err, 0, 1);
  374. bufferTail = bufferTail.slice(strLen.length + 1);
  375. var msgLength = parseInt(strLen, 10);
  376. var msg = bufferTail.slice(1, msgLength + 1);
  377. if (isString) msg = bufferToString(msg);
  378. buffers.push(msg);
  379. bufferTail = bufferTail.slice(msgLength + 1);
  380. }
  381. var total = buffers.length;
  382. buffers.forEach(function(buffer, i) {
  383. callback(exports.decodePacket(buffer, binaryType, true), i, total);
  384. });
  385. };