Brak opisu

object.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. 'use strict';
  2. var utf8 = require('./utf8');
  3. var utils = require('./utils');
  4. var GenericWorker = require('./stream/GenericWorker');
  5. var StreamHelper = require('./stream/StreamHelper');
  6. var defaults = require('./defaults');
  7. var CompressedObject = require('./compressedObject');
  8. var ZipObject = require('./zipObject');
  9. var generate = require("./generate");
  10. var nodejsUtils = require("./nodejsUtils");
  11. var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter");
  12. /**
  13. * Add a file in the current folder.
  14. * @private
  15. * @param {string} name the name of the file
  16. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
  17. * @param {Object} originalOptions the options of the file
  18. * @return {Object} the new file.
  19. */
  20. var fileAdd = function(name, data, originalOptions) {
  21. // be sure sub folders exist
  22. var dataType = utils.getTypeOf(data),
  23. parent;
  24. /*
  25. * Correct options.
  26. */
  27. var o = utils.extend(originalOptions || {}, defaults);
  28. o.date = o.date || new Date();
  29. if (o.compression !== null) {
  30. o.compression = o.compression.toUpperCase();
  31. }
  32. if (typeof o.unixPermissions === "string") {
  33. o.unixPermissions = parseInt(o.unixPermissions, 8);
  34. }
  35. // UNX_IFDIR 0040000 see zipinfo.c
  36. if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
  37. o.dir = true;
  38. }
  39. // Bit 4 Directory
  40. if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
  41. o.dir = true;
  42. }
  43. if (o.dir) {
  44. name = forceTrailingSlash(name);
  45. }
  46. if (o.createFolders && (parent = parentFolder(name))) {
  47. folderAdd.call(this, parent, true);
  48. }
  49. var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false;
  50. if (!originalOptions || typeof originalOptions.binary === "undefined") {
  51. o.binary = !isUnicodeString;
  52. }
  53. var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0;
  54. if (isCompressedEmpty || o.dir || !data || data.length === 0) {
  55. o.base64 = false;
  56. o.binary = true;
  57. data = "";
  58. o.compression = "STORE";
  59. dataType = "string";
  60. }
  61. /*
  62. * Convert content to fit.
  63. */
  64. var zipObjectContent = null;
  65. if (data instanceof CompressedObject || data instanceof GenericWorker) {
  66. zipObjectContent = data;
  67. } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
  68. zipObjectContent = new NodejsStreamInputAdapter(name, data);
  69. } else {
  70. zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64);
  71. }
  72. var object = new ZipObject(name, zipObjectContent, o);
  73. this.files[name] = object;
  74. /*
  75. TODO: we can't throw an exception because we have async promises
  76. (we can have a promise of a Date() for example) but returning a
  77. promise is useless because file(name, data) returns the JSZip
  78. object for chaining. Should we break that to allow the user
  79. to catch the error ?
  80. return external.Promise.resolve(zipObjectContent)
  81. .then(function () {
  82. return object;
  83. });
  84. */
  85. };
  86. /**
  87. * Find the parent folder of the path.
  88. * @private
  89. * @param {string} path the path to use
  90. * @return {string} the parent folder, or ""
  91. */
  92. var parentFolder = function (path) {
  93. if (path.slice(-1) === '/') {
  94. path = path.substring(0, path.length - 1);
  95. }
  96. var lastSlash = path.lastIndexOf('/');
  97. return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
  98. };
  99. /**
  100. * Returns the path with a slash at the end.
  101. * @private
  102. * @param {String} path the path to check.
  103. * @return {String} the path with a trailing slash.
  104. */
  105. var forceTrailingSlash = function(path) {
  106. // Check the name ends with a /
  107. if (path.slice(-1) !== "/") {
  108. path += "/"; // IE doesn't like substr(-1)
  109. }
  110. return path;
  111. };
  112. /**
  113. * Add a (sub) folder in the current folder.
  114. * @private
  115. * @param {string} name the folder's name
  116. * @param {boolean=} [createFolders] If true, automatically create sub
  117. * folders. Defaults to false.
  118. * @return {Object} the new folder.
  119. */
  120. var folderAdd = function(name, createFolders) {
  121. createFolders = (typeof createFolders !== 'undefined') ? createFolders : defaults.createFolders;
  122. name = forceTrailingSlash(name);
  123. // Does this folder already exist?
  124. if (!this.files[name]) {
  125. fileAdd.call(this, name, null, {
  126. dir: true,
  127. createFolders: createFolders
  128. });
  129. }
  130. return this.files[name];
  131. };
  132. /**
  133. * Cross-window, cross-Node-context regular expression detection
  134. * @param {Object} object Anything
  135. * @return {Boolean} true if the object is a regular expression,
  136. * false otherwise
  137. */
  138. function isRegExp(object) {
  139. return Object.prototype.toString.call(object) === "[object RegExp]";
  140. }
  141. // return the actual prototype of JSZip
  142. var out = {
  143. /**
  144. * @see loadAsync
  145. */
  146. load: function() {
  147. throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
  148. },
  149. /**
  150. * Call a callback function for each entry at this folder level.
  151. * @param {Function} cb the callback function:
  152. * function (relativePath, file) {...}
  153. * It takes 2 arguments : the relative path and the file.
  154. */
  155. forEach: function(cb) {
  156. var filename, relativePath, file;
  157. for (filename in this.files) {
  158. if (!this.files.hasOwnProperty(filename)) {
  159. continue;
  160. }
  161. file = this.files[filename];
  162. relativePath = filename.slice(this.root.length, filename.length);
  163. if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root
  164. cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn...
  165. }
  166. }
  167. },
  168. /**
  169. * Filter nested files/folders with the specified function.
  170. * @param {Function} search the predicate to use :
  171. * function (relativePath, file) {...}
  172. * It takes 2 arguments : the relative path and the file.
  173. * @return {Array} An array of matching elements.
  174. */
  175. filter: function(search) {
  176. var result = [];
  177. this.forEach(function (relativePath, entry) {
  178. if (search(relativePath, entry)) { // the file matches the function
  179. result.push(entry);
  180. }
  181. });
  182. return result;
  183. },
  184. /**
  185. * Add a file to the zip file, or search a file.
  186. * @param {string|RegExp} name The name of the file to add (if data is defined),
  187. * the name of the file to find (if no data) or a regex to match files.
  188. * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
  189. * @param {Object} o File options
  190. * @return {JSZip|Object|Array} this JSZip object (when adding a file),
  191. * a file (when searching by string) or an array of files (when searching by regex).
  192. */
  193. file: function(name, data, o) {
  194. if (arguments.length === 1) {
  195. if (isRegExp(name)) {
  196. var regexp = name;
  197. return this.filter(function(relativePath, file) {
  198. return !file.dir && regexp.test(relativePath);
  199. });
  200. }
  201. else { // text
  202. var obj = this.files[this.root + name];
  203. if (obj && !obj.dir) {
  204. return obj;
  205. } else {
  206. return null;
  207. }
  208. }
  209. }
  210. else { // more than one argument : we have data !
  211. name = this.root + name;
  212. fileAdd.call(this, name, data, o);
  213. }
  214. return this;
  215. },
  216. /**
  217. * Add a directory to the zip file, or search.
  218. * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
  219. * @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
  220. */
  221. folder: function(arg) {
  222. if (!arg) {
  223. return this;
  224. }
  225. if (isRegExp(arg)) {
  226. return this.filter(function(relativePath, file) {
  227. return file.dir && arg.test(relativePath);
  228. });
  229. }
  230. // else, name is a new folder
  231. var name = this.root + arg;
  232. var newFolder = folderAdd.call(this, name);
  233. // Allow chaining by returning a new object with this folder as the root
  234. var ret = this.clone();
  235. ret.root = newFolder.name;
  236. return ret;
  237. },
  238. /**
  239. * Delete a file, or a directory and all sub-files, from the zip
  240. * @param {string} name the name of the file to delete
  241. * @return {JSZip} this JSZip object
  242. */
  243. remove: function(name) {
  244. name = this.root + name;
  245. var file = this.files[name];
  246. if (!file) {
  247. // Look for any folders
  248. if (name.slice(-1) !== "/") {
  249. name += "/";
  250. }
  251. file = this.files[name];
  252. }
  253. if (file && !file.dir) {
  254. // file
  255. delete this.files[name];
  256. } else {
  257. // maybe a folder, delete recursively
  258. var kids = this.filter(function(relativePath, file) {
  259. return file.name.slice(0, name.length) === name;
  260. });
  261. for (var i = 0; i < kids.length; i++) {
  262. delete this.files[kids[i].name];
  263. }
  264. }
  265. return this;
  266. },
  267. /**
  268. * Generate the complete zip file
  269. * @param {Object} options the options to generate the zip file :
  270. * - compression, "STORE" by default.
  271. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
  272. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
  273. */
  274. generate: function(options) {
  275. throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
  276. },
  277. /**
  278. * Generate the complete zip file as an internal stream.
  279. * @param {Object} options the options to generate the zip file :
  280. * - compression, "STORE" by default.
  281. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
  282. * @return {StreamHelper} the streamed zip file.
  283. */
  284. generateInternalStream: function(options) {
  285. var worker, opts = {};
  286. try {
  287. opts = utils.extend(options || {}, {
  288. streamFiles: false,
  289. compression: "STORE",
  290. compressionOptions : null,
  291. type: "",
  292. platform: "DOS",
  293. comment: null,
  294. mimeType: 'application/zip',
  295. encodeFileName: utf8.utf8encode
  296. });
  297. opts.type = opts.type.toLowerCase();
  298. opts.compression = opts.compression.toUpperCase();
  299. // "binarystring" is prefered but the internals use "string".
  300. if(opts.type === "binarystring") {
  301. opts.type = "string";
  302. }
  303. if (!opts.type) {
  304. throw new Error("No output type specified.");
  305. }
  306. utils.checkSupport(opts.type);
  307. // accept nodejs `process.platform`
  308. if(
  309. opts.platform === 'darwin' ||
  310. opts.platform === 'freebsd' ||
  311. opts.platform === 'linux' ||
  312. opts.platform === 'sunos'
  313. ) {
  314. opts.platform = "UNIX";
  315. }
  316. if (opts.platform === 'win32') {
  317. opts.platform = "DOS";
  318. }
  319. var comment = opts.comment || this.comment || "";
  320. worker = generate.generateWorker(this, opts, comment);
  321. } catch (e) {
  322. worker = new GenericWorker("error");
  323. worker.error(e);
  324. }
  325. return new StreamHelper(worker, opts.type || "string", opts.mimeType);
  326. },
  327. /**
  328. * Generate the complete zip file asynchronously.
  329. * @see generateInternalStream
  330. */
  331. generateAsync: function(options, onUpdate) {
  332. return this.generateInternalStream(options).accumulate(onUpdate);
  333. },
  334. /**
  335. * Generate the complete zip file asynchronously.
  336. * @see generateInternalStream
  337. */
  338. generateNodeStream: function(options, onUpdate) {
  339. options = options || {};
  340. if (!options.type) {
  341. options.type = "nodebuffer";
  342. }
  343. return this.generateInternalStream(options).toNodejsStream(onUpdate);
  344. }
  345. };
  346. module.exports = out;