object.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. 'use strict';
  2. var support = require('./support');
  3. var utils = require('./utils');
  4. var crc32 = require('./crc32');
  5. var signature = require('./signature');
  6. var defaults = require('./defaults');
  7. var base64 = require('./base64');
  8. var compressions = require('./compressions');
  9. var CompressedObject = require('./compressedObject');
  10. var nodeBuffer = require('./nodeBuffer');
  11. var utf8 = require('./utf8');
  12. var StringWriter = require('./stringWriter');
  13. var Uint8ArrayWriter = require('./uint8ArrayWriter');
  14. /**
  15. * Returns the raw data of a ZipObject, decompress the content if necessary.
  16. * @param {ZipObject} file the file to use.
  17. * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
  18. */
  19. var getRawData = function(file) {
  20. if (file._data instanceof CompressedObject) {
  21. file._data = file._data.getContent();
  22. file.options.binary = true;
  23. file.options.base64 = false;
  24. if (utils.getTypeOf(file._data) === "uint8array") {
  25. var copy = file._data;
  26. // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
  27. // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
  28. file._data = new Uint8Array(copy.length);
  29. // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
  30. if (copy.length !== 0) {
  31. file._data.set(copy, 0);
  32. }
  33. }
  34. }
  35. return file._data;
  36. };
  37. /**
  38. * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
  39. * @param {ZipObject} file the file to use.
  40. * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
  41. */
  42. var getBinaryData = function(file) {
  43. var result = getRawData(file),
  44. type = utils.getTypeOf(result);
  45. if (type === "string") {
  46. if (!file.options.binary) {
  47. // unicode text !
  48. // unicode string => binary string is a painful process, check if we can avoid it.
  49. if (support.nodebuffer) {
  50. return nodeBuffer(result, "utf-8");
  51. }
  52. }
  53. return file.asBinary();
  54. }
  55. return result;
  56. };
  57. /**
  58. * Transform this._data into a string.
  59. * @param {function} filter a function String -> String, applied if not null on the result.
  60. * @return {String} the string representing this._data.
  61. */
  62. var dataToString = function(asUTF8) {
  63. var result = getRawData(this);
  64. if (result === null || typeof result === "undefined") {
  65. return "";
  66. }
  67. // if the data is a base64 string, we decode it before checking the encoding !
  68. if (this.options.base64) {
  69. result = base64.decode(result);
  70. }
  71. if (asUTF8 && this.options.binary) {
  72. // JSZip.prototype.utf8decode supports arrays as input
  73. // skip to array => string step, utf8decode will do it.
  74. result = out.utf8decode(result);
  75. }
  76. else {
  77. // no utf8 transformation, do the array => string step.
  78. result = utils.transformTo("string", result);
  79. }
  80. if (!asUTF8 && !this.options.binary) {
  81. result = utils.transformTo("string", out.utf8encode(result));
  82. }
  83. return result;
  84. };
  85. /**
  86. * A simple object representing a file in the zip file.
  87. * @constructor
  88. * @param {string} name the name of the file
  89. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
  90. * @param {Object} options the options of the file
  91. */
  92. var ZipObject = function(name, data, options) {
  93. this.name = name;
  94. this.dir = options.dir;
  95. this.date = options.date;
  96. this.comment = options.comment;
  97. this._data = data;
  98. this.options = options;
  99. /*
  100. * This object contains initial values for dir and date.
  101. * With them, we can check if the user changed the deprecated metadata in
  102. * `ZipObject#options` or not.
  103. */
  104. this._initialMetadata = {
  105. dir : options.dir,
  106. date : options.date
  107. };
  108. };
  109. ZipObject.prototype = {
  110. /**
  111. * Return the content as UTF8 string.
  112. * @return {string} the UTF8 string.
  113. */
  114. asText: function() {
  115. return dataToString.call(this, true);
  116. },
  117. /**
  118. * Returns the binary content.
  119. * @return {string} the content as binary.
  120. */
  121. asBinary: function() {
  122. return dataToString.call(this, false);
  123. },
  124. /**
  125. * Returns the content as a nodejs Buffer.
  126. * @return {Buffer} the content as a Buffer.
  127. */
  128. asNodeBuffer: function() {
  129. var result = getBinaryData(this);
  130. return utils.transformTo("nodebuffer", result);
  131. },
  132. /**
  133. * Returns the content as an Uint8Array.
  134. * @return {Uint8Array} the content as an Uint8Array.
  135. */
  136. asUint8Array: function() {
  137. var result = getBinaryData(this);
  138. return utils.transformTo("uint8array", result);
  139. },
  140. /**
  141. * Returns the content as an ArrayBuffer.
  142. * @return {ArrayBuffer} the content as an ArrayBufer.
  143. */
  144. asArrayBuffer: function() {
  145. return this.asUint8Array().buffer;
  146. }
  147. };
  148. /**
  149. * Transform an integer into a string in hexadecimal.
  150. * @private
  151. * @param {number} dec the number to convert.
  152. * @param {number} bytes the number of bytes to generate.
  153. * @returns {string} the result.
  154. */
  155. var decToHex = function(dec, bytes) {
  156. var hex = "",
  157. i;
  158. for (i = 0; i < bytes; i++) {
  159. hex += String.fromCharCode(dec & 0xff);
  160. dec = dec >>> 8;
  161. }
  162. return hex;
  163. };
  164. /**
  165. * Merge the objects passed as parameters into a new one.
  166. * @private
  167. * @param {...Object} var_args All objects to merge.
  168. * @return {Object} a new object with the data of the others.
  169. */
  170. var extend = function() {
  171. var result = {}, i, attr;
  172. for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
  173. for (attr in arguments[i]) {
  174. if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
  175. result[attr] = arguments[i][attr];
  176. }
  177. }
  178. }
  179. return result;
  180. };
  181. /**
  182. * Transforms the (incomplete) options from the user into the complete
  183. * set of options to create a file.
  184. * @private
  185. * @param {Object} o the options from the user.
  186. * @return {Object} the complete set of options.
  187. */
  188. var prepareFileAttrs = function(o) {
  189. o = o || {};
  190. if (o.base64 === true && (o.binary === null || o.binary === undefined)) {
  191. o.binary = true;
  192. }
  193. o = extend(o, defaults);
  194. o.date = o.date || new Date();
  195. if (o.compression !== null) o.compression = o.compression.toUpperCase();
  196. return o;
  197. };
  198. /**
  199. * Add a file in the current folder.
  200. * @private
  201. * @param {string} name the name of the file
  202. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
  203. * @param {Object} o the options of the file
  204. * @return {Object} the new file.
  205. */
  206. var fileAdd = function(name, data, o) {
  207. // be sure sub folders exist
  208. var dataType = utils.getTypeOf(data),
  209. parent;
  210. o = prepareFileAttrs(o);
  211. if (o.createFolders && (parent = parentFolder(name))) {
  212. folderAdd.call(this, parent, true);
  213. }
  214. if (o.dir || data === null || typeof data === "undefined") {
  215. o.base64 = false;
  216. o.binary = false;
  217. data = null;
  218. }
  219. else if (dataType === "string") {
  220. if (o.binary && !o.base64) {
  221. // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
  222. if (o.optimizedBinaryString !== true) {
  223. // this is a string, not in a base64 format.
  224. // Be sure that this is a correct "binary string"
  225. data = utils.string2binary(data);
  226. }
  227. }
  228. }
  229. else { // arraybuffer, uint8array, ...
  230. o.base64 = false;
  231. o.binary = true;
  232. if (!dataType && !(data instanceof CompressedObject)) {
  233. throw new Error("The data of '" + name + "' is in an unsupported format !");
  234. }
  235. // special case : it's way easier to work with Uint8Array than with ArrayBuffer
  236. if (dataType === "arraybuffer") {
  237. data = utils.transformTo("uint8array", data);
  238. }
  239. }
  240. var object = new ZipObject(name, data, o);
  241. this.files[name] = object;
  242. return object;
  243. };
  244. /**
  245. * Find the parent folder of the path.
  246. * @private
  247. * @param {string} path the path to use
  248. * @return {string} the parent folder, or ""
  249. */
  250. var parentFolder = function (path) {
  251. if (path.slice(-1) == '/') {
  252. path = path.substring(0, path.length - 1);
  253. }
  254. var lastSlash = path.lastIndexOf('/');
  255. return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
  256. };
  257. /**
  258. * Add a (sub) folder in the current folder.
  259. * @private
  260. * @param {string} name the folder's name
  261. * @param {boolean=} [createFolders] If true, automatically create sub
  262. * folders. Defaults to false.
  263. * @return {Object} the new folder.
  264. */
  265. var folderAdd = function(name, createFolders) {
  266. // Check the name ends with a /
  267. if (name.slice(-1) != "/") {
  268. name += "/"; // IE doesn't like substr(-1)
  269. }
  270. createFolders = (typeof createFolders !== 'undefined') ? createFolders : false;
  271. // Does this folder already exist?
  272. if (!this.files[name]) {
  273. fileAdd.call(this, name, null, {
  274. dir: true,
  275. createFolders: createFolders
  276. });
  277. }
  278. return this.files[name];
  279. };
  280. /**
  281. * Generate a JSZip.CompressedObject for a given zipOject.
  282. * @param {ZipObject} file the object to read.
  283. * @param {JSZip.compression} compression the compression to use.
  284. * @return {JSZip.CompressedObject} the compressed result.
  285. */
  286. var generateCompressedObjectFrom = function(file, compression) {
  287. var result = new CompressedObject(),
  288. content;
  289. // the data has not been decompressed, we might reuse things !
  290. if (file._data instanceof CompressedObject) {
  291. result.uncompressedSize = file._data.uncompressedSize;
  292. result.crc32 = file._data.crc32;
  293. if (result.uncompressedSize === 0 || file.dir) {
  294. compression = compressions['STORE'];
  295. result.compressedContent = "";
  296. result.crc32 = 0;
  297. }
  298. else if (file._data.compressionMethod === compression.magic) {
  299. result.compressedContent = file._data.getCompressedContent();
  300. }
  301. else {
  302. content = file._data.getContent();
  303. // need to decompress / recompress
  304. result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content));
  305. }
  306. }
  307. else {
  308. // have uncompressed data
  309. content = getBinaryData(file);
  310. if (!content || content.length === 0 || file.dir) {
  311. compression = compressions['STORE'];
  312. content = "";
  313. }
  314. result.uncompressedSize = content.length;
  315. result.crc32 = crc32(content);
  316. result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content));
  317. }
  318. result.compressedSize = result.compressedContent.length;
  319. result.compressionMethod = compression.magic;
  320. return result;
  321. };
  322. /**
  323. * Generate the various parts used in the construction of the final zip file.
  324. * @param {string} name the file name.
  325. * @param {ZipObject} file the file content.
  326. * @param {JSZip.CompressedObject} compressedObject the compressed object.
  327. * @param {number} offset the current offset from the start of the zip file.
  328. * @return {object} the zip parts.
  329. */
  330. var generateZipParts = function(name, file, compressedObject, offset) {
  331. var data = compressedObject.compressedContent,
  332. utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
  333. comment = file.comment || "",
  334. utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
  335. useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
  336. useUTF8ForComment = utfEncodedComment.length !== comment.length,
  337. o = file.options,
  338. dosTime,
  339. dosDate,
  340. extraFields = "",
  341. unicodePathExtraField = "",
  342. unicodeCommentExtraField = "",
  343. dir, date;
  344. // handle the deprecated options.dir
  345. if (file._initialMetadata.dir !== file.dir) {
  346. dir = file.dir;
  347. } else {
  348. dir = o.dir;
  349. }
  350. // handle the deprecated options.date
  351. if(file._initialMetadata.date !== file.date) {
  352. date = file.date;
  353. } else {
  354. date = o.date;
  355. }
  356. // date
  357. // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
  358. // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
  359. // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
  360. dosTime = date.getHours();
  361. dosTime = dosTime << 6;
  362. dosTime = dosTime | date.getMinutes();
  363. dosTime = dosTime << 5;
  364. dosTime = dosTime | date.getSeconds() / 2;
  365. dosDate = date.getFullYear() - 1980;
  366. dosDate = dosDate << 4;
  367. dosDate = dosDate | (date.getMonth() + 1);
  368. dosDate = dosDate << 5;
  369. dosDate = dosDate | date.getDate();
  370. if (useUTF8ForFileName) {
  371. // set the unicode path extra field. unzip needs at least one extra
  372. // field to correctly handle unicode path, so using the path is as good
  373. // as any other information. This could improve the situation with
  374. // other archive managers too.
  375. // This field is usually used without the utf8 flag, with a non
  376. // unicode path in the header (winrar, winzip). This helps (a bit)
  377. // with the messy Windows' default compressed folders feature but
  378. // breaks on p7zip which doesn't seek the unicode path extra field.
  379. // So for now, UTF-8 everywhere !
  380. unicodePathExtraField =
  381. // Version
  382. decToHex(1, 1) +
  383. // NameCRC32
  384. decToHex(crc32(utfEncodedFileName), 4) +
  385. // UnicodeName
  386. utfEncodedFileName;
  387. extraFields +=
  388. // Info-ZIP Unicode Path Extra Field
  389. "\x75\x70" +
  390. // size
  391. decToHex(unicodePathExtraField.length, 2) +
  392. // content
  393. unicodePathExtraField;
  394. }
  395. if(useUTF8ForComment) {
  396. unicodeCommentExtraField =
  397. // Version
  398. decToHex(1, 1) +
  399. // CommentCRC32
  400. decToHex(this.crc32(utfEncodedComment), 4) +
  401. // UnicodeName
  402. utfEncodedComment;
  403. extraFields +=
  404. // Info-ZIP Unicode Path Extra Field
  405. "\x75\x63" +
  406. // size
  407. decToHex(unicodeCommentExtraField.length, 2) +
  408. // content
  409. unicodeCommentExtraField;
  410. }
  411. var header = "";
  412. // version needed to extract
  413. header += "\x0A\x00";
  414. // general purpose bit flag
  415. // set bit 11 if utf8
  416. header += (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00";
  417. // compression method
  418. header += compressedObject.compressionMethod;
  419. // last mod file time
  420. header += decToHex(dosTime, 2);
  421. // last mod file date
  422. header += decToHex(dosDate, 2);
  423. // crc-32
  424. header += decToHex(compressedObject.crc32, 4);
  425. // compressed size
  426. header += decToHex(compressedObject.compressedSize, 4);
  427. // uncompressed size
  428. header += decToHex(compressedObject.uncompressedSize, 4);
  429. // file name length
  430. header += decToHex(utfEncodedFileName.length, 2);
  431. // extra field length
  432. header += decToHex(extraFields.length, 2);
  433. var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields;
  434. var dirRecord = signature.CENTRAL_FILE_HEADER +
  435. // version made by (00: DOS)
  436. "\x14\x00" +
  437. // file header (common to file and central directory)
  438. header +
  439. // file comment length
  440. decToHex(utfEncodedComment.length, 2) +
  441. // disk number start
  442. "\x00\x00" +
  443. // internal file attributes TODO
  444. "\x00\x00" +
  445. // external file attributes
  446. (dir === true ? "\x10\x00\x00\x00" : "\x00\x00\x00\x00") +
  447. // relative offset of local header
  448. decToHex(offset, 4) +
  449. // file name
  450. utfEncodedFileName +
  451. // extra field
  452. extraFields +
  453. // file comment
  454. utfEncodedComment;
  455. return {
  456. fileRecord: fileRecord,
  457. dirRecord: dirRecord,
  458. compressedObject: compressedObject
  459. };
  460. };
  461. // return the actual prototype of JSZip
  462. var out = {
  463. /**
  464. * Read an existing zip and merge the data in the current JSZip object.
  465. * The implementation is in jszip-load.js, don't forget to include it.
  466. * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
  467. * @param {Object} options Options for loading the stream.
  468. * options.base64 : is the stream in base64 ? default : false
  469. * @return {JSZip} the current JSZip object
  470. */
  471. load: function(stream, options) {
  472. throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
  473. },
  474. /**
  475. * Filter nested files/folders with the specified function.
  476. * @param {Function} search the predicate to use :
  477. * function (relativePath, file) {...}
  478. * It takes 2 arguments : the relative path and the file.
  479. * @return {Array} An array of matching elements.
  480. */
  481. filter: function(search) {
  482. var result = [],
  483. filename, relativePath, file, fileClone;
  484. for (filename in this.files) {
  485. if (!this.files.hasOwnProperty(filename)) {
  486. continue;
  487. }
  488. file = this.files[filename];
  489. // return a new object, don't let the user mess with our internal objects :)
  490. fileClone = new ZipObject(file.name, file._data, extend(file.options));
  491. relativePath = filename.slice(this.root.length, filename.length);
  492. if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
  493. search(relativePath, fileClone)) { // and the file matches the function
  494. result.push(fileClone);
  495. }
  496. }
  497. return result;
  498. },
  499. /**
  500. * Add a file to the zip file, or search a file.
  501. * @param {string|RegExp} name The name of the file to add (if data is defined),
  502. * the name of the file to find (if no data) or a regex to match files.
  503. * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
  504. * @param {Object} o File options
  505. * @return {JSZip|Object|Array} this JSZip object (when adding a file),
  506. * a file (when searching by string) or an array of files (when searching by regex).
  507. */
  508. file: function(name, data, o) {
  509. if (arguments.length === 1) {
  510. if (utils.isRegExp(name)) {
  511. var regexp = name;
  512. return this.filter(function(relativePath, file) {
  513. return !file.dir && regexp.test(relativePath);
  514. });
  515. }
  516. else { // text
  517. return this.filter(function(relativePath, file) {
  518. return !file.dir && relativePath === name;
  519. })[0] || null;
  520. }
  521. }
  522. else { // more than one argument : we have data !
  523. name = this.root + name;
  524. fileAdd.call(this, name, data, o);
  525. }
  526. return this;
  527. },
  528. /**
  529. * Add a directory to the zip file, or search.
  530. * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
  531. * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
  532. */
  533. folder: function(arg) {
  534. if (!arg) {
  535. return this;
  536. }
  537. if (utils.isRegExp(arg)) {
  538. return this.filter(function(relativePath, file) {
  539. return file.dir && arg.test(relativePath);
  540. });
  541. }
  542. // else, name is a new folder
  543. var name = this.root + arg;
  544. var newFolder = folderAdd.call(this, name);
  545. // Allow chaining by returning a new object with this folder as the root
  546. var ret = this.clone();
  547. ret.root = newFolder.name;
  548. return ret;
  549. },
  550. /**
  551. * Delete a file, or a directory and all sub-files, from the zip
  552. * @param {string} name the name of the file to delete
  553. * @return {JSZip} this JSZip object
  554. */
  555. remove: function(name) {
  556. name = this.root + name;
  557. var file = this.files[name];
  558. if (!file) {
  559. // Look for any folders
  560. if (name.slice(-1) != "/") {
  561. name += "/";
  562. }
  563. file = this.files[name];
  564. }
  565. if (file && !file.dir) {
  566. // file
  567. delete this.files[name];
  568. } else {
  569. // maybe a folder, delete recursively
  570. var kids = this.filter(function(relativePath, file) {
  571. return file.name.slice(0, name.length) === name;
  572. });
  573. for (var i = 0; i < kids.length; i++) {
  574. delete this.files[kids[i].name];
  575. }
  576. }
  577. return this;
  578. },
  579. /**
  580. * Generate the complete zip file
  581. * @param {Object} options the options to generate the zip file :
  582. * - base64, (deprecated, use type instead) true to generate base64.
  583. * - compression, "STORE" by default.
  584. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
  585. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
  586. */
  587. generate: function(options) {
  588. options = extend(options || {}, {
  589. base64: true,
  590. compression: "STORE",
  591. type: "base64",
  592. comment: null
  593. });
  594. utils.checkSupport(options.type);
  595. var zipData = [],
  596. localDirLength = 0,
  597. centralDirLength = 0,
  598. writer, i,
  599. utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || ""));
  600. // first, generate all the zip parts.
  601. for (var name in this.files) {
  602. if (!this.files.hasOwnProperty(name)) {
  603. continue;
  604. }
  605. var file = this.files[name];
  606. var compressionName = file.options.compression || options.compression.toUpperCase();
  607. var compression = compressions[compressionName];
  608. if (!compression) {
  609. throw new Error(compressionName + " is not a valid compression method !");
  610. }
  611. var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
  612. var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
  613. localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
  614. centralDirLength += zipPart.dirRecord.length;
  615. zipData.push(zipPart);
  616. }
  617. var dirEnd = "";
  618. // end of central dir signature
  619. dirEnd = signature.CENTRAL_DIRECTORY_END +
  620. // number of this disk
  621. "\x00\x00" +
  622. // number of the disk with the start of the central directory
  623. "\x00\x00" +
  624. // total number of entries in the central directory on this disk
  625. decToHex(zipData.length, 2) +
  626. // total number of entries in the central directory
  627. decToHex(zipData.length, 2) +
  628. // size of the central directory 4 bytes
  629. decToHex(centralDirLength, 4) +
  630. // offset of start of central directory with respect to the starting disk number
  631. decToHex(localDirLength, 4) +
  632. // .ZIP file comment length
  633. decToHex(utfEncodedComment.length, 2) +
  634. // .ZIP file comment
  635. utfEncodedComment;
  636. // we have all the parts (and the total length)
  637. // time to create a writer !
  638. var typeName = options.type.toLowerCase();
  639. if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") {
  640. writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
  641. }else{
  642. writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
  643. }
  644. for (i = 0; i < zipData.length; i++) {
  645. writer.append(zipData[i].fileRecord);
  646. writer.append(zipData[i].compressedObject.compressedContent);
  647. }
  648. for (i = 0; i < zipData.length; i++) {
  649. writer.append(zipData[i].dirRecord);
  650. }
  651. writer.append(dirEnd);
  652. var zip = writer.finalize();
  653. switch(options.type.toLowerCase()) {
  654. // case "zip is an Uint8Array"
  655. case "uint8array" :
  656. case "arraybuffer" :
  657. case "nodebuffer" :
  658. return utils.transformTo(options.type.toLowerCase(), zip);
  659. case "blob" :
  660. return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip));
  661. // case "zip is a string"
  662. case "base64" :
  663. return (options.base64) ? base64.encode(zip) : zip;
  664. default : // case "string" :
  665. return zip;
  666. }
  667. },
  668. /**
  669. * @deprecated
  670. * This method will be removed in a future version without replacement.
  671. */
  672. crc32: function (input, crc) {
  673. return crc32(input, crc);
  674. },
  675. /**
  676. * @deprecated
  677. * This method will be removed in a future version without replacement.
  678. */
  679. utf8encode: function (string) {
  680. return utils.transformTo("string", utf8.utf8encode(string));
  681. },
  682. /**
  683. * @deprecated
  684. * This method will be removed in a future version without replacement.
  685. */
  686. utf8decode: function (input) {
  687. return utf8.utf8decode(input);
  688. }
  689. };
  690. module.exports = out;