object.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  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.unixPermissions = options.unixPermissions;
  98. this.dosPermissions = options.dosPermissions;
  99. this._data = data;
  100. this.options = options;
  101. /*
  102. * This object contains initial values for dir and date.
  103. * With them, we can check if the user changed the deprecated metadata in
  104. * `ZipObject#options` or not.
  105. */
  106. this._initialMetadata = {
  107. dir : options.dir,
  108. date : options.date
  109. };
  110. };
  111. ZipObject.prototype = {
  112. /**
  113. * Return the content as UTF8 string.
  114. * @return {string} the UTF8 string.
  115. */
  116. asText: function() {
  117. return dataToString.call(this, true);
  118. },
  119. /**
  120. * Returns the binary content.
  121. * @return {string} the content as binary.
  122. */
  123. asBinary: function() {
  124. return dataToString.call(this, false);
  125. },
  126. /**
  127. * Returns the content as a nodejs Buffer.
  128. * @return {Buffer} the content as a Buffer.
  129. */
  130. asNodeBuffer: function() {
  131. var result = getBinaryData(this);
  132. return utils.transformTo("nodebuffer", result);
  133. },
  134. /**
  135. * Returns the content as an Uint8Array.
  136. * @return {Uint8Array} the content as an Uint8Array.
  137. */
  138. asUint8Array: function() {
  139. var result = getBinaryData(this);
  140. return utils.transformTo("uint8array", result);
  141. },
  142. /**
  143. * Returns the content as an ArrayBuffer.
  144. * @return {ArrayBuffer} the content as an ArrayBufer.
  145. */
  146. asArrayBuffer: function() {
  147. return this.asUint8Array().buffer;
  148. }
  149. };
  150. /**
  151. * Transform an integer into a string in hexadecimal.
  152. * @private
  153. * @param {number} dec the number to convert.
  154. * @param {number} bytes the number of bytes to generate.
  155. * @returns {string} the result.
  156. */
  157. var decToHex = function(dec, bytes) {
  158. var hex = "",
  159. i;
  160. for (i = 0; i < bytes; i++) {
  161. hex += String.fromCharCode(dec & 0xff);
  162. dec = dec >>> 8;
  163. }
  164. return hex;
  165. };
  166. /**
  167. * Transforms the (incomplete) options from the user into the complete
  168. * set of options to create a file.
  169. * @private
  170. * @param {Object} o the options from the user.
  171. * @return {Object} the complete set of options.
  172. */
  173. var prepareFileAttrs = function(o) {
  174. o = o || {};
  175. if (o.base64 === true && (o.binary === null || o.binary === undefined)) {
  176. o.binary = true;
  177. }
  178. o = utils.extend(o, defaults);
  179. o.date = o.date || new Date();
  180. if (o.compression !== null) o.compression = o.compression.toUpperCase();
  181. return o;
  182. };
  183. /**
  184. * Add a file in the current folder.
  185. * @private
  186. * @param {string} name the name of the file
  187. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
  188. * @param {Object} o the options of the file
  189. * @return {Object} the new file.
  190. */
  191. var fileAdd = function(name, data, o) {
  192. // be sure sub folders exist
  193. var dataType = utils.getTypeOf(data),
  194. parent;
  195. o = prepareFileAttrs(o);
  196. if (typeof o.unixPermissions === "string") {
  197. o.unixPermissions = parseInt(o.unixPermissions, 8);
  198. }
  199. // UNX_IFDIR 0040000 see zipinfo.c
  200. if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
  201. o.dir = true;
  202. }
  203. // Bit 4 Directory
  204. if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
  205. o.dir = true;
  206. }
  207. if (o.dir) {
  208. name = forceTrailingSlash(name);
  209. }
  210. if (o.createFolders && (parent = parentFolder(name))) {
  211. folderAdd.call(this, parent, true);
  212. }
  213. if (o.dir || data === null || typeof data === "undefined") {
  214. o.base64 = false;
  215. o.binary = false;
  216. data = null;
  217. dataType = 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. * Returns the path with a slash at the end.
  259. * @private
  260. * @param {String} path the path to check.
  261. * @return {String} the path with a trailing slash.
  262. */
  263. var forceTrailingSlash = function(path) {
  264. // Check the name ends with a /
  265. if (path.slice(-1) != "/") {
  266. path += "/"; // IE doesn't like substr(-1)
  267. }
  268. return path;
  269. };
  270. /**
  271. * Add a (sub) folder in the current folder.
  272. * @private
  273. * @param {string} name the folder's name
  274. * @param {boolean=} [createFolders] If true, automatically create sub
  275. * folders. Defaults to false.
  276. * @return {Object} the new folder.
  277. */
  278. var folderAdd = function(name, createFolders) {
  279. createFolders = (typeof createFolders !== 'undefined') ? createFolders : false;
  280. name = forceTrailingSlash(name);
  281. // Does this folder already exist?
  282. if (!this.files[name]) {
  283. fileAdd.call(this, name, null, {
  284. dir: true,
  285. createFolders: createFolders
  286. });
  287. }
  288. return this.files[name];
  289. };
  290. /**
  291. * Generate a JSZip.CompressedObject for a given zipOject.
  292. * @param {ZipObject} file the object to read.
  293. * @param {JSZip.compression} compression the compression to use.
  294. * @param {Object} compressionOptions the options to use when compressing.
  295. * @return {JSZip.CompressedObject} the compressed result.
  296. */
  297. var generateCompressedObjectFrom = function(file, compression, compressionOptions) {
  298. var result = new CompressedObject(),
  299. content;
  300. // the data has not been decompressed, we might reuse things !
  301. if (file._data instanceof CompressedObject) {
  302. result.uncompressedSize = file._data.uncompressedSize;
  303. result.crc32 = file._data.crc32;
  304. if (result.uncompressedSize === 0 || file.dir) {
  305. compression = compressions['STORE'];
  306. result.compressedContent = "";
  307. result.crc32 = 0;
  308. }
  309. else if (file._data.compressionMethod === compression.magic) {
  310. result.compressedContent = file._data.getCompressedContent();
  311. }
  312. else {
  313. content = file._data.getContent();
  314. // need to decompress / recompress
  315. result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
  316. }
  317. }
  318. else {
  319. // have uncompressed data
  320. content = getBinaryData(file);
  321. if (!content || content.length === 0 || file.dir) {
  322. compression = compressions['STORE'];
  323. content = "";
  324. }
  325. result.uncompressedSize = content.length;
  326. result.crc32 = crc32(content);
  327. result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
  328. }
  329. result.compressedSize = result.compressedContent.length;
  330. result.compressionMethod = compression.magic;
  331. return result;
  332. };
  333. /**
  334. * Generate the UNIX part of the external file attributes.
  335. * @param {Object} unixPermissions the unix permissions or null.
  336. * @param {Boolean} isDir true if the entry is a directory, false otherwise.
  337. * @return {Number} a 32 bit integer.
  338. *
  339. * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :
  340. *
  341. * TTTTsstrwxrwxrwx0000000000ADVSHR
  342. * ^^^^____________________________ file type, see zipinfo.c (UNX_*)
  343. * ^^^_________________________ setuid, setgid, sticky
  344. * ^^^^^^^^^________________ permissions
  345. * ^^^^^^^^^^______ not used ?
  346. * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only
  347. */
  348. var generateUnixExternalFileAttr = function (unixPermissions, isDir) {
  349. var result = unixPermissions;
  350. if (!unixPermissions) {
  351. // I can't use octal values in strict mode, hence the hexa.
  352. // 040775 => 0x41fd
  353. // 0100664 => 0x81b4
  354. result = isDir ? 0x41fd : 0x81b4;
  355. }
  356. return (result & 0xFFFF) << 16;
  357. };
  358. /**
  359. * Generate the DOS part of the external file attributes.
  360. * @param {Object} dosPermissions the dos permissions or null.
  361. * @param {Boolean} isDir true if the entry is a directory, false otherwise.
  362. * @return {Number} a 32 bit integer.
  363. *
  364. * Bit 0 Read-Only
  365. * Bit 1 Hidden
  366. * Bit 2 System
  367. * Bit 3 Volume Label
  368. * Bit 4 Directory
  369. * Bit 5 Archive
  370. */
  371. var generateDosExternalFileAttr = function (dosPermissions, isDir) {
  372. // the dir flag is already set for compatibility
  373. return (dosPermissions || 0) & 0x3F;
  374. };
  375. /**
  376. * Generate the various parts used in the construction of the final zip file.
  377. * @param {string} name the file name.
  378. * @param {ZipObject} file the file content.
  379. * @param {JSZip.CompressedObject} compressedObject the compressed object.
  380. * @param {number} offset the current offset from the start of the zip file.
  381. * @param {String} platform let's pretend we are this platform (change platform dependents fields)
  382. * @param {Function} encodeFileName the function to encode the file name / comment.
  383. * @return {object} the zip parts.
  384. */
  385. var generateZipParts = function(name, file, compressedObject, offset, platform, encodeFileName) {
  386. var data = compressedObject.compressedContent,
  387. useCustomEncoding = encodeFileName !== utf8.utf8encode,
  388. encodedFileName = utils.transformTo("string", encodeFileName(file.name)),
  389. utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
  390. comment = file.comment || "",
  391. encodedComment = utils.transformTo("string", encodeFileName(comment)),
  392. utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
  393. useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
  394. useUTF8ForComment = utfEncodedComment.length !== comment.length,
  395. o = file.options,
  396. dosTime,
  397. dosDate,
  398. extraFields = "",
  399. unicodePathExtraField = "",
  400. unicodeCommentExtraField = "",
  401. dir, date;
  402. // handle the deprecated options.dir
  403. if (file._initialMetadata.dir !== file.dir) {
  404. dir = file.dir;
  405. } else {
  406. dir = o.dir;
  407. }
  408. // handle the deprecated options.date
  409. if(file._initialMetadata.date !== file.date) {
  410. date = file.date;
  411. } else {
  412. date = o.date;
  413. }
  414. var extFileAttr = 0;
  415. var versionMadeBy = 0;
  416. if (dir) {
  417. // dos or unix, we set the dos dir flag
  418. extFileAttr |= 0x00010;
  419. }
  420. if(platform === "UNIX") {
  421. versionMadeBy = 0x031E; // UNIX, version 3.0
  422. extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
  423. } else { // DOS or other, fallback to DOS
  424. versionMadeBy = 0x0014; // DOS, version 2.0
  425. extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir);
  426. }
  427. // date
  428. // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
  429. // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
  430. // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
  431. dosTime = date.getHours();
  432. dosTime = dosTime << 6;
  433. dosTime = dosTime | date.getMinutes();
  434. dosTime = dosTime << 5;
  435. dosTime = dosTime | date.getSeconds() / 2;
  436. dosDate = date.getFullYear() - 1980;
  437. dosDate = dosDate << 4;
  438. dosDate = dosDate | (date.getMonth() + 1);
  439. dosDate = dosDate << 5;
  440. dosDate = dosDate | date.getDate();
  441. if (useUTF8ForFileName) {
  442. // set the unicode path extra field. unzip needs at least one extra
  443. // field to correctly handle unicode path, so using the path is as good
  444. // as any other information. This could improve the situation with
  445. // other archive managers too.
  446. // This field is usually used without the utf8 flag, with a non
  447. // unicode path in the header (winrar, winzip). This helps (a bit)
  448. // with the messy Windows' default compressed folders feature but
  449. // breaks on p7zip which doesn't seek the unicode path extra field.
  450. // So for now, UTF-8 everywhere !
  451. unicodePathExtraField =
  452. // Version
  453. decToHex(1, 1) +
  454. // NameCRC32
  455. decToHex(crc32(encodedFileName), 4) +
  456. // UnicodeName
  457. utfEncodedFileName;
  458. extraFields +=
  459. // Info-ZIP Unicode Path Extra Field
  460. "\x75\x70" +
  461. // size
  462. decToHex(unicodePathExtraField.length, 2) +
  463. // content
  464. unicodePathExtraField;
  465. }
  466. if(useUTF8ForComment) {
  467. unicodeCommentExtraField =
  468. // Version
  469. decToHex(1, 1) +
  470. // CommentCRC32
  471. decToHex(this.crc32(encodedComment), 4) +
  472. // UnicodeName
  473. utfEncodedComment;
  474. extraFields +=
  475. // Info-ZIP Unicode Path Extra Field
  476. "\x75\x63" +
  477. // size
  478. decToHex(unicodeCommentExtraField.length, 2) +
  479. // content
  480. unicodeCommentExtraField;
  481. }
  482. var header = "";
  483. // version needed to extract
  484. header += "\x0A\x00";
  485. // general purpose bit flag
  486. // set bit 11 if utf8
  487. header += !useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00";
  488. // compression method
  489. header += compressedObject.compressionMethod;
  490. // last mod file time
  491. header += decToHex(dosTime, 2);
  492. // last mod file date
  493. header += decToHex(dosDate, 2);
  494. // crc-32
  495. header += decToHex(compressedObject.crc32, 4);
  496. // compressed size
  497. header += decToHex(compressedObject.compressedSize, 4);
  498. // uncompressed size
  499. header += decToHex(compressedObject.uncompressedSize, 4);
  500. // file name length
  501. header += decToHex(encodedFileName.length, 2);
  502. // extra field length
  503. header += decToHex(extraFields.length, 2);
  504. var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields;
  505. var dirRecord = signature.CENTRAL_FILE_HEADER +
  506. // version made by (00: DOS)
  507. decToHex(versionMadeBy, 2) +
  508. // file header (common to file and central directory)
  509. header +
  510. // file comment length
  511. decToHex(encodedComment.length, 2) +
  512. // disk number start
  513. "\x00\x00" +
  514. // internal file attributes TODO
  515. "\x00\x00" +
  516. // external file attributes
  517. decToHex(extFileAttr, 4) +
  518. // relative offset of local header
  519. decToHex(offset, 4) +
  520. // file name
  521. encodedFileName +
  522. // extra field
  523. extraFields +
  524. // file comment
  525. encodedComment;
  526. return {
  527. fileRecord: fileRecord,
  528. dirRecord: dirRecord,
  529. compressedObject: compressedObject
  530. };
  531. };
  532. // return the actual prototype of JSZip
  533. var out = {
  534. /**
  535. * Read an existing zip and merge the data in the current JSZip object.
  536. * The implementation is in jszip-load.js, don't forget to include it.
  537. * @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
  538. * @param {Object} options Options for loading the stream.
  539. * options.base64 : is the stream in base64 ? default : false
  540. * @return {JSZip} the current JSZip object
  541. */
  542. load: function(stream, options) {
  543. throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
  544. },
  545. /**
  546. * Filter nested files/folders with the specified function.
  547. * @param {Function} search the predicate to use :
  548. * function (relativePath, file) {...}
  549. * It takes 2 arguments : the relative path and the file.
  550. * @return {Array} An array of matching elements.
  551. */
  552. filter: function(search) {
  553. var result = [],
  554. filename, relativePath, file, fileClone;
  555. for (filename in this.files) {
  556. if (!this.files.hasOwnProperty(filename)) {
  557. continue;
  558. }
  559. file = this.files[filename];
  560. // return a new object, don't let the user mess with our internal objects :)
  561. fileClone = new ZipObject(file.name, file._data, utils.extend(file.options));
  562. relativePath = filename.slice(this.root.length, filename.length);
  563. if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
  564. search(relativePath, fileClone)) { // and the file matches the function
  565. result.push(fileClone);
  566. }
  567. }
  568. return result;
  569. },
  570. /**
  571. * Add a file to the zip file, or search a file.
  572. * @param {string|RegExp} name The name of the file to add (if data is defined),
  573. * the name of the file to find (if no data) or a regex to match files.
  574. * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
  575. * @param {Object} o File options
  576. * @return {JSZip|Object|Array} this JSZip object (when adding a file),
  577. * a file (when searching by string) or an array of files (when searching by regex).
  578. */
  579. file: function(name, data, o) {
  580. if (arguments.length === 1) {
  581. if (utils.isRegExp(name)) {
  582. var regexp = name;
  583. return this.filter(function(relativePath, file) {
  584. return !file.dir && regexp.test(relativePath);
  585. });
  586. }
  587. else { // text
  588. return this.filter(function(relativePath, file) {
  589. return !file.dir && relativePath === name;
  590. })[0] || null;
  591. }
  592. }
  593. else { // more than one argument : we have data !
  594. name = this.root + name;
  595. fileAdd.call(this, name, data, o);
  596. }
  597. return this;
  598. },
  599. /**
  600. * Add a directory to the zip file, or search.
  601. * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
  602. * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
  603. */
  604. folder: function(arg) {
  605. if (!arg) {
  606. return this;
  607. }
  608. if (utils.isRegExp(arg)) {
  609. return this.filter(function(relativePath, file) {
  610. return file.dir && arg.test(relativePath);
  611. });
  612. }
  613. // else, name is a new folder
  614. var name = this.root + arg;
  615. var newFolder = folderAdd.call(this, name);
  616. // Allow chaining by returning a new object with this folder as the root
  617. var ret = this.clone();
  618. ret.root = newFolder.name;
  619. return ret;
  620. },
  621. /**
  622. * Delete a file, or a directory and all sub-files, from the zip
  623. * @param {string} name the name of the file to delete
  624. * @return {JSZip} this JSZip object
  625. */
  626. remove: function(name) {
  627. name = this.root + name;
  628. var file = this.files[name];
  629. if (!file) {
  630. // Look for any folders
  631. if (name.slice(-1) != "/") {
  632. name += "/";
  633. }
  634. file = this.files[name];
  635. }
  636. if (file && !file.dir) {
  637. // file
  638. delete this.files[name];
  639. } else {
  640. // maybe a folder, delete recursively
  641. var kids = this.filter(function(relativePath, file) {
  642. return file.name.slice(0, name.length) === name;
  643. });
  644. for (var i = 0; i < kids.length; i++) {
  645. delete this.files[kids[i].name];
  646. }
  647. }
  648. return this;
  649. },
  650. /**
  651. * Generate the complete zip file
  652. * @param {Object} options the options to generate the zip file :
  653. * - base64, (deprecated, use type instead) true to generate base64.
  654. * - compression, "STORE" by default.
  655. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
  656. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
  657. */
  658. generate: function(options) {
  659. options = utils.extend(options || {}, {
  660. base64: true,
  661. compression: "STORE",
  662. compressionOptions : null,
  663. type: "base64",
  664. platform: "DOS",
  665. comment: null,
  666. mimeType: 'application/zip',
  667. encodeFileName: utf8.utf8encode
  668. });
  669. utils.checkSupport(options.type);
  670. // accept nodejs `process.platform`
  671. if(
  672. options.platform === 'darwin' ||
  673. options.platform === 'freebsd' ||
  674. options.platform === 'linux' ||
  675. options.platform === 'sunos'
  676. ) {
  677. options.platform = "UNIX";
  678. }
  679. if (options.platform === 'win32') {
  680. options.platform = "DOS";
  681. }
  682. var zipData = [],
  683. localDirLength = 0,
  684. centralDirLength = 0,
  685. writer, i,
  686. encodedComment = utils.transformTo("string", options.encodeFileName(options.comment || this.comment || ""));
  687. // first, generate all the zip parts.
  688. for (var name in this.files) {
  689. if (!this.files.hasOwnProperty(name)) {
  690. continue;
  691. }
  692. var file = this.files[name];
  693. var compressionName = file.options.compression || options.compression.toUpperCase();
  694. var compression = compressions[compressionName];
  695. if (!compression) {
  696. throw new Error(compressionName + " is not a valid compression method !");
  697. }
  698. var compressionOptions = file.options.compressionOptions || options.compressionOptions || {};
  699. var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions);
  700. var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform, options.encodeFileName);
  701. localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
  702. centralDirLength += zipPart.dirRecord.length;
  703. zipData.push(zipPart);
  704. }
  705. var dirEnd = "";
  706. // end of central dir signature
  707. dirEnd = signature.CENTRAL_DIRECTORY_END +
  708. // number of this disk
  709. "\x00\x00" +
  710. // number of the disk with the start of the central directory
  711. "\x00\x00" +
  712. // total number of entries in the central directory on this disk
  713. decToHex(zipData.length, 2) +
  714. // total number of entries in the central directory
  715. decToHex(zipData.length, 2) +
  716. // size of the central directory 4 bytes
  717. decToHex(centralDirLength, 4) +
  718. // offset of start of central directory with respect to the starting disk number
  719. decToHex(localDirLength, 4) +
  720. // .ZIP file comment length
  721. decToHex(encodedComment.length, 2) +
  722. // .ZIP file comment
  723. encodedComment;
  724. // we have all the parts (and the total length)
  725. // time to create a writer !
  726. var typeName = options.type.toLowerCase();
  727. if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") {
  728. writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
  729. }else{
  730. writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
  731. }
  732. for (i = 0; i < zipData.length; i++) {
  733. writer.append(zipData[i].fileRecord);
  734. writer.append(zipData[i].compressedObject.compressedContent);
  735. }
  736. for (i = 0; i < zipData.length; i++) {
  737. writer.append(zipData[i].dirRecord);
  738. }
  739. writer.append(dirEnd);
  740. var zip = writer.finalize();
  741. switch(options.type.toLowerCase()) {
  742. // case "zip is an Uint8Array"
  743. case "uint8array" :
  744. case "arraybuffer" :
  745. case "nodebuffer" :
  746. return utils.transformTo(options.type.toLowerCase(), zip);
  747. case "blob" :
  748. return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType);
  749. // case "zip is a string"
  750. case "base64" :
  751. return (options.base64) ? base64.encode(zip) : zip;
  752. default : // case "string" :
  753. return zip;
  754. }
  755. },
  756. /**
  757. * @deprecated
  758. * This method will be removed in a future version without replacement.
  759. */
  760. crc32: function (input, crc) {
  761. return crc32(input, crc);
  762. },
  763. /**
  764. * @deprecated
  765. * This method will be removed in a future version without replacement.
  766. */
  767. utf8encode: function (string) {
  768. return utils.transformTo("string", utf8.utf8encode(string));
  769. },
  770. /**
  771. * @deprecated
  772. * This method will be removed in a future version without replacement.
  773. */
  774. utf8decode: function (input) {
  775. return utf8.utf8decode(input);
  776. }
  777. };
  778. module.exports = out;