949 lines
37 KiB
JavaScript
949 lines
37 KiB
JavaScript
const Utils = require("./util");
|
|
const pth = require("path");
|
|
const ZipEntry = require("./zipEntry");
|
|
const ZipFile = require("./zipFile");
|
|
|
|
const get_Bool = (...val) => Utils.findLast(val, (c) => typeof c === "boolean");
|
|
const get_Str = (...val) => Utils.findLast(val, (c) => typeof c === "string");
|
|
const get_Fun = (...val) => Utils.findLast(val, (c) => typeof c === "function");
|
|
|
|
const defaultOptions = {
|
|
// option "noSort" : if true it disables files sorting
|
|
noSort: false,
|
|
// read entries during load (initial loading may be slower)
|
|
readEntries: false,
|
|
// default method is none
|
|
method: Utils.Constants.NONE,
|
|
// file system
|
|
fs: null
|
|
};
|
|
|
|
module.exports = function (/**String*/ input, /** object */ options) {
|
|
let inBuffer = null;
|
|
|
|
// create object based default options, allowing them to be overwritten
|
|
const opts = Object.assign(Object.create(null), defaultOptions);
|
|
|
|
// test input variable
|
|
if (input && "object" === typeof input) {
|
|
// if value is not buffer we accept it to be object with options
|
|
if (!(input instanceof Uint8Array)) {
|
|
Object.assign(opts, input);
|
|
input = opts.input ? opts.input : undefined;
|
|
if (opts.input) delete opts.input;
|
|
}
|
|
|
|
// if input is buffer
|
|
if (Buffer.isBuffer(input)) {
|
|
inBuffer = input;
|
|
opts.method = Utils.Constants.BUFFER;
|
|
input = undefined;
|
|
}
|
|
}
|
|
|
|
// assign options
|
|
Object.assign(opts, options);
|
|
|
|
// instanciate utils filesystem
|
|
const filetools = new Utils(opts);
|
|
|
|
if (typeof opts.decoder !== "object" || typeof opts.decoder.encode !== "function" || typeof opts.decoder.decode !== "function") {
|
|
opts.decoder = Utils.decoder;
|
|
}
|
|
|
|
// if input is file name we retrieve its content
|
|
if (input && "string" === typeof input) {
|
|
// load zip file
|
|
if (filetools.fs.existsSync(input)) {
|
|
opts.method = Utils.Constants.FILE;
|
|
opts.filename = input;
|
|
inBuffer = filetools.fs.readFileSync(input);
|
|
} else {
|
|
throw Utils.Errors.INVALID_FILENAME();
|
|
}
|
|
}
|
|
|
|
// create variable
|
|
const _zip = new ZipFile(inBuffer, opts);
|
|
|
|
const { canonical, sanitize, zipnamefix } = Utils;
|
|
|
|
function getEntry(/**Object*/ entry) {
|
|
if (entry && _zip) {
|
|
var item;
|
|
// If entry was given as a file name
|
|
if (typeof entry === "string") item = _zip.getEntry(pth.posix.normalize(entry));
|
|
// if entry was given as a ZipEntry object
|
|
if (typeof entry === "object" && typeof entry.entryName !== "undefined" && typeof entry.header !== "undefined") item = _zip.getEntry(entry.entryName);
|
|
|
|
if (item) {
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function fixPath(zipPath) {
|
|
const { join, normalize, sep } = pth.posix;
|
|
// convert windows file separators and normalize
|
|
return join(".", normalize(sep + zipPath.split("\\").join(sep) + sep));
|
|
}
|
|
|
|
function filenameFilter(filterfn) {
|
|
if (filterfn instanceof RegExp) {
|
|
// if filter is RegExp wrap it
|
|
return (function (rx) {
|
|
return function (filename) {
|
|
return rx.test(filename);
|
|
};
|
|
})(filterfn);
|
|
} else if ("function" !== typeof filterfn) {
|
|
// if filter is not function we will replace it
|
|
return () => true;
|
|
}
|
|
return filterfn;
|
|
}
|
|
|
|
// keep last character on folders
|
|
const relativePath = (local, entry) => {
|
|
let lastChar = entry.slice(-1);
|
|
lastChar = lastChar === filetools.sep ? filetools.sep : "";
|
|
return pth.relative(local, entry) + lastChar;
|
|
};
|
|
|
|
return {
|
|
/**
|
|
* Extracts the given entry from the archive and returns the content as a Buffer object
|
|
* @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
|
|
* @param {Buffer|string} [pass] - password
|
|
* @return Buffer or Null in case of error
|
|
*/
|
|
readFile: function (entry, pass) {
|
|
var item = getEntry(entry);
|
|
return (item && item.getData(pass)) || null;
|
|
},
|
|
|
|
/**
|
|
* Returns how many child elements has on entry (directories) on files it is always 0
|
|
* @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
|
|
* @returns {integer}
|
|
*/
|
|
childCount: function (entry) {
|
|
const item = getEntry(entry);
|
|
if (item) {
|
|
return _zip.getChildCount(item);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Asynchronous readFile
|
|
* @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
|
|
* @param {callback} callback
|
|
*
|
|
* @return Buffer or Null in case of error
|
|
*/
|
|
readFileAsync: function (entry, callback) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
item.getDataAsync(callback);
|
|
} else {
|
|
callback(null, "getEntry failed for:" + entry);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Extracts the given entry from the archive and returns the content as plain text in the given encoding
|
|
* @param {ZipEntry|string} entry - ZipEntry object or String with the full path of the entry
|
|
* @param {string} encoding - Optional. If no encoding is specified utf8 is used
|
|
*
|
|
* @return String
|
|
*/
|
|
readAsText: function (entry, encoding) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
var data = item.getData();
|
|
if (data && data.length) {
|
|
return data.toString(encoding || "utf8");
|
|
}
|
|
}
|
|
return "";
|
|
},
|
|
|
|
/**
|
|
* Asynchronous readAsText
|
|
* @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
|
|
* @param {callback} callback
|
|
* @param {string} [encoding] - Optional. If no encoding is specified utf8 is used
|
|
*
|
|
* @return String
|
|
*/
|
|
readAsTextAsync: function (entry, callback, encoding) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
item.getDataAsync(function (data, err) {
|
|
if (err) {
|
|
callback(data, err);
|
|
return;
|
|
}
|
|
|
|
if (data && data.length) {
|
|
callback(data.toString(encoding || "utf8"));
|
|
} else {
|
|
callback("");
|
|
}
|
|
});
|
|
} else {
|
|
callback("");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove the entry from the file or the entry and all it's nested directories and files if the given entry is a directory
|
|
*
|
|
* @param {ZipEntry|string} entry
|
|
* @returns {void}
|
|
*/
|
|
deleteFile: function (entry, withsubfolders = true) {
|
|
// @TODO: test deleteFile
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
_zip.deleteFile(item.entryName, withsubfolders);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove the entry from the file or directory without affecting any nested entries
|
|
*
|
|
* @param {ZipEntry|string} entry
|
|
* @returns {void}
|
|
*/
|
|
deleteEntry: function (entry) {
|
|
// @TODO: test deleteEntry
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
_zip.deleteEntry(item.entryName);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a comment to the zip. The zip must be rewritten after adding the comment.
|
|
*
|
|
* @param {string} comment
|
|
*/
|
|
addZipComment: function (comment) {
|
|
// @TODO: test addZipComment
|
|
_zip.comment = comment;
|
|
},
|
|
|
|
/**
|
|
* Returns the zip comment
|
|
*
|
|
* @return String
|
|
*/
|
|
getZipComment: function () {
|
|
return _zip.comment || "";
|
|
},
|
|
|
|
/**
|
|
* Adds a comment to a specified zipEntry. The zip must be rewritten after adding the comment
|
|
* The comment cannot exceed 65535 characters in length
|
|
*
|
|
* @param {ZipEntry} entry
|
|
* @param {string} comment
|
|
*/
|
|
addZipEntryComment: function (entry, comment) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
item.comment = comment;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the comment of the specified entry
|
|
*
|
|
* @param {ZipEntry} entry
|
|
* @return String
|
|
*/
|
|
getZipEntryComment: function (entry) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
return item.comment || "";
|
|
}
|
|
return "";
|
|
},
|
|
|
|
/**
|
|
* Updates the content of an existing entry inside the archive. The zip must be rewritten after updating the content
|
|
*
|
|
* @param {ZipEntry} entry
|
|
* @param {Buffer} content
|
|
*/
|
|
updateFile: function (entry, content) {
|
|
var item = getEntry(entry);
|
|
if (item) {
|
|
item.setData(content);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a file from the disk to the archive
|
|
*
|
|
* @param {string} localPath File to add to zip
|
|
* @param {string} [zipPath] Optional path inside the zip
|
|
* @param {string} [zipName] Optional name for the file
|
|
* @param {string} [comment] Optional file comment
|
|
*/
|
|
addLocalFile: function (localPath, zipPath, zipName, comment) {
|
|
if (filetools.fs.existsSync(localPath)) {
|
|
// fix ZipPath
|
|
zipPath = zipPath ? fixPath(zipPath) : "";
|
|
|
|
// p - local file name
|
|
const p = pth.win32.basename(pth.win32.normalize(localPath));
|
|
|
|
// add file name into zippath
|
|
zipPath += zipName ? zipName : p;
|
|
|
|
// read file attributes
|
|
const _attr = filetools.fs.statSync(localPath);
|
|
|
|
// get file content
|
|
const data = _attr.isFile() ? filetools.fs.readFileSync(localPath) : Buffer.alloc(0);
|
|
|
|
// if folder
|
|
if (_attr.isDirectory()) zipPath += filetools.sep;
|
|
|
|
// add file into zip file
|
|
this.addFile(zipPath, data, comment, _attr);
|
|
} else {
|
|
throw Utils.Errors.FILE_NOT_FOUND(localPath);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback for showing if everything was done.
|
|
*
|
|
* @callback doneCallback
|
|
* @param {Error} err - Error object
|
|
* @param {boolean} done - was request fully completed
|
|
*/
|
|
|
|
/**
|
|
* Adds a file from the disk to the archive
|
|
*
|
|
* @param {(object|string)} options - options object, if it is string it us used as localPath.
|
|
* @param {string} options.localPath - Local path to the file.
|
|
* @param {string} [options.comment] - Optional file comment.
|
|
* @param {string} [options.zipPath] - Optional path inside the zip
|
|
* @param {string} [options.zipName] - Optional name for the file
|
|
* @param {doneCallback} callback - The callback that handles the response.
|
|
*/
|
|
addLocalFileAsync: function (options, callback) {
|
|
options = typeof options === "object" ? options : { localPath: options };
|
|
const localPath = pth.resolve(options.localPath);
|
|
const { comment } = options;
|
|
let { zipPath, zipName } = options;
|
|
const self = this;
|
|
|
|
filetools.fs.stat(localPath, function (err, stats) {
|
|
if (err) return callback(err, false);
|
|
// fix ZipPath
|
|
zipPath = zipPath ? fixPath(zipPath) : "";
|
|
// p - local file name
|
|
const p = pth.win32.basename(pth.win32.normalize(localPath));
|
|
// add file name into zippath
|
|
zipPath += zipName ? zipName : p;
|
|
|
|
if (stats.isFile()) {
|
|
filetools.fs.readFile(localPath, function (err, data) {
|
|
if (err) return callback(err, false);
|
|
self.addFile(zipPath, data, comment, stats);
|
|
return setImmediate(callback, undefined, true);
|
|
});
|
|
} else if (stats.isDirectory()) {
|
|
zipPath += filetools.sep;
|
|
self.addFile(zipPath, Buffer.alloc(0), comment, stats);
|
|
return setImmediate(callback, undefined, true);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Adds a local directory and all its nested files and directories to the archive
|
|
*
|
|
* @param {string} localPath - local path to the folder
|
|
* @param {string} [zipPath] - optional path inside zip
|
|
* @param {(RegExp|function)} [filter] - optional RegExp or Function if files match will be included.
|
|
*/
|
|
addLocalFolder: function (localPath, zipPath, filter) {
|
|
// Prepare filter
|
|
filter = filenameFilter(filter);
|
|
|
|
// fix ZipPath
|
|
zipPath = zipPath ? fixPath(zipPath) : "";
|
|
|
|
// normalize the path first
|
|
localPath = pth.normalize(localPath);
|
|
|
|
if (filetools.fs.existsSync(localPath)) {
|
|
const items = filetools.findFiles(localPath);
|
|
const self = this;
|
|
|
|
if (items.length) {
|
|
for (const filepath of items) {
|
|
const p = pth.join(zipPath, relativePath(localPath, filepath));
|
|
if (filter(p)) {
|
|
self.addLocalFile(filepath, pth.dirname(p));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw Utils.Errors.FILE_NOT_FOUND(localPath);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Asynchronous addLocalFolder
|
|
* @param {string} localPath
|
|
* @param {callback} callback
|
|
* @param {string} [zipPath] optional path inside zip
|
|
* @param {RegExp|function} [filter] optional RegExp or Function if files match will
|
|
* be included.
|
|
*/
|
|
addLocalFolderAsync: function (localPath, callback, zipPath, filter) {
|
|
// Prepare filter
|
|
filter = filenameFilter(filter);
|
|
|
|
// fix ZipPath
|
|
zipPath = zipPath ? fixPath(zipPath) : "";
|
|
|
|
// normalize the path first
|
|
localPath = pth.normalize(localPath);
|
|
|
|
var self = this;
|
|
filetools.fs.open(localPath, "r", function (err) {
|
|
if (err && err.code === "ENOENT") {
|
|
callback(undefined, Utils.Errors.FILE_NOT_FOUND(localPath));
|
|
} else if (err) {
|
|
callback(undefined, err);
|
|
} else {
|
|
var items = filetools.findFiles(localPath);
|
|
var i = -1;
|
|
|
|
var next = function () {
|
|
i += 1;
|
|
if (i < items.length) {
|
|
var filepath = items[i];
|
|
var p = relativePath(localPath, filepath).split("\\").join("/"); //windows fix
|
|
p = p
|
|
.normalize("NFD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^\x20-\x7E]/g, ""); // accent fix
|
|
if (filter(p)) {
|
|
filetools.fs.stat(filepath, function (er0, stats) {
|
|
if (er0) callback(undefined, er0);
|
|
if (stats.isFile()) {
|
|
filetools.fs.readFile(filepath, function (er1, data) {
|
|
if (er1) {
|
|
callback(undefined, er1);
|
|
} else {
|
|
self.addFile(zipPath + p, data, "", stats);
|
|
next();
|
|
}
|
|
});
|
|
} else {
|
|
self.addFile(zipPath + p + "/", Buffer.alloc(0), "", stats);
|
|
next();
|
|
}
|
|
});
|
|
} else {
|
|
process.nextTick(() => {
|
|
next();
|
|
});
|
|
}
|
|
} else {
|
|
callback(true, undefined);
|
|
}
|
|
};
|
|
|
|
next();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Adds a local directory and all its nested files and directories to the archive
|
|
*
|
|
* @param {object | string} options - options object, if it is string it us used as localPath.
|
|
* @param {string} options.localPath - Local path to the folder.
|
|
* @param {string} [options.zipPath] - optional path inside zip.
|
|
* @param {RegExp|function} [options.filter] - optional RegExp or Function if files match will be included.
|
|
* @param {function|string} [options.namefix] - optional function to help fix filename
|
|
* @param {doneCallback} callback - The callback that handles the response.
|
|
*
|
|
*/
|
|
addLocalFolderAsync2: function (options, callback) {
|
|
const self = this;
|
|
options = typeof options === "object" ? options : { localPath: options };
|
|
localPath = pth.resolve(fixPath(options.localPath));
|
|
let { zipPath, filter, namefix } = options;
|
|
|
|
if (filter instanceof RegExp) {
|
|
filter = (function (rx) {
|
|
return function (filename) {
|
|
return rx.test(filename);
|
|
};
|
|
})(filter);
|
|
} else if ("function" !== typeof filter) {
|
|
filter = function () {
|
|
return true;
|
|
};
|
|
}
|
|
|
|
// fix ZipPath
|
|
zipPath = zipPath ? fixPath(zipPath) : "";
|
|
|
|
// Check Namefix function
|
|
if (namefix == "latin1") {
|
|
namefix = (str) =>
|
|
str
|
|
.normalize("NFD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^\x20-\x7E]/g, ""); // accent fix (latin1 characers only)
|
|
}
|
|
|
|
if (typeof namefix !== "function") namefix = (str) => str;
|
|
|
|
// internal, create relative path + fix the name
|
|
const relPathFix = (entry) => pth.join(zipPath, namefix(relativePath(localPath, entry)));
|
|
const fileNameFix = (entry) => pth.win32.basename(pth.win32.normalize(namefix(entry)));
|
|
|
|
filetools.fs.open(localPath, "r", function (err) {
|
|
if (err && err.code === "ENOENT") {
|
|
callback(undefined, Utils.Errors.FILE_NOT_FOUND(localPath));
|
|
} else if (err) {
|
|
callback(undefined, err);
|
|
} else {
|
|
filetools.findFilesAsync(localPath, function (err, fileEntries) {
|
|
if (err) return callback(err);
|
|
fileEntries = fileEntries.filter((dir) => filter(relPathFix(dir)));
|
|
if (!fileEntries.length) callback(undefined, false);
|
|
|
|
setImmediate(
|
|
fileEntries.reverse().reduce(function (next, entry) {
|
|
return function (err, done) {
|
|
if (err || done === false) return setImmediate(next, err, false);
|
|
|
|
self.addLocalFileAsync(
|
|
{
|
|
localPath: entry,
|
|
zipPath: pth.dirname(relPathFix(entry)),
|
|
zipName: fileNameFix(entry)
|
|
},
|
|
next
|
|
);
|
|
};
|
|
}, callback)
|
|
);
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Adds a local directory and all its nested files and directories to the archive
|
|
*
|
|
* @param {string} localPath - path where files will be extracted
|
|
* @param {object} props - optional properties
|
|
* @param {string} [props.zipPath] - optional path inside zip
|
|
* @param {RegExp|function} [props.filter] - optional RegExp or Function if files match will be included.
|
|
* @param {function|string} [props.namefix] - optional function to help fix filename
|
|
*/
|
|
addLocalFolderPromise: function (localPath, props) {
|
|
return new Promise((resolve, reject) => {
|
|
this.addLocalFolderAsync2(Object.assign({ localPath }, props), (err, done) => {
|
|
if (err) reject(err);
|
|
if (done) resolve(this);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Allows you to create a entry (file or directory) in the zip file.
|
|
* If you want to create a directory the entryName must end in / and a null buffer should be provided.
|
|
* Comment and attributes are optional
|
|
*
|
|
* @param {string} entryName
|
|
* @param {Buffer | string} content - file content as buffer or utf8 coded string
|
|
* @param {string} [comment] - file comment
|
|
* @param {number | object} [attr] - number as unix file permissions, object as filesystem Stats object
|
|
*/
|
|
addFile: function (entryName, content, comment, attr) {
|
|
entryName = zipnamefix(entryName);
|
|
let entry = getEntry(entryName);
|
|
const update = entry != null;
|
|
|
|
// prepare new entry
|
|
if (!update) {
|
|
entry = new ZipEntry(opts);
|
|
entry.entryName = entryName;
|
|
}
|
|
entry.comment = comment || "";
|
|
|
|
const isStat = "object" === typeof attr && attr instanceof filetools.fs.Stats;
|
|
|
|
// last modification time from file stats
|
|
if (isStat) {
|
|
entry.header.time = attr.mtime;
|
|
}
|
|
|
|
// Set file attribute
|
|
var fileattr = entry.isDirectory ? 0x10 : 0; // (MS-DOS directory flag)
|
|
|
|
// extended attributes field for Unix
|
|
// set file type either S_IFDIR / S_IFREG
|
|
let unix = entry.isDirectory ? 0x4000 : 0x8000;
|
|
|
|
if (isStat) {
|
|
// File attributes from file stats
|
|
unix |= 0xfff & attr.mode;
|
|
} else if ("number" === typeof attr) {
|
|
// attr from given attr values
|
|
unix |= 0xfff & attr;
|
|
} else {
|
|
// Default values:
|
|
unix |= entry.isDirectory ? 0o755 : 0o644; // permissions (drwxr-xr-x) or (-r-wr--r--)
|
|
}
|
|
|
|
fileattr = (fileattr | (unix << 16)) >>> 0; // add attributes
|
|
|
|
entry.attr = fileattr;
|
|
|
|
entry.setData(content);
|
|
if (!update) _zip.setEntry(entry);
|
|
|
|
return entry;
|
|
},
|
|
|
|
/**
|
|
* Returns an array of ZipEntry objects representing the files and folders inside the archive
|
|
*
|
|
* @param {string} [password]
|
|
* @returns Array
|
|
*/
|
|
getEntries: function (password) {
|
|
_zip.password = password;
|
|
return _zip ? _zip.entries : [];
|
|
},
|
|
|
|
/**
|
|
* Returns a ZipEntry object representing the file or folder specified by ``name``.
|
|
*
|
|
* @param {string} name
|
|
* @return ZipEntry
|
|
*/
|
|
getEntry: function (/**String*/ name) {
|
|
return getEntry(name);
|
|
},
|
|
|
|
getEntryCount: function () {
|
|
return _zip.getEntryCount();
|
|
},
|
|
|
|
forEach: function (callback) {
|
|
return _zip.forEach(callback);
|
|
},
|
|
|
|
/**
|
|
* Extracts the given entry to the given targetPath
|
|
* If the entry is a directory inside the archive, the entire directory and it's subdirectories will be extracted
|
|
*
|
|
* @param {string|ZipEntry} entry - ZipEntry object or String with the full path of the entry
|
|
* @param {string} targetPath - Target folder where to write the file
|
|
* @param {boolean} [maintainEntryPath=true] - If maintainEntryPath is true and the entry is inside a folder, the entry folder will be created in targetPath as well. Default is TRUE
|
|
* @param {boolean} [overwrite=false] - If the file already exists at the target path, the file will be overwriten if this is true.
|
|
* @param {boolean} [keepOriginalPermission=false] - The file will be set as the permission from the entry if this is true.
|
|
* @param {string} [outFileName] - String If set will override the filename of the extracted file (Only works if the entry is a file)
|
|
*
|
|
* @return Boolean
|
|
*/
|
|
extractEntryTo: function (entry, targetPath, maintainEntryPath, overwrite, keepOriginalPermission, outFileName) {
|
|
overwrite = get_Bool(false, overwrite);
|
|
keepOriginalPermission = get_Bool(false, keepOriginalPermission);
|
|
maintainEntryPath = get_Bool(true, maintainEntryPath);
|
|
outFileName = get_Str(keepOriginalPermission, outFileName);
|
|
|
|
var item = getEntry(entry);
|
|
if (!item) {
|
|
throw Utils.Errors.NO_ENTRY();
|
|
}
|
|
|
|
var entryName = canonical(item.entryName);
|
|
|
|
var target = sanitize(targetPath, outFileName && !item.isDirectory ? outFileName : maintainEntryPath ? entryName : pth.basename(entryName));
|
|
|
|
if (item.isDirectory) {
|
|
var children = _zip.getEntryChildren(item);
|
|
children.forEach(function (child) {
|
|
if (child.isDirectory) return;
|
|
var content = child.getData();
|
|
if (!content) {
|
|
throw Utils.Errors.CANT_EXTRACT_FILE();
|
|
}
|
|
var name = canonical(child.entryName);
|
|
var childName = sanitize(targetPath, maintainEntryPath ? name : pth.basename(name));
|
|
// The reverse operation for attr depend on method addFile()
|
|
const fileAttr = keepOriginalPermission ? child.header.fileAttr : undefined;
|
|
filetools.writeFileTo(childName, content, overwrite, fileAttr);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
var content = item.getData(_zip.password);
|
|
if (!content) throw Utils.Errors.CANT_EXTRACT_FILE();
|
|
|
|
if (filetools.fs.existsSync(target) && !overwrite) {
|
|
throw Utils.Errors.CANT_OVERRIDE();
|
|
}
|
|
// The reverse operation for attr depend on method addFile()
|
|
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
|
|
filetools.writeFileTo(target, content, overwrite, fileAttr);
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Test the archive
|
|
* @param {string} [pass]
|
|
*/
|
|
test: function (pass) {
|
|
if (!_zip) {
|
|
return false;
|
|
}
|
|
|
|
for (var entry in _zip.entries) {
|
|
try {
|
|
if (entry.isDirectory) {
|
|
continue;
|
|
}
|
|
var content = _zip.entries[entry].getData(pass);
|
|
if (!content) {
|
|
return false;
|
|
}
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Extracts the entire archive to the given location
|
|
*
|
|
* @param {string} targetPath Target location
|
|
* @param {boolean} [overwrite=false] If the file already exists at the target path, the file will be overwriten if this is true.
|
|
* Default is FALSE
|
|
* @param {boolean} [keepOriginalPermission=false] The file will be set as the permission from the entry if this is true.
|
|
* Default is FALSE
|
|
* @param {string|Buffer} [pass] password
|
|
*/
|
|
extractAllTo: function (targetPath, overwrite, keepOriginalPermission, pass) {
|
|
keepOriginalPermission = get_Bool(false, keepOriginalPermission);
|
|
pass = get_Str(keepOriginalPermission, pass);
|
|
overwrite = get_Bool(false, overwrite);
|
|
if (!_zip) throw Utils.Errors.NO_ZIP();
|
|
|
|
_zip.entries.forEach(function (entry) {
|
|
var entryName = sanitize(targetPath, canonical(entry.entryName));
|
|
if (entry.isDirectory) {
|
|
filetools.makeDir(entryName);
|
|
return;
|
|
}
|
|
var content = entry.getData(pass);
|
|
if (!content) {
|
|
throw Utils.Errors.CANT_EXTRACT_FILE();
|
|
}
|
|
// The reverse operation for attr depend on method addFile()
|
|
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
|
|
filetools.writeFileTo(entryName, content, overwrite, fileAttr);
|
|
try {
|
|
filetools.fs.utimesSync(entryName, entry.header.time, entry.header.time);
|
|
} catch (err) {
|
|
throw Utils.Errors.CANT_EXTRACT_FILE();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Asynchronous extractAllTo
|
|
*
|
|
* @param {string} targetPath Target location
|
|
* @param {boolean} [overwrite=false] If the file already exists at the target path, the file will be overwriten if this is true.
|
|
* Default is FALSE
|
|
* @param {boolean} [keepOriginalPermission=false] The file will be set as the permission from the entry if this is true.
|
|
* Default is FALSE
|
|
* @param {function} callback The callback will be executed when all entries are extracted successfully or any error is thrown.
|
|
*/
|
|
extractAllToAsync: function (targetPath, overwrite, keepOriginalPermission, callback) {
|
|
callback = get_Fun(overwrite, keepOriginalPermission, callback);
|
|
keepOriginalPermission = get_Bool(false, keepOriginalPermission);
|
|
overwrite = get_Bool(false, overwrite);
|
|
if (!callback) {
|
|
return new Promise((resolve, reject) => {
|
|
this.extractAllToAsync(targetPath, overwrite, keepOriginalPermission, function (err) {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(this);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
if (!_zip) {
|
|
callback(Utils.Errors.NO_ZIP());
|
|
return;
|
|
}
|
|
|
|
targetPath = pth.resolve(targetPath);
|
|
// convert entryName to
|
|
const getPath = (entry) => sanitize(targetPath, pth.normalize(canonical(entry.entryName)));
|
|
const getError = (msg, file) => new Error(msg + ': "' + file + '"');
|
|
|
|
// separate directories from files
|
|
const dirEntries = [];
|
|
const fileEntries = [];
|
|
_zip.entries.forEach((e) => {
|
|
if (e.isDirectory) {
|
|
dirEntries.push(e);
|
|
} else {
|
|
fileEntries.push(e);
|
|
}
|
|
});
|
|
|
|
// Create directory entries first synchronously
|
|
// this prevents race condition and assures folders are there before writing files
|
|
for (const entry of dirEntries) {
|
|
const dirPath = getPath(entry);
|
|
// The reverse operation for attr depend on method addFile()
|
|
const dirAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
|
|
try {
|
|
filetools.makeDir(dirPath);
|
|
if (dirAttr) filetools.fs.chmodSync(dirPath, dirAttr);
|
|
// in unix timestamp will change if files are later added to folder, but still
|
|
filetools.fs.utimesSync(dirPath, entry.header.time, entry.header.time);
|
|
} catch (er) {
|
|
callback(getError("Unable to create folder", dirPath));
|
|
}
|
|
}
|
|
|
|
fileEntries.reverse().reduce(function (next, entry) {
|
|
return function (err) {
|
|
if (err) {
|
|
next(err);
|
|
} else {
|
|
const entryName = pth.normalize(canonical(entry.entryName));
|
|
const filePath = sanitize(targetPath, entryName);
|
|
entry.getDataAsync(function (content, err_1) {
|
|
if (err_1) {
|
|
next(err_1);
|
|
} else if (!content) {
|
|
next(Utils.Errors.CANT_EXTRACT_FILE());
|
|
} else {
|
|
// The reverse operation for attr depend on method addFile()
|
|
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
|
|
filetools.writeFileToAsync(filePath, content, overwrite, fileAttr, function (succ) {
|
|
if (!succ) {
|
|
next(getError("Unable to write file", filePath));
|
|
}
|
|
filetools.fs.utimes(filePath, entry.header.time, entry.header.time, function (err_2) {
|
|
if (err_2) {
|
|
next(getError("Unable to set times", filePath));
|
|
} else {
|
|
next();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}, callback)();
|
|
},
|
|
|
|
/**
|
|
* Writes the newly created zip file to disk at the specified location or if a zip was opened and no ``targetFileName`` is provided, it will overwrite the opened zip
|
|
*
|
|
* @param {string} targetFileName
|
|
* @param {function} callback
|
|
*/
|
|
writeZip: function (targetFileName, callback) {
|
|
if (arguments.length === 1) {
|
|
if (typeof targetFileName === "function") {
|
|
callback = targetFileName;
|
|
targetFileName = "";
|
|
}
|
|
}
|
|
|
|
if (!targetFileName && opts.filename) {
|
|
targetFileName = opts.filename;
|
|
}
|
|
if (!targetFileName) return;
|
|
|
|
var zipData = _zip.compressToBuffer();
|
|
if (zipData) {
|
|
var ok = filetools.writeFileTo(targetFileName, zipData, true);
|
|
if (typeof callback === "function") callback(!ok ? new Error("failed") : null, "");
|
|
}
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {string} targetFileName
|
|
* @param {object} [props]
|
|
* @param {boolean} [props.overwrite=true] If the file already exists at the target path, the file will be overwriten if this is true.
|
|
* @param {boolean} [props.perm] The file will be set as the permission from the entry if this is true.
|
|
|
|
* @returns {Promise<void>}
|
|
*/
|
|
writeZipPromise: function (/**String*/ targetFileName, /* object */ props) {
|
|
const { overwrite, perm } = Object.assign({ overwrite: true }, props);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
// find file name
|
|
if (!targetFileName && opts.filename) targetFileName = opts.filename;
|
|
if (!targetFileName) reject("ADM-ZIP: ZIP File Name Missing");
|
|
|
|
this.toBufferPromise().then((zipData) => {
|
|
const ret = (done) => (done ? resolve(done) : reject("ADM-ZIP: Wasn't able to write zip file"));
|
|
filetools.writeFileToAsync(targetFileName, zipData, overwrite, perm, ret);
|
|
}, reject);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @returns {Promise<Buffer>} A promise to the Buffer.
|
|
*/
|
|
toBufferPromise: function () {
|
|
return new Promise((resolve, reject) => {
|
|
_zip.toAsyncBuffer(resolve, reject);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns the content of the entire zip file as a Buffer object
|
|
*
|
|
* @prop {function} [onSuccess]
|
|
* @prop {function} [onFail]
|
|
* @prop {function} [onItemStart]
|
|
* @prop {function} [onItemEnd]
|
|
* @returns {Buffer}
|
|
*/
|
|
toBuffer: function (onSuccess, onFail, onItemStart, onItemEnd) {
|
|
if (typeof onSuccess === "function") {
|
|
_zip.toAsyncBuffer(onSuccess, onFail, onItemStart, onItemEnd);
|
|
return null;
|
|
}
|
|
return _zip.compressToBuffer();
|
|
}
|
|
};
|
|
};
|