zipEntry.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. var Utils = require("./util"),
  2. Headers = require("./headers"),
  3. Constants = Utils.Constants,
  4. Methods = require("./methods");
  5. module.exports = function (/*Buffer*/ input) {
  6. var _entryHeader = new Headers.EntryHeader(),
  7. _entryName = Buffer.alloc(0),
  8. _comment = Buffer.alloc(0),
  9. _isDirectory = false,
  10. uncompressedData = null,
  11. _extra = Buffer.alloc(0);
  12. function getCompressedDataFromZip() {
  13. if (!input || !Buffer.isBuffer(input)) {
  14. return Buffer.alloc(0);
  15. }
  16. _entryHeader.loadDataHeaderFromBinary(input);
  17. return input.slice(_entryHeader.realDataOffset, _entryHeader.realDataOffset + _entryHeader.compressedSize);
  18. }
  19. function crc32OK(data) {
  20. // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written
  21. if ((_entryHeader.flags & 0x8) !== 0x8) {
  22. if (Utils.crc32(data) !== _entryHeader.dataHeader.crc) {
  23. return false;
  24. }
  25. } else {
  26. // @TODO: load and check data descriptor header
  27. // The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure
  28. // (optionally preceded by a 4-byte signature) immediately after the compressed data:
  29. }
  30. return true;
  31. }
  32. function decompress(/*Boolean*/ async, /*Function*/ callback, /*String, Buffer*/ pass) {
  33. if (typeof callback === "undefined" && typeof async === "string") {
  34. pass = async;
  35. async = void 0;
  36. }
  37. if (_isDirectory) {
  38. if (async && callback) {
  39. callback(Buffer.alloc(0), Utils.Errors.DIRECTORY_CONTENT_ERROR); //si added error.
  40. }
  41. return Buffer.alloc(0);
  42. }
  43. var compressedData = getCompressedDataFromZip();
  44. if (compressedData.length === 0) {
  45. // File is empty, nothing to decompress.
  46. if (async && callback) callback(compressedData);
  47. return compressedData;
  48. }
  49. if (_entryHeader.encripted) {
  50. if ("string" !== typeof pass && !Buffer.isBuffer(pass)) {
  51. throw new Error("ADM-ZIP: Incompatible password parameter");
  52. }
  53. compressedData = Methods.ZipCrypto.decrypt(compressedData, _entryHeader, pass);
  54. }
  55. var data = Buffer.alloc(_entryHeader.size);
  56. switch (_entryHeader.method) {
  57. case Utils.Constants.STORED:
  58. compressedData.copy(data);
  59. if (!crc32OK(data)) {
  60. if (async && callback) callback(data, Utils.Errors.BAD_CRC); //si added error
  61. throw new Error(Utils.Errors.BAD_CRC);
  62. } else {
  63. //si added otherwise did not seem to return data.
  64. if (async && callback) callback(data);
  65. return data;
  66. }
  67. case Utils.Constants.DEFLATED:
  68. var inflater = new Methods.Inflater(compressedData);
  69. if (!async) {
  70. const result = inflater.inflate(data);
  71. result.copy(data, 0);
  72. if (!crc32OK(data)) {
  73. throw new Error(Utils.Errors.BAD_CRC + " " + _entryName.toString());
  74. }
  75. return data;
  76. } else {
  77. inflater.inflateAsync(function (result) {
  78. result.copy(result, 0);
  79. if (callback) {
  80. if (!crc32OK(result)) {
  81. callback(result, Utils.Errors.BAD_CRC); //si added error
  82. } else {
  83. callback(result);
  84. }
  85. }
  86. });
  87. }
  88. break;
  89. default:
  90. if (async && callback) callback(Buffer.alloc(0), Utils.Errors.UNKNOWN_METHOD);
  91. throw new Error(Utils.Errors.UNKNOWN_METHOD);
  92. }
  93. }
  94. function compress(/*Boolean*/ async, /*Function*/ callback) {
  95. if ((!uncompressedData || !uncompressedData.length) && Buffer.isBuffer(input)) {
  96. // no data set or the data wasn't changed to require recompression
  97. if (async && callback) callback(getCompressedDataFromZip());
  98. return getCompressedDataFromZip();
  99. }
  100. if (uncompressedData.length && !_isDirectory) {
  101. var compressedData;
  102. // Local file header
  103. switch (_entryHeader.method) {
  104. case Utils.Constants.STORED:
  105. _entryHeader.compressedSize = _entryHeader.size;
  106. compressedData = Buffer.alloc(uncompressedData.length);
  107. uncompressedData.copy(compressedData);
  108. if (async && callback) callback(compressedData);
  109. return compressedData;
  110. default:
  111. case Utils.Constants.DEFLATED:
  112. var deflater = new Methods.Deflater(uncompressedData);
  113. if (!async) {
  114. var deflated = deflater.deflate();
  115. _entryHeader.compressedSize = deflated.length;
  116. return deflated;
  117. } else {
  118. deflater.deflateAsync(function (data) {
  119. compressedData = Buffer.alloc(data.length);
  120. _entryHeader.compressedSize = data.length;
  121. data.copy(compressedData);
  122. callback && callback(compressedData);
  123. });
  124. }
  125. deflater = null;
  126. break;
  127. }
  128. } else if (async && callback) {
  129. callback(Buffer.alloc(0));
  130. } else {
  131. return Buffer.alloc(0);
  132. }
  133. }
  134. function readUInt64LE(buffer, offset) {
  135. return (buffer.readUInt32LE(offset + 4) << 4) + buffer.readUInt32LE(offset);
  136. }
  137. function parseExtra(data) {
  138. var offset = 0;
  139. var signature, size, part;
  140. while (offset < data.length) {
  141. signature = data.readUInt16LE(offset);
  142. offset += 2;
  143. size = data.readUInt16LE(offset);
  144. offset += 2;
  145. part = data.slice(offset, offset + size);
  146. offset += size;
  147. if (Constants.ID_ZIP64 === signature) {
  148. parseZip64ExtendedInformation(part);
  149. }
  150. }
  151. }
  152. //Override header field values with values from the ZIP64 extra field
  153. function parseZip64ExtendedInformation(data) {
  154. var size, compressedSize, offset, diskNumStart;
  155. if (data.length >= Constants.EF_ZIP64_SCOMP) {
  156. size = readUInt64LE(data, Constants.EF_ZIP64_SUNCOMP);
  157. if (_entryHeader.size === Constants.EF_ZIP64_OR_32) {
  158. _entryHeader.size = size;
  159. }
  160. }
  161. if (data.length >= Constants.EF_ZIP64_RHO) {
  162. compressedSize = readUInt64LE(data, Constants.EF_ZIP64_SCOMP);
  163. if (_entryHeader.compressedSize === Constants.EF_ZIP64_OR_32) {
  164. _entryHeader.compressedSize = compressedSize;
  165. }
  166. }
  167. if (data.length >= Constants.EF_ZIP64_DSN) {
  168. offset = readUInt64LE(data, Constants.EF_ZIP64_RHO);
  169. if (_entryHeader.offset === Constants.EF_ZIP64_OR_32) {
  170. _entryHeader.offset = offset;
  171. }
  172. }
  173. if (data.length >= Constants.EF_ZIP64_DSN + 4) {
  174. diskNumStart = data.readUInt32LE(Constants.EF_ZIP64_DSN);
  175. if (_entryHeader.diskNumStart === Constants.EF_ZIP64_OR_16) {
  176. _entryHeader.diskNumStart = diskNumStart;
  177. }
  178. }
  179. }
  180. return {
  181. get entryName() {
  182. return _entryName.toString();
  183. },
  184. get rawEntryName() {
  185. return _entryName;
  186. },
  187. set entryName(val) {
  188. _entryName = Utils.toBuffer(val);
  189. var lastChar = _entryName[_entryName.length - 1];
  190. _isDirectory = lastChar === 47 || lastChar === 92;
  191. _entryHeader.fileNameLength = _entryName.length;
  192. },
  193. get extra() {
  194. return _extra;
  195. },
  196. set extra(val) {
  197. _extra = val;
  198. _entryHeader.extraLength = val.length;
  199. parseExtra(val);
  200. },
  201. get comment() {
  202. return _comment.toString();
  203. },
  204. set comment(val) {
  205. _comment = Utils.toBuffer(val);
  206. _entryHeader.commentLength = _comment.length;
  207. },
  208. get name() {
  209. var n = _entryName.toString();
  210. return _isDirectory
  211. ? n
  212. .substr(n.length - 1)
  213. .split("/")
  214. .pop()
  215. : n.split("/").pop();
  216. },
  217. get isDirectory() {
  218. return _isDirectory;
  219. },
  220. getCompressedData: function () {
  221. return compress(false, null);
  222. },
  223. getCompressedDataAsync: function (/*Function*/ callback) {
  224. compress(true, callback);
  225. },
  226. setData: function (value) {
  227. uncompressedData = Utils.toBuffer(value);
  228. if (!_isDirectory && uncompressedData.length) {
  229. _entryHeader.size = uncompressedData.length;
  230. _entryHeader.method = Utils.Constants.DEFLATED;
  231. _entryHeader.crc = Utils.crc32(value);
  232. _entryHeader.changed = true;
  233. } else {
  234. // folders and blank files should be stored
  235. _entryHeader.method = Utils.Constants.STORED;
  236. }
  237. },
  238. getData: function (pass) {
  239. if (_entryHeader.changed) {
  240. return uncompressedData;
  241. } else {
  242. return decompress(false, null, pass);
  243. }
  244. },
  245. getDataAsync: function (/*Function*/ callback, pass) {
  246. if (_entryHeader.changed) {
  247. callback(uncompressedData);
  248. } else {
  249. decompress(true, callback, pass);
  250. }
  251. },
  252. set attr(attr) {
  253. _entryHeader.attr = attr;
  254. },
  255. get attr() {
  256. return _entryHeader.attr;
  257. },
  258. set header(/*Buffer*/ data) {
  259. _entryHeader.loadFromBinary(data);
  260. },
  261. get header() {
  262. return _entryHeader;
  263. },
  264. packHeader: function () {
  265. // 1. create header (buffer)
  266. var header = _entryHeader.entryHeaderToBinary();
  267. var addpos = Utils.Constants.CENHDR;
  268. // 2. add file name
  269. _entryName.copy(header, addpos);
  270. addpos += _entryName.length;
  271. // 3. add extra data
  272. if (_entryHeader.extraLength) {
  273. _extra.copy(header, addpos);
  274. addpos += _entryHeader.extraLength;
  275. }
  276. // 4. add file comment
  277. if (_entryHeader.commentLength) {
  278. _comment.copy(header, addpos);
  279. }
  280. return header;
  281. },
  282. toJSON: function () {
  283. const bytes = function (nr) {
  284. return "<" + ((nr && nr.length + " bytes buffer") || "null") + ">";
  285. };
  286. return {
  287. entryName: this.entryName,
  288. name: this.name,
  289. comment: this.comment,
  290. isDirectory: this.isDirectory,
  291. header: _entryHeader.toJSON(),
  292. compressedData: bytes(input),
  293. data: bytes(uncompressedData)
  294. };
  295. },
  296. toString: function () {
  297. return JSON.stringify(this.toJSON(), null, "\t");
  298. }
  299. };
  300. };