index.js 11 KB

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