index.js 11 KB

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