adm-zip.js 30 KB

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