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
			| 
											2 years ago
										 | "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; |