You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							385 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							385 lines
						
					
					
						
							12 KiB
						
					
					
				| "use strict";
 | |
| var utf8 = require("./utf8");
 | |
| var utils = require("./utils");
 | |
| var GenericWorker = require("./stream/GenericWorker");
 | |
| var StreamHelper = require("./stream/StreamHelper");
 | |
| var defaults = require("./defaults");
 | |
| var CompressedObject = require("./compressedObject");
 | |
| var ZipObject = require("./zipObject");
 | |
| var generate = require("./generate");
 | |
| var nodejsUtils = require("./nodejsUtils");
 | |
| var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter");
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Add a file in the current folder.
 | |
|  * @private
 | |
|  * @param {string} name the name of the file
 | |
|  * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
 | |
|  * @param {Object} originalOptions the options of the file
 | |
|  * @return {Object} the new file.
 | |
|  */
 | |
| var fileAdd = function(name, data, originalOptions) {
 | |
|     // be sure sub folders exist
 | |
|     var dataType = utils.getTypeOf(data),
 | |
|         parent;
 | |
| 
 | |
| 
 | |
|     /*
 | |
|      * Correct options.
 | |
|      */
 | |
| 
 | |
|     var o = utils.extend(originalOptions || {}, defaults);
 | |
|     o.date = o.date || new Date();
 | |
|     if (o.compression !== null) {
 | |
|         o.compression = o.compression.toUpperCase();
 | |
|     }
 | |
| 
 | |
|     if (typeof o.unixPermissions === "string") {
 | |
|         o.unixPermissions = parseInt(o.unixPermissions, 8);
 | |
|     }
 | |
| 
 | |
|     // UNX_IFDIR  0040000 see zipinfo.c
 | |
|     if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
 | |
|         o.dir = true;
 | |
|     }
 | |
|     // Bit 4    Directory
 | |
|     if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
 | |
|         o.dir = true;
 | |
|     }
 | |
| 
 | |
|     if (o.dir) {
 | |
|         name = forceTrailingSlash(name);
 | |
|     }
 | |
|     if (o.createFolders && (parent = parentFolder(name))) {
 | |
|         folderAdd.call(this, parent, true);
 | |
|     }
 | |
| 
 | |
|     var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false;
 | |
|     if (!originalOptions || typeof originalOptions.binary === "undefined") {
 | |
|         o.binary = !isUnicodeString;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0;
 | |
| 
 | |
|     if (isCompressedEmpty || o.dir || !data || data.length === 0) {
 | |
|         o.base64 = false;
 | |
|         o.binary = true;
 | |
|         data = "";
 | |
|         o.compression = "STORE";
 | |
|         dataType = "string";
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Convert content to fit.
 | |
|      */
 | |
| 
 | |
|     var zipObjectContent = null;
 | |
|     if (data instanceof CompressedObject || data instanceof GenericWorker) {
 | |
|         zipObjectContent = data;
 | |
|     } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
 | |
|         zipObjectContent = new NodejsStreamInputAdapter(name, data);
 | |
|     } else {
 | |
|         zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64);
 | |
|     }
 | |
| 
 | |
|     var object = new ZipObject(name, zipObjectContent, o);
 | |
|     this.files[name] = object;
 | |
|     /*
 | |
|     TODO: we can't throw an exception because we have async promises
 | |
|     (we can have a promise of a Date() for example) but returning a
 | |
|     promise is useless because file(name, data) returns the JSZip
 | |
|     object for chaining. Should we break that to allow the user
 | |
|     to catch the error ?
 | |
| 
 | |
|     return external.Promise.resolve(zipObjectContent)
 | |
|     .then(function () {
 | |
|         return object;
 | |
|     });
 | |
|     */
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Find the parent folder of the path.
 | |
|  * @private
 | |
|  * @param {string} path the path to use
 | |
|  * @return {string} the parent folder, or ""
 | |
|  */
 | |
| var parentFolder = function (path) {
 | |
|     if (path.slice(-1) === "/") {
 | |
|         path = path.substring(0, path.length - 1);
 | |
|     }
 | |
|     var lastSlash = path.lastIndexOf("/");
 | |
|     return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Returns the path with a slash at the end.
 | |
|  * @private
 | |
|  * @param {String} path the path to check.
 | |
|  * @return {String} the path with a trailing slash.
 | |
|  */
 | |
| var forceTrailingSlash = function(path) {
 | |
|     // Check the name ends with a /
 | |
|     if (path.slice(-1) !== "/") {
 | |
|         path += "/"; // IE doesn't like substr(-1)
 | |
|     }
 | |
|     return path;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Add a (sub) folder in the current folder.
 | |
|  * @private
 | |
|  * @param {string} name the folder's name
 | |
|  * @param {boolean=} [createFolders] If true, automatically create sub
 | |
|  *  folders. Defaults to false.
 | |
|  * @return {Object} the new folder.
 | |
|  */
 | |
| var folderAdd = function(name, createFolders) {
 | |
|     createFolders = (typeof createFolders !== "undefined") ? createFolders : defaults.createFolders;
 | |
| 
 | |
|     name = forceTrailingSlash(name);
 | |
| 
 | |
|     // Does this folder already exist?
 | |
|     if (!this.files[name]) {
 | |
|         fileAdd.call(this, name, null, {
 | |
|             dir: true,
 | |
|             createFolders: createFolders
 | |
|         });
 | |
|     }
 | |
|     return this.files[name];
 | |
| };
 | |
| 
 | |
| /**
 | |
| * Cross-window, cross-Node-context regular expression detection
 | |
| * @param  {Object}  object Anything
 | |
| * @return {Boolean}        true if the object is a regular expression,
 | |
| * false otherwise
 | |
| */
 | |
| function isRegExp(object) {
 | |
|     return Object.prototype.toString.call(object) === "[object RegExp]";
 | |
| }
 | |
| 
 | |
| // return the actual prototype of JSZip
 | |
| var out = {
 | |
|     /**
 | |
|      * @see loadAsync
 | |
|      */
 | |
|     load: function() {
 | |
|         throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
 | |
|     },
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Call a callback function for each entry at this folder level.
 | |
|      * @param {Function} cb the callback function:
 | |
|      * function (relativePath, file) {...}
 | |
|      * It takes 2 arguments : the relative path and the file.
 | |
|      */
 | |
|     forEach: function(cb) {
 | |
|         var filename, relativePath, file;
 | |
|         // ignore warning about unwanted properties because this.files is a null prototype object
 | |
|         /* eslint-disable-next-line guard-for-in */
 | |
|         for (filename in this.files) {
 | |
|             file = this.files[filename];
 | |
|             relativePath = filename.slice(this.root.length, filename.length);
 | |
|             if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root
 | |
|                 cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn...
 | |
|             }
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Filter nested files/folders with the specified function.
 | |
|      * @param {Function} search the predicate to use :
 | |
|      * function (relativePath, file) {...}
 | |
|      * It takes 2 arguments : the relative path and the file.
 | |
|      * @return {Array} An array of matching elements.
 | |
|      */
 | |
|     filter: function(search) {
 | |
|         var result = [];
 | |
|         this.forEach(function (relativePath, entry) {
 | |
|             if (search(relativePath, entry)) { // the file matches the function
 | |
|                 result.push(entry);
 | |
|             }
 | |
| 
 | |
|         });
 | |
|         return result;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Add a file to the zip file, or search a file.
 | |
|      * @param   {string|RegExp} name The name of the file to add (if data is defined),
 | |
|      * the name of the file to find (if no data) or a regex to match files.
 | |
|      * @param   {String|ArrayBuffer|Uint8Array|Buffer} data  The file data, either raw or base64 encoded
 | |
|      * @param   {Object} o     File options
 | |
|      * @return  {JSZip|Object|Array} this JSZip object (when adding a file),
 | |
|      * a file (when searching by string) or an array of files (when searching by regex).
 | |
|      */
 | |
|     file: function(name, data, o) {
 | |
|         if (arguments.length === 1) {
 | |
|             if (isRegExp(name)) {
 | |
|                 var regexp = name;
 | |
|                 return this.filter(function(relativePath, file) {
 | |
|                     return !file.dir && regexp.test(relativePath);
 | |
|                 });
 | |
|             }
 | |
|             else { // text
 | |
|                 var obj = this.files[this.root + name];
 | |
|                 if (obj && !obj.dir) {
 | |
|                     return obj;
 | |
|                 } else {
 | |
|                     return null;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else { // more than one argument : we have data !
 | |
|             name = this.root + name;
 | |
|             fileAdd.call(this, name, data, o);
 | |
|         }
 | |
|         return this;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Add a directory to the zip file, or search.
 | |
|      * @param   {String|RegExp} arg The name of the directory to add, or a regex to search folders.
 | |
|      * @return  {JSZip} an object with the new directory as the root, or an array containing matching folders.
 | |
|      */
 | |
|     folder: function(arg) {
 | |
|         if (!arg) {
 | |
|             return this;
 | |
|         }
 | |
| 
 | |
|         if (isRegExp(arg)) {
 | |
|             return this.filter(function(relativePath, file) {
 | |
|                 return file.dir && arg.test(relativePath);
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // else, name is a new folder
 | |
|         var name = this.root + arg;
 | |
|         var newFolder = folderAdd.call(this, name);
 | |
| 
 | |
|         // Allow chaining by returning a new object with this folder as the root
 | |
|         var ret = this.clone();
 | |
|         ret.root = newFolder.name;
 | |
|         return ret;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Delete a file, or a directory and all sub-files, from the zip
 | |
|      * @param {string} name the name of the file to delete
 | |
|      * @return {JSZip} this JSZip object
 | |
|      */
 | |
|     remove: function(name) {
 | |
|         name = this.root + name;
 | |
|         var file = this.files[name];
 | |
|         if (!file) {
 | |
|             // Look for any folders
 | |
|             if (name.slice(-1) !== "/") {
 | |
|                 name += "/";
 | |
|             }
 | |
|             file = this.files[name];
 | |
|         }
 | |
| 
 | |
|         if (file && !file.dir) {
 | |
|             // file
 | |
|             delete this.files[name];
 | |
|         } else {
 | |
|             // maybe a folder, delete recursively
 | |
|             var kids = this.filter(function(relativePath, file) {
 | |
|                 return file.name.slice(0, name.length) === name;
 | |
|             });
 | |
|             for (var i = 0; i < kids.length; i++) {
 | |
|                 delete this.files[kids[i].name];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return this;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * @deprecated This method has been removed in JSZip 3.0, please check the upgrade guide.
 | |
|      */
 | |
|     generate: function() {
 | |
|         throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Generate the complete zip file as an internal stream.
 | |
|      * @param {Object} options the options to generate the zip file :
 | |
|      * - compression, "STORE" by default.
 | |
|      * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
 | |
|      * @return {StreamHelper} the streamed zip file.
 | |
|      */
 | |
|     generateInternalStream: function(options) {
 | |
|         var worker, opts = {};
 | |
|         try {
 | |
|             opts = utils.extend(options || {}, {
 | |
|                 streamFiles: false,
 | |
|                 compression: "STORE",
 | |
|                 compressionOptions : null,
 | |
|                 type: "",
 | |
|                 platform: "DOS",
 | |
|                 comment: null,
 | |
|                 mimeType: "application/zip",
 | |
|                 encodeFileName: utf8.utf8encode
 | |
|             });
 | |
| 
 | |
|             opts.type = opts.type.toLowerCase();
 | |
|             opts.compression = opts.compression.toUpperCase();
 | |
| 
 | |
|             // "binarystring" is preferred but the internals use "string".
 | |
|             if(opts.type === "binarystring") {
 | |
|                 opts.type = "string";
 | |
|             }
 | |
| 
 | |
|             if (!opts.type) {
 | |
|                 throw new Error("No output type specified.");
 | |
|             }
 | |
| 
 | |
|             utils.checkSupport(opts.type);
 | |
| 
 | |
|             // accept nodejs `process.platform`
 | |
|             if(
 | |
|                 opts.platform === "darwin" ||
 | |
|                 opts.platform === "freebsd" ||
 | |
|                 opts.platform === "linux" ||
 | |
|                 opts.platform === "sunos"
 | |
|             ) {
 | |
|                 opts.platform = "UNIX";
 | |
|             }
 | |
|             if (opts.platform === "win32") {
 | |
|                 opts.platform = "DOS";
 | |
|             }
 | |
| 
 | |
|             var comment = opts.comment || this.comment || "";
 | |
|             worker = generate.generateWorker(this, opts, comment);
 | |
|         } catch (e) {
 | |
|             worker = new GenericWorker("error");
 | |
|             worker.error(e);
 | |
|         }
 | |
|         return new StreamHelper(worker, opts.type || "string", opts.mimeType);
 | |
|     },
 | |
|     /**
 | |
|      * Generate the complete zip file asynchronously.
 | |
|      * @see generateInternalStream
 | |
|      */
 | |
|     generateAsync: function(options, onUpdate) {
 | |
|         return this.generateInternalStream(options).accumulate(onUpdate);
 | |
|     },
 | |
|     /**
 | |
|      * Generate the complete zip file asynchronously.
 | |
|      * @see generateInternalStream
 | |
|      */
 | |
|     generateNodeStream: function(options, onUpdate) {
 | |
|         options = options || {};
 | |
|         if (!options.type) {
 | |
|             options.type = "nodebuffer";
 | |
|         }
 | |
|         return this.generateInternalStream(options).toNodejsStream(onUpdate);
 | |
|     }
 | |
| };
 | |
| module.exports = out;
 |