zipEntry.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. var Utils = require("./util"),
  2. Headers = require("./headers"),
  3. Constants = Utils.Constants,
  4. Methods = require("./methods");
  5. module.exports = function (/** object */ options, /*Buffer*/ input) {
  6. var _centralHeader = 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. _extralocal = Buffer.alloc(0),
  13. _efs = true;
  14. // assign options
  15. const opts = options;
  16. const decoder = typeof opts.decoder === "object" ? opts.decoder : Utils.decoder;
  17. _efs = decoder.hasOwnProperty("efs") ? decoder.efs : false;
  18. function getCompressedDataFromZip() {
  19. //if (!input || !Buffer.isBuffer(input)) {
  20. if (!input || !(input instanceof Uint8Array)) {
  21. return Buffer.alloc(0);
  22. }
  23. _extralocal = _centralHeader.loadLocalHeaderFromBinary(input);
  24. return input.slice(_centralHeader.realDataOffset, _centralHeader.realDataOffset + _centralHeader.compressedSize);
  25. }
  26. function crc32OK(data) {
  27. // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the local header is written
  28. if (!_centralHeader.flags_desc) {
  29. if (Utils.crc32(data) !== _centralHeader.localHeader.crc) {
  30. return false;
  31. }
  32. } else {
  33. const descriptor = {};
  34. const dataEndOffset = _centralHeader.realDataOffset + _centralHeader.compressedSize;
  35. // no descriptor after compressed data, instead new local header
  36. if (input.readUInt32LE(dataEndOffset) == Constants.LOCSIG || input.readUInt32LE(dataEndOffset) == Constants.CENSIG) {
  37. throw Utils.Errors.DESCRIPTOR_NOT_EXIST();
  38. }
  39. // get decriptor data
  40. if (input.readUInt32LE(dataEndOffset) == Constants.EXTSIG) {
  41. // descriptor with signature
  42. descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC);
  43. descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ);
  44. descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN);
  45. } else if (input.readUInt16LE(dataEndOffset + 12) === 0x4b50) {
  46. // descriptor without signature (we check is new header starting where we expect)
  47. descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC - 4);
  48. descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ - 4);
  49. descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN - 4);
  50. } else {
  51. throw Utils.Errors.DESCRIPTOR_UNKNOWN();
  52. }
  53. // check data integrity
  54. if (descriptor.compressedSize !== _centralHeader.compressedSize || descriptor.size !== _centralHeader.size || descriptor.crc !== _centralHeader.crc) {
  55. throw Utils.Errors.DESCRIPTOR_FAULTY();
  56. }
  57. if (Utils.crc32(data) !== descriptor.crc) {
  58. return false;
  59. }
  60. // @TODO: zip64 bit descriptor fields
  61. // if bit 3 is set and any value in local header "zip64 Extended information" extra field are set 0 (place holder)
  62. // then 64-bit descriptor format is used instead of 32-bit
  63. // central header - "zip64 Extended information" extra field should store real values and not place holders
  64. }
  65. return true;
  66. }
  67. function decompress(/*Boolean*/ async, /*Function*/ callback, /*String, Buffer*/ pass) {
  68. if (typeof callback === "undefined" && typeof async === "string") {
  69. pass = async;
  70. async = void 0;
  71. }
  72. if (_isDirectory) {
  73. if (async && callback) {
  74. callback(Buffer.alloc(0), Utils.Errors.DIRECTORY_CONTENT_ERROR()); //si added error.
  75. }
  76. return Buffer.alloc(0);
  77. }
  78. var compressedData = getCompressedDataFromZip();
  79. if (compressedData.length === 0) {
  80. // File is empty, nothing to decompress.
  81. if (async && callback) callback(compressedData);
  82. return compressedData;
  83. }
  84. if (_centralHeader.encrypted) {
  85. if ("string" !== typeof pass && !Buffer.isBuffer(pass)) {
  86. throw Utils.Errors.INVALID_PASS_PARAM();
  87. }
  88. compressedData = Methods.ZipCrypto.decrypt(compressedData, _centralHeader, pass);
  89. }
  90. var data = Buffer.alloc(_centralHeader.size);
  91. switch (_centralHeader.method) {
  92. case Utils.Constants.STORED:
  93. compressedData.copy(data);
  94. if (!crc32OK(data)) {
  95. if (async && callback) callback(data, Utils.Errors.BAD_CRC()); //si added error
  96. throw Utils.Errors.BAD_CRC();
  97. } else {
  98. //si added otherwise did not seem to return data.
  99. if (async && callback) callback(data);
  100. return data;
  101. }
  102. case Utils.Constants.DEFLATED:
  103. var inflater = new Methods.Inflater(compressedData, _centralHeader.size);
  104. if (!async) {
  105. const result = inflater.inflate(data);
  106. result.copy(data, 0);
  107. if (!crc32OK(data)) {
  108. throw Utils.Errors.BAD_CRC(`"${decoder.decode(_entryName)}"`);
  109. }
  110. return data;
  111. } else {
  112. inflater.inflateAsync(function (result) {
  113. result.copy(result, 0);
  114. if (callback) {
  115. if (!crc32OK(result)) {
  116. callback(result, Utils.Errors.BAD_CRC()); //si added error
  117. } else {
  118. callback(result);
  119. }
  120. }
  121. });
  122. }
  123. break;
  124. default:
  125. if (async && callback) callback(Buffer.alloc(0), Utils.Errors.UNKNOWN_METHOD());
  126. throw Utils.Errors.UNKNOWN_METHOD();
  127. }
  128. }
  129. function compress(/*Boolean*/ async, /*Function*/ callback) {
  130. if ((!uncompressedData || !uncompressedData.length) && Buffer.isBuffer(input)) {
  131. // no data set or the data wasn't changed to require recompression
  132. if (async && callback) callback(getCompressedDataFromZip());
  133. return getCompressedDataFromZip();
  134. }
  135. if (uncompressedData.length && !_isDirectory) {
  136. var compressedData;
  137. // Local file header
  138. switch (_centralHeader.method) {
  139. case Utils.Constants.STORED:
  140. _centralHeader.compressedSize = _centralHeader.size;
  141. compressedData = Buffer.alloc(uncompressedData.length);
  142. uncompressedData.copy(compressedData);
  143. if (async && callback) callback(compressedData);
  144. return compressedData;
  145. default:
  146. case Utils.Constants.DEFLATED:
  147. var deflater = new Methods.Deflater(uncompressedData);
  148. if (!async) {
  149. var deflated = deflater.deflate();
  150. _centralHeader.compressedSize = deflated.length;
  151. return deflated;
  152. } else {
  153. deflater.deflateAsync(function (data) {
  154. compressedData = Buffer.alloc(data.length);
  155. _centralHeader.compressedSize = data.length;
  156. data.copy(compressedData);
  157. callback && callback(compressedData);
  158. });
  159. }
  160. deflater = null;
  161. break;
  162. }
  163. } else if (async && callback) {
  164. callback(Buffer.alloc(0));
  165. } else {
  166. return Buffer.alloc(0);
  167. }
  168. }
  169. function readUInt64LE(buffer, offset) {
  170. return (buffer.readUInt32LE(offset + 4) << 4) + buffer.readUInt32LE(offset);
  171. }
  172. function parseExtra(data) {
  173. try {
  174. var offset = 0;
  175. var signature, size, part;
  176. while (offset + 4 < data.length) {
  177. signature = data.readUInt16LE(offset);
  178. offset += 2;
  179. size = data.readUInt16LE(offset);
  180. offset += 2;
  181. part = data.slice(offset, offset + size);
  182. offset += size;
  183. if (Constants.ID_ZIP64 === signature) {
  184. parseZip64ExtendedInformation(part);
  185. }
  186. }
  187. } catch (error) {
  188. throw Utils.Errors.EXTRA_FIELD_PARSE_ERROR();
  189. }
  190. }
  191. //Override header field values with values from the ZIP64 extra field
  192. function parseZip64ExtendedInformation(data) {
  193. var size, compressedSize, offset, diskNumStart;
  194. if (data.length >= Constants.EF_ZIP64_SCOMP) {
  195. size = readUInt64LE(data, Constants.EF_ZIP64_SUNCOMP);
  196. if (_centralHeader.size === Constants.EF_ZIP64_OR_32) {
  197. _centralHeader.size = size;
  198. }
  199. }
  200. if (data.length >= Constants.EF_ZIP64_RHO) {
  201. compressedSize = readUInt64LE(data, Constants.EF_ZIP64_SCOMP);
  202. if (_centralHeader.compressedSize === Constants.EF_ZIP64_OR_32) {
  203. _centralHeader.compressedSize = compressedSize;
  204. }
  205. }
  206. if (data.length >= Constants.EF_ZIP64_DSN) {
  207. offset = readUInt64LE(data, Constants.EF_ZIP64_RHO);
  208. if (_centralHeader.offset === Constants.EF_ZIP64_OR_32) {
  209. _centralHeader.offset = offset;
  210. }
  211. }
  212. if (data.length >= Constants.EF_ZIP64_DSN + 4) {
  213. diskNumStart = data.readUInt32LE(Constants.EF_ZIP64_DSN);
  214. if (_centralHeader.diskNumStart === Constants.EF_ZIP64_OR_16) {
  215. _centralHeader.diskNumStart = diskNumStart;
  216. }
  217. }
  218. }
  219. return {
  220. get entryName() {
  221. return decoder.decode(_entryName);
  222. },
  223. get rawEntryName() {
  224. return _entryName;
  225. },
  226. set entryName(val) {
  227. _entryName = Utils.toBuffer(val, decoder.encode);
  228. var lastChar = _entryName[_entryName.length - 1];
  229. _isDirectory = lastChar === 47 || lastChar === 92;
  230. _centralHeader.fileNameLength = _entryName.length;
  231. },
  232. get efs() {
  233. if (typeof _efs === "function") {
  234. return _efs(this.entryName);
  235. } else {
  236. return _efs;
  237. }
  238. },
  239. get extra() {
  240. return _extra;
  241. },
  242. set extra(val) {
  243. _extra = val;
  244. _centralHeader.extraLength = val.length;
  245. parseExtra(val);
  246. },
  247. get comment() {
  248. return decoder.decode(_comment);
  249. },
  250. set comment(val) {
  251. _comment = Utils.toBuffer(val, decoder.encode);
  252. _centralHeader.commentLength = _comment.length;
  253. if (_comment.length > 0xffff) throw Utils.Errors.COMMENT_TOO_LONG();
  254. },
  255. get name() {
  256. var n = decoder.decode(_entryName);
  257. return _isDirectory
  258. ? n
  259. .substr(n.length - 1)
  260. .split("/")
  261. .pop()
  262. : n.split("/").pop();
  263. },
  264. get isDirectory() {
  265. return _isDirectory;
  266. },
  267. getCompressedData: function () {
  268. return compress(false, null);
  269. },
  270. getCompressedDataAsync: function (/*Function*/ callback) {
  271. compress(true, callback);
  272. },
  273. setData: function (value) {
  274. uncompressedData = Utils.toBuffer(value, Utils.decoder.encode);
  275. if (!_isDirectory && uncompressedData.length) {
  276. _centralHeader.size = uncompressedData.length;
  277. _centralHeader.method = Utils.Constants.DEFLATED;
  278. _centralHeader.crc = Utils.crc32(value);
  279. _centralHeader.changed = true;
  280. } else {
  281. // folders and blank files should be stored
  282. _centralHeader.method = Utils.Constants.STORED;
  283. }
  284. },
  285. getData: function (pass) {
  286. if (_centralHeader.changed) {
  287. return uncompressedData;
  288. } else {
  289. return decompress(false, null, pass);
  290. }
  291. },
  292. getDataAsync: function (/*Function*/ callback, pass) {
  293. if (_centralHeader.changed) {
  294. callback(uncompressedData);
  295. } else {
  296. decompress(true, callback, pass);
  297. }
  298. },
  299. set attr(attr) {
  300. _centralHeader.attr = attr;
  301. },
  302. get attr() {
  303. return _centralHeader.attr;
  304. },
  305. set header(/*Buffer*/ data) {
  306. _centralHeader.loadFromBinary(data);
  307. },
  308. get header() {
  309. return _centralHeader;
  310. },
  311. packCentralHeader: function () {
  312. _centralHeader.flags_efs = this.efs;
  313. _centralHeader.extraLength = _extra.length;
  314. // 1. create header (buffer)
  315. var header = _centralHeader.centralHeaderToBinary();
  316. var addpos = Utils.Constants.CENHDR;
  317. // 2. add file name
  318. _entryName.copy(header, addpos);
  319. addpos += _entryName.length;
  320. // 3. add extra data
  321. _extra.copy(header, addpos);
  322. addpos += _centralHeader.extraLength;
  323. // 4. add file comment
  324. _comment.copy(header, addpos);
  325. return header;
  326. },
  327. packLocalHeader: function () {
  328. let addpos = 0;
  329. _centralHeader.flags_efs = this.efs;
  330. _centralHeader.extraLocalLength = _extralocal.length;
  331. // 1. construct local header Buffer
  332. const localHeaderBuf = _centralHeader.localHeaderToBinary();
  333. // 2. localHeader - crate header buffer
  334. const localHeader = Buffer.alloc(localHeaderBuf.length + _entryName.length + _centralHeader.extraLocalLength);
  335. // 2.1 add localheader
  336. localHeaderBuf.copy(localHeader, addpos);
  337. addpos += localHeaderBuf.length;
  338. // 2.2 add file name
  339. _entryName.copy(localHeader, addpos);
  340. addpos += _entryName.length;
  341. // 2.3 add extra field
  342. _extralocal.copy(localHeader, addpos);
  343. addpos += _extralocal.length;
  344. return localHeader;
  345. },
  346. toJSON: function () {
  347. const bytes = function (nr) {
  348. return "<" + ((nr && nr.length + " bytes buffer") || "null") + ">";
  349. };
  350. return {
  351. entryName: this.entryName,
  352. name: this.name,
  353. comment: this.comment,
  354. isDirectory: this.isDirectory,
  355. header: _centralHeader.toJSON(),
  356. compressedData: bytes(input),
  357. data: bytes(uncompressedData)
  358. };
  359. },
  360. toString: function () {
  361. return JSON.stringify(this.toJSON(), null, "\t");
  362. }
  363. };
  364. };