adm-zip.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. const Utils = require("./util");
  2. const pth = require("path");
  3. const ZipEntry = require("./zipEntry");
  4. const ZipFile = require("./zipFile");
  5. const get_Bool = (...val) => Utils.findLast(val, (c) => typeof c === "boolean");
  6. const get_Str = (...val) => Utils.findLast(val, (c) => typeof c === "string");
  7. const get_Fun = (...val) => Utils.findLast(val, (c) => typeof c === "function");
  8. const defaultOptions = {
  9. // option "noSort" : if true it disables files sorting
  10. noSort: false,
  11. // read entries during load (initial loading may be slower)
  12. readEntries: false,
  13. // default method is none
  14. method: Utils.Constants.NONE,
  15. // file system
  16. fs: null
  17. };
  18. module.exports = function (/**String*/ input, /** object */ options) {
  19. let inBuffer = null;
  20. // create object based default options, allowing them to be overwritten
  21. const opts = Object.assign(Object.create(null), defaultOptions);
  22. // test input variable
  23. if (input && "object" === typeof input) {
  24. // if value is not buffer we accept it to be object with options
  25. if (!(input instanceof Uint8Array)) {
  26. Object.assign(opts, input);
  27. input = opts.input ? opts.input : undefined;
  28. if (opts.input) delete opts.input;
  29. }
  30. // if input is buffer
  31. if (Buffer.isBuffer(input)) {
  32. inBuffer = input;
  33. opts.method = Utils.Constants.BUFFER;
  34. input = undefined;
  35. }
  36. }
  37. // assign options
  38. Object.assign(opts, options);
  39. // instanciate utils filesystem
  40. const filetools = new Utils(opts);
  41. if (typeof opts.decoder !== "object" || typeof opts.decoder.encode !== "function" || typeof opts.decoder.decode !== "function") {
  42. opts.decoder = Utils.decoder;
  43. }
  44. // if input is file name we retrieve its content
  45. if (input && "string" === typeof input) {
  46. // load zip file
  47. if (filetools.fs.existsSync(input)) {
  48. opts.method = Utils.Constants.FILE;
  49. opts.filename = input;
  50. inBuffer = filetools.fs.readFileSync(input);
  51. } else {
  52. throw Utils.Errors.INVALID_FILENAME();
  53. }
  54. }
  55. // create variable
  56. const _zip = new ZipFile(inBuffer, opts);
  57. const { canonical, sanitize, zipnamefix } = Utils;
  58. function getEntry(/**Object*/ entry) {
  59. if (entry && _zip) {
  60. var item;
  61. // If entry was given as a file name
  62. if (typeof entry === "string") item = _zip.getEntry(pth.posix.normalize(entry));
  63. // if entry was given as a ZipEntry object
  64. if (typeof entry === "object" && typeof entry.entryName !== "undefined" && typeof entry.header !== "undefined") item = _zip.getEntry(entry.entryName);
  65. if (item) {
  66. return item;
  67. }
  68. }
  69. return null;
  70. }
  71. function fixPath(zipPath) {
  72. const { join, normalize, sep } = pth.posix;
  73. // convert windows file separators and normalize
  74. return join(".", normalize(sep + zipPath.split("\\").join(sep) + sep));
  75. }
  76. function filenameFilter(filterfn) {
  77. if (filterfn instanceof RegExp) {
  78. // if filter is RegExp wrap it
  79. return (function (rx) {
  80. return function (filename) {
  81. return rx.test(filename);
  82. };
  83. })(filterfn);
  84. } else if ("function" !== typeof filterfn) {
  85. // if filter is not function we will replace it
  86. return () => true;
  87. }
  88. return filterfn;
  89. }
  90. // keep last character on folders
  91. const relativePath = (local, entry) => {
  92. let lastChar = entry.slice(-1);
  93. lastChar = lastChar === filetools.sep ? filetools.sep : "";
  94. return pth.relative(local, entry) + lastChar;
  95. };
  96. return {
  97. /**
  98. * Extracts the given entry from the archive and returns the content as a Buffer object
  99. * @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
  100. * @param {Buffer|string} [pass] - password
  101. * @return Buffer or Null in case of error
  102. */
  103. readFile: function (entry, pass) {
  104. var item = getEntry(entry);
  105. return (item && item.getData(pass)) || null;
  106. },
  107. /**
  108. * Returns how many child elements has on entry (directories) on files it is always 0
  109. * @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
  110. * @returns {integer}
  111. */
  112. childCount: function (entry) {
  113. const item = getEntry(entry);
  114. if (item) {
  115. return _zip.getChildCount(item);
  116. }
  117. },
  118. /**
  119. * Asynchronous readFile
  120. * @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
  121. * @param {callback} callback
  122. *
  123. * @return Buffer or Null in case of error
  124. */
  125. readFileAsync: function (entry, callback) {
  126. var item = getEntry(entry);
  127. if (item) {
  128. item.getDataAsync(callback);
  129. } else {
  130. callback(null, "getEntry failed for:" + entry);
  131. }
  132. },
  133. /**
  134. * Extracts the given entry from the archive and returns the content as plain text in the given encoding
  135. * @param {ZipEntry|string} entry - ZipEntry object or String with the full path of the entry
  136. * @param {string} encoding - Optional. If no encoding is specified utf8 is used
  137. *
  138. * @return String
  139. */
  140. readAsText: function (entry, encoding) {
  141. var item = getEntry(entry);
  142. if (item) {
  143. var data = item.getData();
  144. if (data && data.length) {
  145. return data.toString(encoding || "utf8");
  146. }
  147. }
  148. return "";
  149. },
  150. /**
  151. * Asynchronous readAsText
  152. * @param {ZipEntry|string} entry ZipEntry object or String with the full path of the entry
  153. * @param {callback} callback
  154. * @param {string} [encoding] - Optional. If no encoding is specified utf8 is used
  155. *
  156. * @return String
  157. */
  158. readAsTextAsync: function (entry, callback, encoding) {
  159. var item = getEntry(entry);
  160. if (item) {
  161. item.getDataAsync(function (data, err) {
  162. if (err) {
  163. callback(data, err);
  164. return;
  165. }
  166. if (data && data.length) {
  167. callback(data.toString(encoding || "utf8"));
  168. } else {
  169. callback("");
  170. }
  171. });
  172. } else {
  173. callback("");
  174. }
  175. },
  176. /**
  177. * Remove the entry from the file or the entry and all it's nested directories and files if the given entry is a directory
  178. *
  179. * @param {ZipEntry|string} entry
  180. * @returns {void}
  181. */
  182. deleteFile: function (entry, withsubfolders = true) {
  183. // @TODO: test deleteFile
  184. var item = getEntry(entry);
  185. if (item) {
  186. _zip.deleteFile(item.entryName, withsubfolders);
  187. }
  188. },
  189. /**
  190. * Remove the entry from the file or directory without affecting any nested entries
  191. *
  192. * @param {ZipEntry|string} entry
  193. * @returns {void}
  194. */
  195. deleteEntry: function (entry) {
  196. // @TODO: test deleteEntry
  197. var item = getEntry(entry);
  198. if (item) {
  199. _zip.deleteEntry(item.entryName);
  200. }
  201. },
  202. /**
  203. * Adds a comment to the zip. The zip must be rewritten after adding the comment.
  204. *
  205. * @param {string} comment
  206. */
  207. addZipComment: function (comment) {
  208. // @TODO: test addZipComment
  209. _zip.comment = comment;
  210. },
  211. /**
  212. * Returns the zip comment
  213. *
  214. * @return String
  215. */
  216. getZipComment: function () {
  217. return _zip.comment || "";
  218. },
  219. /**
  220. * Adds a comment to a specified zipEntry. The zip must be rewritten after adding the comment
  221. * The comment cannot exceed 65535 characters in length
  222. *
  223. * @param {ZipEntry} entry
  224. * @param {string} comment
  225. */
  226. addZipEntryComment: function (entry, comment) {
  227. var item = getEntry(entry);
  228. if (item) {
  229. item.comment = comment;
  230. }
  231. },
  232. /**
  233. * Returns the comment of the specified entry
  234. *
  235. * @param {ZipEntry} entry
  236. * @return String
  237. */
  238. getZipEntryComment: function (entry) {
  239. var item = getEntry(entry);
  240. if (item) {
  241. return item.comment || "";
  242. }
  243. return "";
  244. },
  245. /**
  246. * Updates the content of an existing entry inside the archive. The zip must be rewritten after updating the content
  247. *
  248. * @param {ZipEntry} entry
  249. * @param {Buffer} content
  250. */
  251. updateFile: function (entry, content) {
  252. var item = getEntry(entry);
  253. if (item) {
  254. item.setData(content);
  255. }
  256. },
  257. /**
  258. * Adds a file from the disk to the archive
  259. *
  260. * @param {string} localPath File to add to zip
  261. * @param {string} [zipPath] Optional path inside the zip
  262. * @param {string} [zipName] Optional name for the file
  263. * @param {string} [comment] Optional file comment
  264. */
  265. addLocalFile: function (localPath, zipPath, zipName, comment) {
  266. if (filetools.fs.existsSync(localPath)) {
  267. // fix ZipPath
  268. zipPath = zipPath ? fixPath(zipPath) : "";
  269. // p - local file name
  270. const p = pth.win32.basename(pth.win32.normalize(localPath));
  271. // add file name into zippath
  272. zipPath += zipName ? zipName : p;
  273. // read file attributes
  274. const _attr = filetools.fs.statSync(localPath);
  275. // get file content
  276. const data = _attr.isFile() ? filetools.fs.readFileSync(localPath) : Buffer.alloc(0);
  277. // if folder
  278. if (_attr.isDirectory()) zipPath += filetools.sep;
  279. // add file into zip file
  280. this.addFile(zipPath, data, comment, _attr);
  281. } else {
  282. throw Utils.Errors.FILE_NOT_FOUND(localPath);
  283. }
  284. },
  285. /**
  286. * Callback for showing if everything was done.
  287. *
  288. * @callback doneCallback
  289. * @param {Error} err - Error object
  290. * @param {boolean} done - was request fully completed
  291. */
  292. /**
  293. * Adds a file from the disk to the archive
  294. *
  295. * @param {(object|string)} options - options object, if it is string it us used as localPath.
  296. * @param {string} options.localPath - Local path to the file.
  297. * @param {string} [options.comment] - Optional file comment.
  298. * @param {string} [options.zipPath] - Optional path inside the zip
  299. * @param {string} [options.zipName] - Optional name for the file
  300. * @param {doneCallback} callback - The callback that handles the response.
  301. */
  302. addLocalFileAsync: function (options, callback) {
  303. options = typeof options === "object" ? options : { localPath: options };
  304. const localPath = pth.resolve(options.localPath);
  305. const { comment } = options;
  306. let { zipPath, zipName } = options;
  307. const self = this;
  308. filetools.fs.stat(localPath, function (err, stats) {
  309. if (err) return callback(err, false);
  310. // fix ZipPath
  311. zipPath = zipPath ? fixPath(zipPath) : "";
  312. // p - local file name
  313. const p = pth.win32.basename(pth.win32.normalize(localPath));
  314. // add file name into zippath
  315. zipPath += zipName ? zipName : p;
  316. if (stats.isFile()) {
  317. filetools.fs.readFile(localPath, function (err, data) {
  318. if (err) return callback(err, false);
  319. self.addFile(zipPath, data, comment, stats);
  320. return setImmediate(callback, undefined, true);
  321. });
  322. } else if (stats.isDirectory()) {
  323. zipPath += filetools.sep;
  324. self.addFile(zipPath, Buffer.alloc(0), comment, stats);
  325. return setImmediate(callback, undefined, true);
  326. }
  327. });
  328. },
  329. /**
  330. * Adds a local directory and all its nested files and directories to the archive
  331. *
  332. * @param {string} localPath - local path to the folder
  333. * @param {string} [zipPath] - optional path inside zip
  334. * @param {(RegExp|function)} [filter] - optional RegExp or Function if files match will be included.
  335. */
  336. addLocalFolder: function (localPath, zipPath, filter) {
  337. // Prepare filter
  338. filter = filenameFilter(filter);
  339. // fix ZipPath
  340. zipPath = zipPath ? fixPath(zipPath) : "";
  341. // normalize the path first
  342. localPath = pth.normalize(localPath);
  343. if (filetools.fs.existsSync(localPath)) {
  344. const items = filetools.findFiles(localPath);
  345. const self = this;
  346. if (items.length) {
  347. for (const filepath of items) {
  348. const p = pth.join(zipPath, relativePath(localPath, filepath));
  349. if (filter(p)) {
  350. self.addLocalFile(filepath, pth.dirname(p));
  351. }
  352. }
  353. }
  354. } else {
  355. throw Utils.Errors.FILE_NOT_FOUND(localPath);
  356. }
  357. },
  358. /**
  359. * Asynchronous addLocalFolder
  360. * @param {string} localPath
  361. * @param {callback} callback
  362. * @param {string} [zipPath] optional path inside zip
  363. * @param {RegExp|function} [filter] optional RegExp or Function if files match will
  364. * be included.
  365. */
  366. addLocalFolderAsync: function (localPath, callback, zipPath, filter) {
  367. // Prepare filter
  368. filter = filenameFilter(filter);
  369. // fix ZipPath
  370. zipPath = zipPath ? fixPath(zipPath) : "";
  371. // normalize the path first
  372. localPath = pth.normalize(localPath);
  373. var self = this;
  374. filetools.fs.open(localPath, "r", function (err) {
  375. if (err && err.code === "ENOENT") {
  376. callback(undefined, Utils.Errors.FILE_NOT_FOUND(localPath));
  377. } else if (err) {
  378. callback(undefined, err);
  379. } else {
  380. var items = filetools.findFiles(localPath);
  381. var i = -1;
  382. var next = function () {
  383. i += 1;
  384. if (i < items.length) {
  385. var filepath = items[i];
  386. var p = relativePath(localPath, filepath).split("\\").join("/"); //windows fix
  387. p = p
  388. .normalize("NFD")
  389. .replace(/[\u0300-\u036f]/g, "")
  390. .replace(/[^\x20-\x7E]/g, ""); // accent fix
  391. if (filter(p)) {
  392. filetools.fs.stat(filepath, function (er0, stats) {
  393. if (er0) callback(undefined, er0);
  394. if (stats.isFile()) {
  395. filetools.fs.readFile(filepath, function (er1, data) {
  396. if (er1) {
  397. callback(undefined, er1);
  398. } else {
  399. self.addFile(zipPath + p, data, "", stats);
  400. next();
  401. }
  402. });
  403. } else {
  404. self.addFile(zipPath + p + "/", Buffer.alloc(0), "", stats);
  405. next();
  406. }
  407. });
  408. } else {
  409. process.nextTick(() => {
  410. next();
  411. });
  412. }
  413. } else {
  414. callback(true, undefined);
  415. }
  416. };
  417. next();
  418. }
  419. });
  420. },
  421. /**
  422. * Adds a local directory and all its nested files and directories to the archive
  423. *
  424. * @param {object | string} options - options object, if it is string it us used as localPath.
  425. * @param {string} options.localPath - Local path to the folder.
  426. * @param {string} [options.zipPath] - optional path inside zip.
  427. * @param {RegExp|function} [options.filter] - optional RegExp or Function if files match will be included.
  428. * @param {function|string} [options.namefix] - optional function to help fix filename
  429. * @param {doneCallback} callback - The callback that handles the response.
  430. *
  431. */
  432. addLocalFolderAsync2: function (options, callback) {
  433. const self = this;
  434. options = typeof options === "object" ? options : { localPath: options };
  435. localPath = pth.resolve(fixPath(options.localPath));
  436. let { zipPath, filter, namefix } = options;
  437. if (filter instanceof RegExp) {
  438. filter = (function (rx) {
  439. return function (filename) {
  440. return rx.test(filename);
  441. };
  442. })(filter);
  443. } else if ("function" !== typeof filter) {
  444. filter = function () {
  445. return true;
  446. };
  447. }
  448. // fix ZipPath
  449. zipPath = zipPath ? fixPath(zipPath) : "";
  450. // Check Namefix function
  451. if (namefix == "latin1") {
  452. namefix = (str) =>
  453. str
  454. .normalize("NFD")
  455. .replace(/[\u0300-\u036f]/g, "")
  456. .replace(/[^\x20-\x7E]/g, ""); // accent fix (latin1 characers only)
  457. }
  458. if (typeof namefix !== "function") namefix = (str) => str;
  459. // internal, create relative path + fix the name
  460. const relPathFix = (entry) => pth.join(zipPath, namefix(relativePath(localPath, entry)));
  461. const fileNameFix = (entry) => pth.win32.basename(pth.win32.normalize(namefix(entry)));
  462. filetools.fs.open(localPath, "r", function (err) {
  463. if (err && err.code === "ENOENT") {
  464. callback(undefined, Utils.Errors.FILE_NOT_FOUND(localPath));
  465. } else if (err) {
  466. callback(undefined, err);
  467. } else {
  468. filetools.findFilesAsync(localPath, function (err, fileEntries) {
  469. if (err) return callback(err);
  470. fileEntries = fileEntries.filter((dir) => filter(relPathFix(dir)));
  471. if (!fileEntries.length) callback(undefined, false);
  472. setImmediate(
  473. fileEntries.reverse().reduce(function (next, entry) {
  474. return function (err, done) {
  475. if (err || done === false) return setImmediate(next, err, false);
  476. self.addLocalFileAsync(
  477. {
  478. localPath: entry,
  479. zipPath: pth.dirname(relPathFix(entry)),
  480. zipName: fileNameFix(entry)
  481. },
  482. next
  483. );
  484. };
  485. }, callback)
  486. );
  487. });
  488. }
  489. });
  490. },
  491. /**
  492. * Adds a local directory and all its nested files and directories to the archive
  493. *
  494. * @param {string} localPath - path where files will be extracted
  495. * @param {object} props - optional properties
  496. * @param {string} [props.zipPath] - optional path inside zip
  497. * @param {RegExp|function} [props.filter] - optional RegExp or Function if files match will be included.
  498. * @param {function|string} [props.namefix] - optional function to help fix filename
  499. */
  500. addLocalFolderPromise: function (localPath, props) {
  501. return new Promise((resolve, reject) => {
  502. this.addLocalFolderAsync2(Object.assign({ localPath }, props), (err, done) => {
  503. if (err) reject(err);
  504. if (done) resolve(this);
  505. });
  506. });
  507. },
  508. /**
  509. * Allows you to create a entry (file or directory) in the zip file.
  510. * If you want to create a directory the entryName must end in / and a null buffer should be provided.
  511. * Comment and attributes are optional
  512. *
  513. * @param {string} entryName
  514. * @param {Buffer | string} content - file content as buffer or utf8 coded string
  515. * @param {string} [comment] - file comment
  516. * @param {number | object} [attr] - number as unix file permissions, object as filesystem Stats object
  517. */
  518. addFile: function (entryName, content, comment, attr) {
  519. entryName = zipnamefix(entryName);
  520. let entry = getEntry(entryName);
  521. const update = entry != null;
  522. // prepare new entry
  523. if (!update) {
  524. entry = new ZipEntry(opts);
  525. entry.entryName = entryName;
  526. }
  527. entry.comment = comment || "";
  528. const isStat = "object" === typeof attr && attr instanceof filetools.fs.Stats;
  529. // last modification time from file stats
  530. if (isStat) {
  531. entry.header.time = attr.mtime;
  532. }
  533. // Set file attribute
  534. var fileattr = entry.isDirectory ? 0x10 : 0; // (MS-DOS directory flag)
  535. // extended attributes field for Unix
  536. // set file type either S_IFDIR / S_IFREG
  537. let unix = entry.isDirectory ? 0x4000 : 0x8000;
  538. if (isStat) {
  539. // File attributes from file stats
  540. unix |= 0xfff & attr.mode;
  541. } else if ("number" === typeof attr) {
  542. // attr from given attr values
  543. unix |= 0xfff & attr;
  544. } else {
  545. // Default values:
  546. unix |= entry.isDirectory ? 0o755 : 0o644; // permissions (drwxr-xr-x) or (-r-wr--r--)
  547. }
  548. fileattr = (fileattr | (unix << 16)) >>> 0; // add attributes
  549. entry.attr = fileattr;
  550. entry.setData(content);
  551. if (!update) _zip.setEntry(entry);
  552. return entry;
  553. },
  554. /**
  555. * Returns an array of ZipEntry objects representing the files and folders inside the archive
  556. *
  557. * @param {string} [password]
  558. * @returns Array
  559. */
  560. getEntries: function (password) {
  561. _zip.password = password;
  562. return _zip ? _zip.entries : [];
  563. },
  564. /**
  565. * Returns a ZipEntry object representing the file or folder specified by ``name``.
  566. *
  567. * @param {string} name
  568. * @return ZipEntry
  569. */
  570. getEntry: function (/**String*/ name) {
  571. return getEntry(name);
  572. },
  573. getEntryCount: function () {
  574. return _zip.getEntryCount();
  575. },
  576. forEach: function (callback) {
  577. return _zip.forEach(callback);
  578. },
  579. /**
  580. * Extracts the given entry to the given targetPath
  581. * If the entry is a directory inside the archive, the entire directory and it's subdirectories will be extracted
  582. *
  583. * @param {string|ZipEntry} entry - ZipEntry object or String with the full path of the entry
  584. * @param {string} targetPath - Target folder where to write the file
  585. * @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
  586. * @param {boolean} [overwrite=false] - If the file already exists at the target path, the file will be overwriten if this is true.
  587. * @param {boolean} [keepOriginalPermission=false] - The file will be set as the permission from the entry if this is true.
  588. * @param {string} [outFileName] - String If set will override the filename of the extracted file (Only works if the entry is a file)
  589. *
  590. * @return Boolean
  591. */
  592. extractEntryTo: function (entry, targetPath, maintainEntryPath, overwrite, keepOriginalPermission, outFileName) {
  593. overwrite = get_Bool(false, overwrite);
  594. keepOriginalPermission = get_Bool(false, keepOriginalPermission);
  595. maintainEntryPath = get_Bool(true, maintainEntryPath);
  596. outFileName = get_Str(keepOriginalPermission, outFileName);
  597. var item = getEntry(entry);
  598. if (!item) {
  599. throw Utils.Errors.NO_ENTRY();
  600. }
  601. var entryName = canonical(item.entryName);
  602. var target = sanitize(targetPath, outFileName && !item.isDirectory ? outFileName : maintainEntryPath ? entryName : pth.basename(entryName));
  603. if (item.isDirectory) {
  604. var children = _zip.getEntryChildren(item);
  605. children.forEach(function (child) {
  606. if (child.isDirectory) return;
  607. var content = child.getData();
  608. if (!content) {
  609. throw Utils.Errors.CANT_EXTRACT_FILE();
  610. }
  611. var name = canonical(child.entryName);
  612. var childName = sanitize(targetPath, maintainEntryPath ? name : pth.basename(name));
  613. // The reverse operation for attr depend on method addFile()
  614. const fileAttr = keepOriginalPermission ? child.header.fileAttr : undefined;
  615. filetools.writeFileTo(childName, content, overwrite, fileAttr);
  616. });
  617. return true;
  618. }
  619. var content = item.getData(_zip.password);
  620. if (!content) throw Utils.Errors.CANT_EXTRACT_FILE();
  621. if (filetools.fs.existsSync(target) && !overwrite) {
  622. throw Utils.Errors.CANT_OVERRIDE();
  623. }
  624. // The reverse operation for attr depend on method addFile()
  625. const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
  626. filetools.writeFileTo(target, content, overwrite, fileAttr);
  627. return true;
  628. },
  629. /**
  630. * Test the archive
  631. * @param {string} [pass]
  632. */
  633. test: function (pass) {
  634. if (!_zip) {
  635. return false;
  636. }
  637. for (var entry in _zip.entries) {
  638. try {
  639. if (entry.isDirectory) {
  640. continue;
  641. }
  642. var content = _zip.entries[entry].getData(pass);
  643. if (!content) {
  644. return false;
  645. }
  646. } catch (err) {
  647. return false;
  648. }
  649. }
  650. return true;
  651. },
  652. /**
  653. * Extracts the entire archive to the given location
  654. *
  655. * @param {string} targetPath Target location
  656. * @param {boolean} [overwrite=false] If the file already exists at the target path, the file will be overwriten if this is true.
  657. * Default is FALSE
  658. * @param {boolean} [keepOriginalPermission=false] The file will be set as the permission from the entry if this is true.
  659. * Default is FALSE
  660. * @param {string|Buffer} [pass] password
  661. */
  662. extractAllTo: function (targetPath, overwrite, keepOriginalPermission, pass) {
  663. keepOriginalPermission = get_Bool(false, keepOriginalPermission);
  664. pass = get_Str(keepOriginalPermission, pass);
  665. overwrite = get_Bool(false, overwrite);
  666. if (!_zip) throw Utils.Errors.NO_ZIP();
  667. _zip.entries.forEach(function (entry) {
  668. var entryName = sanitize(targetPath, canonical(entry.entryName));
  669. if (entry.isDirectory) {
  670. filetools.makeDir(entryName);
  671. return;
  672. }
  673. var content = entry.getData(pass);
  674. if (!content) {
  675. throw Utils.Errors.CANT_EXTRACT_FILE();
  676. }
  677. // The reverse operation for attr depend on method addFile()
  678. const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
  679. filetools.writeFileTo(entryName, content, overwrite, fileAttr);
  680. try {
  681. filetools.fs.utimesSync(entryName, entry.header.time, entry.header.time);
  682. } catch (err) {
  683. throw Utils.Errors.CANT_EXTRACT_FILE();
  684. }
  685. });
  686. },
  687. /**
  688. * Asynchronous extractAllTo
  689. *
  690. * @param {string} targetPath Target location
  691. * @param {boolean} [overwrite=false] If the file already exists at the target path, the file will be overwriten if this is true.
  692. * Default is FALSE
  693. * @param {boolean} [keepOriginalPermission=false] The file will be set as the permission from the entry if this is true.
  694. * Default is FALSE
  695. * @param {function} callback The callback will be executed when all entries are extracted successfully or any error is thrown.
  696. */
  697. extractAllToAsync: function (targetPath, overwrite, keepOriginalPermission, callback) {
  698. callback = get_Fun(overwrite, keepOriginalPermission, callback);
  699. keepOriginalPermission = get_Bool(false, keepOriginalPermission);
  700. overwrite = get_Bool(false, overwrite);
  701. if (!callback) {
  702. return new Promise((resolve, reject) => {
  703. this.extractAllToAsync(targetPath, overwrite, keepOriginalPermission, function (err) {
  704. if (err) {
  705. reject(err);
  706. } else {
  707. resolve(this);
  708. }
  709. });
  710. });
  711. }
  712. if (!_zip) {
  713. callback(Utils.Errors.NO_ZIP());
  714. return;
  715. }
  716. targetPath = pth.resolve(targetPath);
  717. // convert entryName to
  718. const getPath = (entry) => sanitize(targetPath, pth.normalize(canonical(entry.entryName)));
  719. const getError = (msg, file) => new Error(msg + ': "' + file + '"');
  720. // separate directories from files
  721. const dirEntries = [];
  722. const fileEntries = [];
  723. _zip.entries.forEach((e) => {
  724. if (e.isDirectory) {
  725. dirEntries.push(e);
  726. } else {
  727. fileEntries.push(e);
  728. }
  729. });
  730. // Create directory entries first synchronously
  731. // this prevents race condition and assures folders are there before writing files
  732. for (const entry of dirEntries) {
  733. const dirPath = getPath(entry);
  734. // The reverse operation for attr depend on method addFile()
  735. const dirAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
  736. try {
  737. filetools.makeDir(dirPath);
  738. if (dirAttr) filetools.fs.chmodSync(dirPath, dirAttr);
  739. // in unix timestamp will change if files are later added to folder, but still
  740. filetools.fs.utimesSync(dirPath, entry.header.time, entry.header.time);
  741. } catch (er) {
  742. callback(getError("Unable to create folder", dirPath));
  743. }
  744. }
  745. fileEntries.reverse().reduce(function (next, entry) {
  746. return function (err) {
  747. if (err) {
  748. next(err);
  749. } else {
  750. const entryName = pth.normalize(canonical(entry.entryName));
  751. const filePath = sanitize(targetPath, entryName);
  752. entry.getDataAsync(function (content, err_1) {
  753. if (err_1) {
  754. next(err_1);
  755. } else if (!content) {
  756. next(Utils.Errors.CANT_EXTRACT_FILE());
  757. } else {
  758. // The reverse operation for attr depend on method addFile()
  759. const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
  760. filetools.writeFileToAsync(filePath, content, overwrite, fileAttr, function (succ) {
  761. if (!succ) {
  762. next(getError("Unable to write file", filePath));
  763. }
  764. filetools.fs.utimes(filePath, entry.header.time, entry.header.time, function (err_2) {
  765. if (err_2) {
  766. next(getError("Unable to set times", filePath));
  767. } else {
  768. next();
  769. }
  770. });
  771. });
  772. }
  773. });
  774. }
  775. };
  776. }, callback)();
  777. },
  778. /**
  779. * 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
  780. *
  781. * @param {string} targetFileName
  782. * @param {function} callback
  783. */
  784. writeZip: function (targetFileName, callback) {
  785. if (arguments.length === 1) {
  786. if (typeof targetFileName === "function") {
  787. callback = targetFileName;
  788. targetFileName = "";
  789. }
  790. }
  791. if (!targetFileName && opts.filename) {
  792. targetFileName = opts.filename;
  793. }
  794. if (!targetFileName) return;
  795. var zipData = _zip.compressToBuffer();
  796. if (zipData) {
  797. var ok = filetools.writeFileTo(targetFileName, zipData, true);
  798. if (typeof callback === "function") callback(!ok ? new Error("failed") : null, "");
  799. }
  800. },
  801. /**
  802. *
  803. * @param {string} targetFileName
  804. * @param {object} [props]
  805. * @param {boolean} [props.overwrite=true] If the file already exists at the target path, the file will be overwriten if this is true.
  806. * @param {boolean} [props.perm] The file will be set as the permission from the entry if this is true.
  807. * @returns {Promise<void>}
  808. */
  809. writeZipPromise: function (/**String*/ targetFileName, /* object */ props) {
  810. const { overwrite, perm } = Object.assign({ overwrite: true }, props);
  811. return new Promise((resolve, reject) => {
  812. // find file name
  813. if (!targetFileName && opts.filename) targetFileName = opts.filename;
  814. if (!targetFileName) reject("ADM-ZIP: ZIP File Name Missing");
  815. this.toBufferPromise().then((zipData) => {
  816. const ret = (done) => (done ? resolve(done) : reject("ADM-ZIP: Wasn't able to write zip file"));
  817. filetools.writeFileToAsync(targetFileName, zipData, overwrite, perm, ret);
  818. }, reject);
  819. });
  820. },
  821. /**
  822. * @returns {Promise<Buffer>} A promise to the Buffer.
  823. */
  824. toBufferPromise: function () {
  825. return new Promise((resolve, reject) => {
  826. _zip.toAsyncBuffer(resolve, reject);
  827. });
  828. },
  829. /**
  830. * Returns the content of the entire zip file as a Buffer object
  831. *
  832. * @prop {function} [onSuccess]
  833. * @prop {function} [onFail]
  834. * @prop {function} [onItemStart]
  835. * @prop {function} [onItemEnd]
  836. * @returns {Buffer}
  837. */
  838. toBuffer: function (onSuccess, onFail, onItemStart, onItemEnd) {
  839. if (typeof onSuccess === "function") {
  840. _zip.toAsyncBuffer(onSuccess, onFail, onItemStart, onItemEnd);
  841. return null;
  842. }
  843. return _zip.compressToBuffer();
  844. }
  845. };
  846. };