adm-zip.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. var Utils = require("./util");
  2. var fs = Utils.FileSystem.require(),
  3. pth = require("path");
  4. fs.existsSync = fs.existsSync || pth.existsSync;
  5. var ZipEntry = require("./zipEntry"),
  6. ZipFile = require("./zipFile");
  7. var isWin = /^win/.test(process.platform);
  8. module.exports = function (/*String*/input) {
  9. var _zip = undefined,
  10. _filename = "";
  11. if (input && typeof input === "string") { // load zip file
  12. if (fs.existsSync(input)) {
  13. _filename = input;
  14. _zip = new ZipFile(input, Utils.Constants.FILE);
  15. } else {
  16. throw Utils.Errors.INVALID_FILENAME;
  17. }
  18. } else if (input && Buffer.isBuffer(input)) { // load buffer
  19. _zip = new ZipFile(input, Utils.Constants.BUFFER);
  20. } else { // create new zip file
  21. _zip = new ZipFile(null, Utils.Constants.NONE);
  22. }
  23. function sanitize(prefix, name) {
  24. prefix = pth.resolve(pth.normalize(prefix));
  25. var parts = name.split('/');
  26. for (var i = 0, l = parts.length; i < l; i++) {
  27. var path = pth.normalize(pth.join(prefix, parts.slice(i, l).join(pth.sep)));
  28. if (path.indexOf(prefix) === 0) {
  29. return path;
  30. }
  31. }
  32. return pth.normalize(pth.join(prefix, pth.basename(name)));
  33. }
  34. function getEntry(/*Object*/entry) {
  35. if (entry && _zip) {
  36. var item;
  37. // If entry was given as a file name
  38. if (typeof entry === "string")
  39. item = _zip.getEntry(entry);
  40. // if entry was given as a ZipEntry object
  41. if (typeof entry === "object" && typeof entry.entryName !== "undefined" && typeof entry.header !== "undefined")
  42. item = _zip.getEntry(entry.entryName);
  43. if (item) {
  44. return item;
  45. }
  46. }
  47. return null;
  48. }
  49. return {
  50. /**
  51. * Extracts the given entry from the archive and returns the content as a Buffer object
  52. * @param entry ZipEntry object or String with the full path of the entry
  53. *
  54. * @return Buffer or Null in case of error
  55. */
  56. readFile: function (/*Object*/entry) {
  57. var item = getEntry(entry);
  58. return item && item.getData() || null;
  59. },
  60. /**
  61. * Asynchronous readFile
  62. * @param entry ZipEntry object or String with the full path of the entry
  63. * @param callback
  64. *
  65. * @return Buffer or Null in case of error
  66. */
  67. readFileAsync: function (/*Object*/entry, /*Function*/callback) {
  68. var item = getEntry(entry);
  69. if (item) {
  70. item.getDataAsync(callback);
  71. } else {
  72. callback(null, "getEntry failed for:" + entry)
  73. }
  74. },
  75. /**
  76. * Extracts the given entry from the archive and returns the content as plain text in the given encoding
  77. * @param entry ZipEntry object or String with the full path of the entry
  78. * @param encoding Optional. If no encoding is specified utf8 is used
  79. *
  80. * @return String
  81. */
  82. readAsText: function (/*Object*/entry, /*String - Optional*/encoding) {
  83. var item = getEntry(entry);
  84. if (item) {
  85. var data = item.getData();
  86. if (data && data.length) {
  87. return data.toString(encoding || "utf8");
  88. }
  89. }
  90. return "";
  91. },
  92. /**
  93. * Asynchronous readAsText
  94. * @param entry ZipEntry object or String with the full path of the entry
  95. * @param callback
  96. * @param encoding Optional. If no encoding is specified utf8 is used
  97. *
  98. * @return String
  99. */
  100. readAsTextAsync: function (/*Object*/entry, /*Function*/callback, /*String - Optional*/encoding) {
  101. var item = getEntry(entry);
  102. if (item) {
  103. item.getDataAsync(function (data) {
  104. if (data && data.length) {
  105. callback(data.toString(encoding || "utf8"));
  106. } else {
  107. callback("");
  108. }
  109. })
  110. } else {
  111. callback("");
  112. }
  113. },
  114. /**
  115. * Remove the entry from the file or the entry and all it's nested directories and files if the given entry is a directory
  116. *
  117. * @param entry
  118. */
  119. deleteFile: function (/*Object*/entry) { // @TODO: test deleteFile
  120. var item = getEntry(entry);
  121. if (item) {
  122. _zip.deleteEntry(item.entryName);
  123. }
  124. },
  125. /**
  126. * Adds a comment to the zip. The zip must be rewritten after adding the comment.
  127. *
  128. * @param comment
  129. */
  130. addZipComment: function (/*String*/comment) { // @TODO: test addZipComment
  131. _zip.comment = comment;
  132. },
  133. /**
  134. * Returns the zip comment
  135. *
  136. * @return String
  137. */
  138. getZipComment: function () {
  139. return _zip.comment || '';
  140. },
  141. /**
  142. * Adds a comment to a specified zipEntry. The zip must be rewritten after adding the comment
  143. * The comment cannot exceed 65535 characters in length
  144. *
  145. * @param entry
  146. * @param comment
  147. */
  148. addZipEntryComment: function (/*Object*/entry, /*String*/comment) {
  149. var item = getEntry(entry);
  150. if (item) {
  151. item.comment = comment;
  152. }
  153. },
  154. /**
  155. * Returns the comment of the specified entry
  156. *
  157. * @param entry
  158. * @return String
  159. */
  160. getZipEntryComment: function (/*Object*/entry) {
  161. var item = getEntry(entry);
  162. if (item) {
  163. return item.comment || '';
  164. }
  165. return ''
  166. },
  167. /**
  168. * Updates the content of an existing entry inside the archive. The zip must be rewritten after updating the content
  169. *
  170. * @param entry
  171. * @param content
  172. */
  173. updateFile: function (/*Object*/entry, /*Buffer*/content) {
  174. var item = getEntry(entry);
  175. if (item) {
  176. item.setData(content);
  177. }
  178. },
  179. /**
  180. * Adds a file from the disk to the archive
  181. *
  182. * @param localPath File to add to zip
  183. * @param zipPath Optional path inside the zip
  184. * @param zipName Optional name for the file
  185. */
  186. addLocalFile: function (/*String*/localPath, /*String*/zipPath, /*String*/zipName) {
  187. if (fs.existsSync(localPath)) {
  188. if (zipPath) {
  189. zipPath = zipPath.split("\\").join("/");
  190. if (zipPath.charAt(zipPath.length - 1) !== "/") {
  191. zipPath += "/";
  192. }
  193. } else {
  194. zipPath = "";
  195. }
  196. var p = localPath.split("\\").join("/").split("/").pop();
  197. if (zipName) {
  198. this.addFile(zipPath + zipName, fs.readFileSync(localPath), "", 0)
  199. } else {
  200. this.addFile(zipPath + p, fs.readFileSync(localPath), "", 0)
  201. }
  202. } else {
  203. throw Utils.Errors.FILE_NOT_FOUND.replace("%s", localPath);
  204. }
  205. },
  206. /**
  207. * Adds a local directory and all its nested files and directories to the archive
  208. *
  209. * @param localPath
  210. * @param zipPath optional path inside zip
  211. * @param filter optional RegExp or Function if files match will
  212. * be included.
  213. */
  214. addLocalFolder: function (/*String*/localPath, /*String*/zipPath, /*RegExp|Function*/filter) {
  215. if (filter === undefined) {
  216. filter = function () {
  217. return true;
  218. };
  219. } else if (filter instanceof RegExp) {
  220. filter = function (filter) {
  221. return function (filename) {
  222. return filter.test(filename);
  223. }
  224. }(filter);
  225. }
  226. if (zipPath) {
  227. zipPath = zipPath.split("\\").join("/");
  228. if (zipPath.charAt(zipPath.length - 1) !== "/") {
  229. zipPath += "/";
  230. }
  231. } else {
  232. zipPath = "";
  233. }
  234. // normalize the path first
  235. localPath = pth.normalize(localPath);
  236. localPath = localPath.split("\\").join("/"); //windows fix
  237. if (localPath.charAt(localPath.length - 1) !== "/")
  238. localPath += "/";
  239. if (fs.existsSync(localPath)) {
  240. var items = Utils.findFiles(localPath),
  241. self = this;
  242. if (items.length) {
  243. items.forEach(function (path) {
  244. var p = path.split("\\").join("/").replace(new RegExp(localPath.replace(/(\(|\))/g, '\\$1'), 'i'), ""); //windows fix
  245. if (filter(p)) {
  246. if (p.charAt(p.length - 1) !== "/") {
  247. self.addFile(zipPath + p, fs.readFileSync(path), "", 0)
  248. } else {
  249. self.addFile(zipPath + p, Buffer.alloc(0), "", 0)
  250. }
  251. }
  252. });
  253. }
  254. } else {
  255. throw Utils.Errors.FILE_NOT_FOUND.replace("%s", localPath);
  256. }
  257. },
  258. /**
  259. * Allows you to create a entry (file or directory) in the zip file.
  260. * If you want to create a directory the entryName must end in / and a null buffer should be provided.
  261. * Comment and attributes are optional
  262. *
  263. * @param entryName
  264. * @param content
  265. * @param comment
  266. * @param attr
  267. */
  268. addFile: function (/*String*/entryName, /*Buffer*/content, /*String*/comment, /*Number*/attr) {
  269. var entry = new ZipEntry();
  270. entry.entryName = entryName;
  271. entry.comment = comment || "";
  272. if (!attr) {
  273. if (entry.isDirectory) {
  274. attr = (0o40755 << 16) | 0x10; // (permissions drwxr-xr-x) + (MS-DOS directory flag)
  275. } else {
  276. attr = 0o644 << 16; // permissions -r-wr--r--
  277. }
  278. }
  279. entry.attr = attr;
  280. entry.setData(content);
  281. _zip.setEntry(entry);
  282. },
  283. /**
  284. * Returns an array of ZipEntry objects representing the files and folders inside the archive
  285. *
  286. * @return Array
  287. */
  288. getEntries: function () {
  289. if (_zip) {
  290. return _zip.entries;
  291. } else {
  292. return [];
  293. }
  294. },
  295. /**
  296. * Returns a ZipEntry object representing the file or folder specified by ``name``.
  297. *
  298. * @param name
  299. * @return ZipEntry
  300. */
  301. getEntry: function (/*String*/name) {
  302. return getEntry(name);
  303. },
  304. /**
  305. * Extracts the given entry to the given targetPath
  306. * If the entry is a directory inside the archive, the entire directory and it's subdirectories will be extracted
  307. *
  308. * @param entry ZipEntry object or String with the full path of the entry
  309. * @param targetPath Target folder where to write the file
  310. * @param maintainEntryPath If maintainEntryPath is true and the entry is inside a folder, the entry folder
  311. * will be created in targetPath as well. Default is TRUE
  312. * @param overwrite If the file already exists at the target path, the file will be overwriten if this is true.
  313. * Default is FALSE
  314. *
  315. * @return Boolean
  316. */
  317. extractEntryTo: function (/*Object*/entry, /*String*/targetPath, /*Boolean*/maintainEntryPath, /*Boolean*/overwrite) {
  318. overwrite = overwrite || false;
  319. maintainEntryPath = typeof maintainEntryPath === "undefined" ? true : maintainEntryPath;
  320. var item = getEntry(entry);
  321. if (!item) {
  322. throw Utils.Errors.NO_ENTRY;
  323. }
  324. var entryName = item.entryName;
  325. var target = sanitize(targetPath, pth.resolve(targetPath, maintainEntryPath ? entryName : pth.basename(entryName)));
  326. if (item.isDirectory) {
  327. target = pth.resolve(target, "..");
  328. var children = _zip.getEntryChildren(item);
  329. children.forEach(function (child) {
  330. if (child.isDirectory) return;
  331. var content = child.getData();
  332. if (!content) {
  333. throw Utils.Errors.CANT_EXTRACT_FILE;
  334. }
  335. var childName = sanitize(targetPath, child.entryName);
  336. Utils.writeFileTo(pth.resolve(targetPath, maintainEntryPath ? childName : childName.substr(entryName.length)), content, overwrite);
  337. });
  338. return true;
  339. }
  340. var content = item.getData();
  341. if (!content) throw Utils.Errors.CANT_EXTRACT_FILE;
  342. if (fs.existsSync(target) && !overwrite) {
  343. throw Utils.Errors.CANT_OVERRIDE;
  344. }
  345. Utils.writeFileTo(target, content, overwrite);
  346. return true;
  347. },
  348. /**
  349. * Test the archive
  350. *
  351. */
  352. test: function () {
  353. if (!_zip) {
  354. return false;
  355. }
  356. for (var entry in _zip.entries) {
  357. try {
  358. if (entry.isDirectory) {
  359. continue;
  360. }
  361. var content = _zip.entries[entry].getData();
  362. if (!content) {
  363. return false;
  364. }
  365. } catch (err) {
  366. return false;
  367. }
  368. }
  369. return true;
  370. },
  371. /**
  372. * Extracts the entire archive to the given location
  373. *
  374. * @param targetPath Target location
  375. * @param overwrite If the file already exists at the target path, the file will be overwriten if this is true.
  376. * Default is FALSE
  377. */
  378. extractAllTo: function (/*String*/targetPath, /*Boolean*/overwrite) {
  379. overwrite = overwrite || false;
  380. if (!_zip) {
  381. throw Utils.Errors.NO_ZIP;
  382. }
  383. _zip.entries.forEach(function (entry) {
  384. var entryName = sanitize(targetPath, entry.entryName.toString());
  385. if (entry.isDirectory) {
  386. Utils.makeDir(entryName);
  387. return;
  388. }
  389. var content = entry.getData();
  390. if (!content) {
  391. throw Utils.Errors.CANT_EXTRACT_FILE;
  392. }
  393. Utils.writeFileTo(entryName, content, overwrite);
  394. fs.utimesSync(entryName, entry.header.time, entry.header.time)
  395. })
  396. },
  397. /**
  398. * Asynchronous extractAllTo
  399. *
  400. * @param targetPath Target location
  401. * @param overwrite If the file already exists at the target path, the file will be overwriten if this is true.
  402. * Default is FALSE
  403. * @param callback
  404. */
  405. extractAllToAsync: function (/*String*/targetPath, /*Boolean*/overwrite, /*Function*/callback) {
  406. if (!callback) {
  407. callback = function() {}
  408. }
  409. overwrite = overwrite || false;
  410. if (!_zip) {
  411. callback(new Error(Utils.Errors.NO_ZIP));
  412. return;
  413. }
  414. var entries = _zip.entries;
  415. var i = entries.length;
  416. entries.forEach(function (entry) {
  417. if (i <= 0) return; // Had an error already
  418. var entryName = pth.normalize(entry.entryName.toString());
  419. if (entry.isDirectory) {
  420. Utils.makeDir(sanitize(targetPath, entryName));
  421. if (--i === 0)
  422. callback(undefined);
  423. return;
  424. }
  425. entry.getDataAsync(function (content) {
  426. if (i <= 0) return;
  427. if (!content) {
  428. i = 0;
  429. callback(new Error(Utils.Errors.CANT_EXTRACT_FILE));
  430. return;
  431. }
  432. Utils.writeFileToAsync(sanitize(targetPath, entryName), content, overwrite, function (succ) {
  433. fs.utimesSync(pth.resolve(targetPath, entryName), entry.header.time, entry.header.time);
  434. if (i <= 0) return;
  435. if (!succ) {
  436. i = 0;
  437. callback(new Error('Unable to write'));
  438. return;
  439. }
  440. if (--i === 0)
  441. callback(undefined);
  442. });
  443. });
  444. })
  445. },
  446. /**
  447. * 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
  448. *
  449. * @param targetFileName
  450. * @param callback
  451. */
  452. writeZip: function (/*String*/targetFileName, /*Function*/callback) {
  453. if (arguments.length === 1) {
  454. if (typeof targetFileName === "function") {
  455. callback = targetFileName;
  456. targetFileName = "";
  457. }
  458. }
  459. if (!targetFileName && _filename) {
  460. targetFileName = _filename;
  461. }
  462. if (!targetFileName) return;
  463. var zipData = _zip.compressToBuffer();
  464. if (zipData) {
  465. var ok = Utils.writeFileTo(targetFileName, zipData, true);
  466. if (typeof callback === 'function') callback(!ok ? new Error("failed") : null, "");
  467. }
  468. },
  469. /**
  470. * Returns the content of the entire zip file as a Buffer object
  471. *
  472. * @return Buffer
  473. */
  474. toBuffer: function (/*Function*/onSuccess, /*Function*/onFail, /*Function*/onItemStart, /*Function*/onItemEnd) {
  475. this.valueOf = 2;
  476. if (typeof onSuccess === "function") {
  477. _zip.toAsyncBuffer(onSuccess, onFail, onItemStart, onItemEnd);
  478. return null;
  479. }
  480. return _zip.compressToBuffer()
  481. }
  482. }
  483. };