zipEntry.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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.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*/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. if (async && callback) callback(compressedData, Utils.Errors.NO_DATA);//si added error.
  46. return compressedData;
  47. }
  48. var data = Buffer.alloc(_entryHeader.size);
  49. switch (_entryHeader.method) {
  50. case Utils.Constants.STORED:
  51. compressedData.copy(data);
  52. if (!crc32OK(data)) {
  53. if (async && callback) callback(data, Utils.Errors.BAD_CRC);//si added error
  54. return Utils.Errors.BAD_CRC;
  55. } else {//si added otherwise did not seem to return data.
  56. if (async && callback) callback(data);
  57. return data;
  58. }
  59. case Utils.Constants.DEFLATED:
  60. var inflater = new Methods.Inflater(compressedData);
  61. if (!async) {
  62. inflater.inflate(data);
  63. if (!crc32OK(data)) {
  64. console.warn(Utils.Errors.BAD_CRC + " " + _entryName.toString())
  65. }
  66. return data;
  67. } else {
  68. inflater.inflateAsync(function(result) {
  69. result.copy(data, 0);
  70. if (!crc32OK(data)) {
  71. if (callback) callback(data, Utils.Errors.BAD_CRC); //si added error
  72. } else { //si added otherwise did not seem to return data.
  73. if (callback) callback(data);
  74. }
  75. })
  76. }
  77. break;
  78. default:
  79. if (async && callback) callback(Buffer.alloc(0), Utils.Errors.UNKNOWN_METHOD);
  80. return Utils.Errors.UNKNOWN_METHOD;
  81. }
  82. }
  83. function compress(/*Boolean*/async, /*Function*/callback) {
  84. if ((!uncompressedData || !uncompressedData.length) && Buffer.isBuffer(input)) {
  85. // no data set or the data wasn't changed to require recompression
  86. if (async && callback) callback(getCompressedDataFromZip());
  87. return getCompressedDataFromZip();
  88. }
  89. if (uncompressedData.length && !_isDirectory) {
  90. var compressedData;
  91. // Local file header
  92. switch (_entryHeader.method) {
  93. case Utils.Constants.STORED:
  94. _entryHeader.compressedSize = _entryHeader.size;
  95. compressedData = Buffer.alloc(uncompressedData.length);
  96. uncompressedData.copy(compressedData);
  97. if (async && callback) callback(compressedData);
  98. return compressedData;
  99. default:
  100. case Utils.Constants.DEFLATED:
  101. var deflater = new Methods.Deflater(uncompressedData);
  102. if (!async) {
  103. var deflated = deflater.deflate();
  104. _entryHeader.compressedSize = deflated.length;
  105. return deflated;
  106. } else {
  107. deflater.deflateAsync(function(data) {
  108. compressedData = Buffer.alloc(data.length);
  109. _entryHeader.compressedSize = data.length;
  110. data.copy(compressedData);
  111. callback && callback(compressedData);
  112. })
  113. }
  114. deflater = null;
  115. break;
  116. }
  117. } else {
  118. if (async && callback) {
  119. callback(Buffer.alloc(0));
  120. } else {
  121. return Buffer.alloc(0);
  122. }
  123. }
  124. }
  125. function readUInt64LE(buffer, offset) {
  126. return (buffer.readUInt32LE(offset + 4) << 4) + buffer.readUInt32LE(offset);
  127. }
  128. function parseExtra(data) {
  129. var offset = 0;
  130. var signature, size, part;
  131. while(offset<data.length) {
  132. signature = data.readUInt16LE(offset);
  133. offset += 2;
  134. size = data.readUInt16LE(offset);
  135. offset += 2;
  136. part = data.slice(offset, offset+size);
  137. offset += size;
  138. if(Constants.ID_ZIP64 === signature) {
  139. parseZip64ExtendedInformation(part);
  140. }
  141. }
  142. }
  143. //Override header field values with values from the ZIP64 extra field
  144. function parseZip64ExtendedInformation(data) {
  145. var size, compressedSize, offset, diskNumStart;
  146. if(data.length >= Constants.EF_ZIP64_SCOMP) {
  147. size = readUInt64LE(data, Constants.EF_ZIP64_SUNCOMP);
  148. if(_entryHeader.size === Constants.EF_ZIP64_OR_32) {
  149. _entryHeader.size = size;
  150. }
  151. }
  152. if(data.length >= Constants.EF_ZIP64_RHO) {
  153. compressedSize = readUInt64LE(data, Constants.EF_ZIP64_SCOMP);
  154. if(_entryHeader.compressedSize === Constants.EF_ZIP64_OR_32) {
  155. _entryHeader.compressedSize = compressedSize;
  156. }
  157. }
  158. if(data.length >= Constants.EF_ZIP64_DSN) {
  159. offset = readUInt64LE(data, Constants.EF_ZIP64_RHO);
  160. if(_entryHeader.offset === Constants.EF_ZIP64_OR_32) {
  161. _entryHeader.offset = offset;
  162. }
  163. }
  164. if(data.length >= Constants.EF_ZIP64_DSN+4) {
  165. diskNumStart = data.readUInt32LE(Constants.EF_ZIP64_DSN);
  166. if(_entryHeader.diskNumStart === Constants.EF_ZIP64_OR_16) {
  167. _entryHeader.diskNumStart = diskNumStart;
  168. }
  169. }
  170. }
  171. return {
  172. get entryName () { return _entryName.toString(); },
  173. get rawEntryName() { return _entryName; },
  174. set entryName (val) {
  175. _entryName = Utils.toBuffer(val);
  176. var lastChar = _entryName[_entryName.length - 1];
  177. _isDirectory = (lastChar === 47) || (lastChar === 92);
  178. _entryHeader.fileNameLength = _entryName.length;
  179. },
  180. get extra () { return _extra; },
  181. set extra (val) {
  182. _extra = val;
  183. _entryHeader.extraLength = val.length;
  184. parseExtra(val);
  185. },
  186. get comment () { return _comment.toString(); },
  187. set comment (val) {
  188. _comment = Utils.toBuffer(val);
  189. _entryHeader.commentLength = _comment.length;
  190. },
  191. get name () { var n = _entryName.toString(); return _isDirectory ? n.substr(n.length - 1).split("/").pop() : n.split("/").pop(); },
  192. get isDirectory () { return _isDirectory },
  193. getCompressedData : function() {
  194. return compress(false, null)
  195. },
  196. getCompressedDataAsync : function(/*Function*/callback) {
  197. compress(true, callback)
  198. },
  199. setData : function(value) {
  200. uncompressedData = Utils.toBuffer(value);
  201. if (!_isDirectory && uncompressedData.length) {
  202. _entryHeader.size = uncompressedData.length;
  203. _entryHeader.method = Utils.Constants.STORED;
  204. _entryHeader.crc = Utils.crc32(value);
  205. _entryHeader.changed = true;
  206. } else { // folders and blank files should be stored
  207. _entryHeader.method = Utils.Constants.STORED;
  208. }
  209. },
  210. getData : function(pass) {
  211. if (_entryHeader.changed) {
  212. return uncompressedData;
  213. } else {
  214. return decompress(false, null, pass);
  215. }
  216. },
  217. getDataAsync : function(/*Function*/callback, pass) {
  218. if (_entryHeader.changed) {
  219. callback(uncompressedData)
  220. } else {
  221. decompress(true, callback, pass)
  222. }
  223. },
  224. set attr(attr) { _entryHeader.attr = attr; },
  225. get attr() { return _entryHeader.attr; },
  226. set header(/*Buffer*/data) {
  227. _entryHeader.loadFromBinary(data);
  228. },
  229. get header() {
  230. return _entryHeader;
  231. },
  232. packHeader : function() {
  233. var header = _entryHeader.entryHeaderToBinary();
  234. // add
  235. _entryName.copy(header, Utils.Constants.CENHDR);
  236. if (_entryHeader.extraLength) {
  237. _extra.copy(header, Utils.Constants.CENHDR + _entryName.length)
  238. }
  239. if (_entryHeader.commentLength) {
  240. _comment.copy(header, Utils.Constants.CENHDR + _entryName.length + _entryHeader.extraLength, _comment.length);
  241. }
  242. return header;
  243. },
  244. toString : function() {
  245. return '{\n' +
  246. '\t"entryName" : "' + _entryName.toString() + "\",\n" +
  247. '\t"name" : "' + (_isDirectory ? _entryName.toString().replace(/\/$/, '').split("/").pop() : _entryName.toString().split("/").pop()) + "\",\n" +
  248. '\t"comment" : "' + _comment.toString() + "\",\n" +
  249. '\t"isDirectory" : ' + _isDirectory + ",\n" +
  250. '\t"header" : ' + _entryHeader.toString().replace(/\t/mg, "\t\t").replace(/}/mg, "\t}") + ",\n" +
  251. '\t"compressedData" : <' + (input && input.length + " bytes buffer" || "null") + ">\n" +
  252. '\t"data" : <' + (uncompressedData && uncompressedData.length + " bytes buffer" || "null") + ">\n" +
  253. '}';
  254. }
  255. }
  256. };