No Description

utils.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. 'use strict';
  2. var support = require('./support');
  3. var base64 = require('./base64');
  4. var nodejsUtils = require('./nodejsUtils');
  5. var setImmediate = require('set-immediate-shim');
  6. var external = require("./external");
  7. /**
  8. * Convert a string that pass as a "binary string": it should represent a byte
  9. * array but may have > 255 char codes. Be sure to take only the first byte
  10. * and returns the byte array.
  11. * @param {String} str the string to transform.
  12. * @return {Array|Uint8Array} the string in a binary format.
  13. */
  14. function string2binary(str) {
  15. var result = null;
  16. if (support.uint8array) {
  17. result = new Uint8Array(str.length);
  18. } else {
  19. result = new Array(str.length);
  20. }
  21. return stringToArrayLike(str, result);
  22. }
  23. /**
  24. * Create a new blob with the given content and the given type.
  25. * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use
  26. * an Uint8Array because the stock browser of android 4 won't accept it (it
  27. * will be silently converted to a string, "[object Uint8Array]").
  28. *
  29. * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge:
  30. * when a large amount of Array is used to create the Blob, the amount of
  31. * memory consumed is nearly 100 times the original data amount.
  32. *
  33. * @param {String} type the mime type of the blob.
  34. * @return {Blob} the created blob.
  35. */
  36. exports.newBlob = function(part, type) {
  37. exports.checkSupport("blob");
  38. try {
  39. // Blob constructor
  40. return new Blob([part], {
  41. type: type
  42. });
  43. }
  44. catch (e) {
  45. try {
  46. // deprecated, browser only, old way
  47. var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder;
  48. var builder = new Builder();
  49. builder.append(part);
  50. return builder.getBlob(type);
  51. }
  52. catch (e) {
  53. // well, fuck ?!
  54. throw new Error("Bug : can't construct the Blob.");
  55. }
  56. }
  57. };
  58. /**
  59. * The identity function.
  60. * @param {Object} input the input.
  61. * @return {Object} the same input.
  62. */
  63. function identity(input) {
  64. return input;
  65. }
  66. /**
  67. * Fill in an array with a string.
  68. * @param {String} str the string to use.
  69. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
  70. * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
  71. */
  72. function stringToArrayLike(str, array) {
  73. for (var i = 0; i < str.length; ++i) {
  74. array[i] = str.charCodeAt(i) & 0xFF;
  75. }
  76. return array;
  77. }
  78. /**
  79. * An helper for the function arrayLikeToString.
  80. * This contains static informations and functions that
  81. * can be optimized by the browser JIT compiler.
  82. */
  83. var arrayToStringHelper = {
  84. /**
  85. * Transform an array of int into a string, chunk by chunk.
  86. * See the performances notes on arrayLikeToString.
  87. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
  88. * @param {String} type the type of the array.
  89. * @param {Integer} chunk the chunk size.
  90. * @return {String} the resulting string.
  91. * @throws Error if the chunk is too big for the stack.
  92. */
  93. stringifyByChunk: function(array, type, chunk) {
  94. var result = [], k = 0, len = array.length;
  95. // shortcut
  96. if (len <= chunk) {
  97. return String.fromCharCode.apply(null, array);
  98. }
  99. while (k < len) {
  100. if (type === "array" || type === "nodebuffer") {
  101. result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
  102. }
  103. else {
  104. result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
  105. }
  106. k += chunk;
  107. }
  108. return result.join("");
  109. },
  110. /**
  111. * Call String.fromCharCode on every item in the array.
  112. * This is the naive implementation, which generate A LOT of intermediate string.
  113. * This should be used when everything else fail.
  114. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
  115. * @return {String} the result.
  116. */
  117. stringifyByChar: function(array){
  118. var resultStr = "";
  119. for(var i = 0; i < array.length; i++) {
  120. resultStr += String.fromCharCode(array[i]);
  121. }
  122. return resultStr;
  123. },
  124. applyCanBeUsed : {
  125. /**
  126. * true if the browser accepts to use String.fromCharCode on Uint8Array
  127. */
  128. uint8array : (function () {
  129. try {
  130. return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1;
  131. } catch (e) {
  132. return false;
  133. }
  134. })(),
  135. /**
  136. * true if the browser accepts to use String.fromCharCode on nodejs Buffer.
  137. */
  138. nodebuffer : (function () {
  139. try {
  140. return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.allocBuffer(1)).length === 1;
  141. } catch (e) {
  142. return false;
  143. }
  144. })()
  145. }
  146. };
  147. /**
  148. * Transform an array-like object to a string.
  149. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
  150. * @return {String} the result.
  151. */
  152. function arrayLikeToString(array) {
  153. // Performances notes :
  154. // --------------------
  155. // String.fromCharCode.apply(null, array) is the fastest, see
  156. // see http://jsperf.com/converting-a-uint8array-to-a-string/2
  157. // but the stack is limited (and we can get huge arrays !).
  158. //
  159. // result += String.fromCharCode(array[i]); generate too many strings !
  160. //
  161. // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
  162. // TODO : we now have workers that split the work. Do we still need that ?
  163. var chunk = 65536,
  164. type = exports.getTypeOf(array),
  165. canUseApply = true;
  166. if (type === "uint8array") {
  167. canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array;
  168. } else if (type === "nodebuffer") {
  169. canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer;
  170. }
  171. if (canUseApply) {
  172. while (chunk > 1) {
  173. try {
  174. return arrayToStringHelper.stringifyByChunk(array, type, chunk);
  175. } catch (e) {
  176. chunk = Math.floor(chunk / 2);
  177. }
  178. }
  179. }
  180. // no apply or chunk error : slow and painful algorithm
  181. // default browser on android 4.*
  182. return arrayToStringHelper.stringifyByChar(array);
  183. }
  184. exports.applyFromCharCode = arrayLikeToString;
  185. /**
  186. * Copy the data from an array-like to an other array-like.
  187. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
  188. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
  189. * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
  190. */
  191. function arrayLikeToArrayLike(arrayFrom, arrayTo) {
  192. for (var i = 0; i < arrayFrom.length; i++) {
  193. arrayTo[i] = arrayFrom[i];
  194. }
  195. return arrayTo;
  196. }
  197. // a matrix containing functions to transform everything into everything.
  198. var transform = {};
  199. // string to ?
  200. transform["string"] = {
  201. "string": identity,
  202. "array": function(input) {
  203. return stringToArrayLike(input, new Array(input.length));
  204. },
  205. "arraybuffer": function(input) {
  206. return transform["string"]["uint8array"](input).buffer;
  207. },
  208. "uint8array": function(input) {
  209. return stringToArrayLike(input, new Uint8Array(input.length));
  210. },
  211. "nodebuffer": function(input) {
  212. return stringToArrayLike(input, nodejsUtils.allocBuffer(input.length));
  213. }
  214. };
  215. // array to ?
  216. transform["array"] = {
  217. "string": arrayLikeToString,
  218. "array": identity,
  219. "arraybuffer": function(input) {
  220. return (new Uint8Array(input)).buffer;
  221. },
  222. "uint8array": function(input) {
  223. return new Uint8Array(input);
  224. },
  225. "nodebuffer": function(input) {
  226. return nodejsUtils.newBufferFrom(input);
  227. }
  228. };
  229. // arraybuffer to ?
  230. transform["arraybuffer"] = {
  231. "string": function(input) {
  232. return arrayLikeToString(new Uint8Array(input));
  233. },
  234. "array": function(input) {
  235. return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
  236. },
  237. "arraybuffer": identity,
  238. "uint8array": function(input) {
  239. return new Uint8Array(input);
  240. },
  241. "nodebuffer": function(input) {
  242. return nodejsUtils.newBufferFrom(new Uint8Array(input));
  243. }
  244. };
  245. // uint8array to ?
  246. transform["uint8array"] = {
  247. "string": arrayLikeToString,
  248. "array": function(input) {
  249. return arrayLikeToArrayLike(input, new Array(input.length));
  250. },
  251. "arraybuffer": function(input) {
  252. return input.buffer;
  253. },
  254. "uint8array": identity,
  255. "nodebuffer": function(input) {
  256. return nodejsUtils.newBufferFrom(input);
  257. }
  258. };
  259. // nodebuffer to ?
  260. transform["nodebuffer"] = {
  261. "string": arrayLikeToString,
  262. "array": function(input) {
  263. return arrayLikeToArrayLike(input, new Array(input.length));
  264. },
  265. "arraybuffer": function(input) {
  266. return transform["nodebuffer"]["uint8array"](input).buffer;
  267. },
  268. "uint8array": function(input) {
  269. return arrayLikeToArrayLike(input, new Uint8Array(input.length));
  270. },
  271. "nodebuffer": identity
  272. };
  273. /**
  274. * Transform an input into any type.
  275. * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
  276. * If no output type is specified, the unmodified input will be returned.
  277. * @param {String} outputType the output type.
  278. * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
  279. * @throws {Error} an Error if the browser doesn't support the requested output type.
  280. */
  281. exports.transformTo = function(outputType, input) {
  282. if (!input) {
  283. // undefined, null, etc
  284. // an empty string won't harm.
  285. input = "";
  286. }
  287. if (!outputType) {
  288. return input;
  289. }
  290. exports.checkSupport(outputType);
  291. var inputType = exports.getTypeOf(input);
  292. var result = transform[inputType][outputType](input);
  293. return result;
  294. };
  295. /**
  296. * Return the type of the input.
  297. * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
  298. * @param {Object} input the input to identify.
  299. * @return {String} the (lowercase) type of the input.
  300. */
  301. exports.getTypeOf = function(input) {
  302. if (typeof input === "string") {
  303. return "string";
  304. }
  305. if (Object.prototype.toString.call(input) === "[object Array]") {
  306. return "array";
  307. }
  308. if (support.nodebuffer && nodejsUtils.isBuffer(input)) {
  309. return "nodebuffer";
  310. }
  311. if (support.uint8array && input instanceof Uint8Array) {
  312. return "uint8array";
  313. }
  314. if (support.arraybuffer && input instanceof ArrayBuffer) {
  315. return "arraybuffer";
  316. }
  317. };
  318. /**
  319. * Throw an exception if the type is not supported.
  320. * @param {String} type the type to check.
  321. * @throws {Error} an Error if the browser doesn't support the requested type.
  322. */
  323. exports.checkSupport = function(type) {
  324. var supported = support[type.toLowerCase()];
  325. if (!supported) {
  326. throw new Error(type + " is not supported by this platform");
  327. }
  328. };
  329. exports.MAX_VALUE_16BITS = 65535;
  330. exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1
  331. /**
  332. * Prettify a string read as binary.
  333. * @param {string} str the string to prettify.
  334. * @return {string} a pretty string.
  335. */
  336. exports.pretty = function(str) {
  337. var res = '',
  338. code, i;
  339. for (i = 0; i < (str || "").length; i++) {
  340. code = str.charCodeAt(i);
  341. res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
  342. }
  343. return res;
  344. };
  345. /**
  346. * Defer the call of a function.
  347. * @param {Function} callback the function to call asynchronously.
  348. * @param {Array} args the arguments to give to the callback.
  349. */
  350. exports.delay = function(callback, args, self) {
  351. setImmediate(function () {
  352. callback.apply(self || null, args || []);
  353. });
  354. };
  355. /**
  356. * Extends a prototype with an other, without calling a constructor with
  357. * side effects. Inspired by nodejs' `utils.inherits`
  358. * @param {Function} ctor the constructor to augment
  359. * @param {Function} superCtor the parent constructor to use
  360. */
  361. exports.inherits = function (ctor, superCtor) {
  362. var Obj = function() {};
  363. Obj.prototype = superCtor.prototype;
  364. ctor.prototype = new Obj();
  365. };
  366. /**
  367. * Merge the objects passed as parameters into a new one.
  368. * @private
  369. * @param {...Object} var_args All objects to merge.
  370. * @return {Object} a new object with the data of the others.
  371. */
  372. exports.extend = function() {
  373. var result = {}, i, attr;
  374. for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
  375. for (attr in arguments[i]) {
  376. if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
  377. result[attr] = arguments[i][attr];
  378. }
  379. }
  380. }
  381. return result;
  382. };
  383. /**
  384. * Transform arbitrary content into a Promise.
  385. * @param {String} name a name for the content being processed.
  386. * @param {Object} inputData the content to process.
  387. * @param {Boolean} isBinary true if the content is not an unicode string
  388. * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character.
  389. * @param {Boolean} isBase64 true if the string content is encoded with base64.
  390. * @return {Promise} a promise in a format usable by JSZip.
  391. */
  392. exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) {
  393. // if inputData is already a promise, this flatten it.
  394. var promise = external.Promise.resolve(inputData).then(function(data) {
  395. var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1);
  396. if (isBlob && typeof FileReader !== "undefined") {
  397. return new external.Promise(function (resolve, reject) {
  398. var reader = new FileReader();
  399. reader.onload = function(e) {
  400. resolve(e.target.result);
  401. };
  402. reader.onerror = function(e) {
  403. reject(e.target.error);
  404. };
  405. reader.readAsArrayBuffer(data);
  406. });
  407. } else {
  408. return data;
  409. }
  410. });
  411. return promise.then(function(data) {
  412. var dataType = exports.getTypeOf(data);
  413. if (!dataType) {
  414. return external.Promise.reject(
  415. new Error("Can't read the data of '" + name + "'. Is it " +
  416. "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?")
  417. );
  418. }
  419. // special case : it's way easier to work with Uint8Array than with ArrayBuffer
  420. if (dataType === "arraybuffer") {
  421. data = exports.transformTo("uint8array", data);
  422. } else if (dataType === "string") {
  423. if (isBase64) {
  424. data = base64.decode(data);
  425. }
  426. else if (isBinary) {
  427. // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask
  428. if (isOptimizedBinaryString !== true) {
  429. // this is a string, not in a base64 format.
  430. // Be sure that this is a correct "binary string"
  431. data = string2binary(data);
  432. }
  433. }
  434. }
  435. return data;
  436. });
  437. };