You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. var fs = require("fs");
  2. var Transform = require("stream").Transform;
  3. var PassThrough = require("stream").PassThrough;
  4. var zlib = require("zlib");
  5. var util = require("util");
  6. var EventEmitter = require("events").EventEmitter;
  7. var crc32 = require("buffer-crc32");
  8. exports.ZipFile = ZipFile;
  9. exports.dateToDosDateTime = dateToDosDateTime;
  10. util.inherits(ZipFile, EventEmitter);
  11. function ZipFile() {
  12. this.outputStream = new PassThrough();
  13. this.entries = [];
  14. this.outputStreamCursor = 0;
  15. this.ended = false; // .end() sets this
  16. this.allDone = false; // set when we've written the last bytes
  17. this.forceZip64Eocd = false; // configurable in .end()
  18. }
  19. ZipFile.prototype.addFile = function(realPath, metadataPath, options) {
  20. var self = this;
  21. metadataPath = validateMetadataPath(metadataPath, false);
  22. if (options == null) options = {};
  23. var entry = new Entry(metadataPath, false, options);
  24. self.entries.push(entry);
  25. fs.stat(realPath, function(err, stats) {
  26. if (err) return self.emit("error", err);
  27. if (!stats.isFile()) return self.emit("error", new Error("not a file: " + realPath));
  28. entry.uncompressedSize = stats.size;
  29. if (options.mtime == null) entry.setLastModDate(stats.mtime);
  30. if (options.mode == null) entry.setFileAttributesMode(stats.mode);
  31. entry.setFileDataPumpFunction(function() {
  32. var readStream = fs.createReadStream(realPath);
  33. entry.state = Entry.FILE_DATA_IN_PROGRESS;
  34. readStream.on("error", function(err) {
  35. self.emit("error", err);
  36. });
  37. pumpFileDataReadStream(self, entry, readStream);
  38. });
  39. pumpEntries(self);
  40. });
  41. };
  42. ZipFile.prototype.addReadStream = function(readStream, metadataPath, options) {
  43. var self = this;
  44. metadataPath = validateMetadataPath(metadataPath, false);
  45. if (options == null) options = {};
  46. var entry = new Entry(metadataPath, false, options);
  47. self.entries.push(entry);
  48. entry.setFileDataPumpFunction(function() {
  49. entry.state = Entry.FILE_DATA_IN_PROGRESS;
  50. pumpFileDataReadStream(self, entry, readStream);
  51. });
  52. pumpEntries(self);
  53. };
  54. ZipFile.prototype.addBuffer = function(buffer, metadataPath, options) {
  55. var self = this;
  56. metadataPath = validateMetadataPath(metadataPath, false);
  57. if (buffer.length > 0x3fffffff) throw new Error("buffer too large: " + buffer.length + " > " + 0x3fffffff);
  58. if (options == null) options = {};
  59. if (options.size != null) throw new Error("options.size not allowed");
  60. var entry = new Entry(metadataPath, false, options);
  61. entry.uncompressedSize = buffer.length;
  62. entry.crc32 = crc32.unsigned(buffer);
  63. entry.crcAndFileSizeKnown = true;
  64. self.entries.push(entry);
  65. if (!entry.compress) {
  66. setCompressedBuffer(buffer);
  67. } else {
  68. zlib.deflateRaw(buffer, function(err, compressedBuffer) {
  69. setCompressedBuffer(compressedBuffer);
  70. });
  71. }
  72. function setCompressedBuffer(compressedBuffer) {
  73. entry.compressedSize = compressedBuffer.length;
  74. entry.setFileDataPumpFunction(function() {
  75. writeToOutputStream(self, compressedBuffer);
  76. writeToOutputStream(self, entry.getDataDescriptor());
  77. entry.state = Entry.FILE_DATA_DONE;
  78. // don't call pumpEntries() recursively.
  79. // (also, don't call process.nextTick recursively.)
  80. setImmediate(function() {
  81. pumpEntries(self);
  82. });
  83. });
  84. pumpEntries(self);
  85. }
  86. };
  87. ZipFile.prototype.addEmptyDirectory = function(metadataPath, options) {
  88. var self = this;
  89. metadataPath = validateMetadataPath(metadataPath, true);
  90. if (options == null) options = {};
  91. if (options.size != null) throw new Error("options.size not allowed");
  92. if (options.compress != null) throw new Error("options.compress not allowed");
  93. var entry = new Entry(metadataPath, true, options);
  94. self.entries.push(entry);
  95. entry.setFileDataPumpFunction(function() {
  96. writeToOutputStream(self, entry.getDataDescriptor());
  97. entry.state = Entry.FILE_DATA_DONE;
  98. pumpEntries(self);
  99. });
  100. pumpEntries(self);
  101. };
  102. var eocdrSignatureBuffer = bufferFrom([0x50, 0x4b, 0x05, 0x06]);
  103. ZipFile.prototype.end = function(options, finalSizeCallback) {
  104. if (typeof options === "function") {
  105. finalSizeCallback = options;
  106. options = null;
  107. }
  108. if (options == null) options = {};
  109. if (this.ended) return;
  110. this.ended = true;
  111. this.finalSizeCallback = finalSizeCallback;
  112. this.forceZip64Eocd = !!options.forceZip64Format;
  113. if (options.comment) {
  114. if (typeof options.comment === "string") {
  115. this.comment = encodeCp437(options.comment);
  116. } else {
  117. // It should be a Buffer
  118. this.comment = options.comment;
  119. }
  120. if (this.comment.length > 0xffff) throw new Error("comment is too large");
  121. // gotta check for this, because the zipfile format is actually ambiguous.
  122. if (bufferIncludes(this.comment, eocdrSignatureBuffer)) throw new Error("comment contains end of central directory record signature");
  123. } else {
  124. // no comment.
  125. this.comment = EMPTY_BUFFER;
  126. }
  127. pumpEntries(this);
  128. };
  129. function writeToOutputStream(self, buffer) {
  130. self.outputStream.write(buffer);
  131. self.outputStreamCursor += buffer.length;
  132. }
  133. function pumpFileDataReadStream(self, entry, readStream) {
  134. var crc32Watcher = new Crc32Watcher();
  135. var uncompressedSizeCounter = new ByteCounter();
  136. var compressor = entry.compress ? new zlib.DeflateRaw() : new PassThrough();
  137. var compressedSizeCounter = new ByteCounter();
  138. readStream.pipe(crc32Watcher)
  139. .pipe(uncompressedSizeCounter)
  140. .pipe(compressor)
  141. .pipe(compressedSizeCounter)
  142. .pipe(self.outputStream, {end: false});
  143. compressedSizeCounter.on("end", function() {
  144. entry.crc32 = crc32Watcher.crc32;
  145. if (entry.uncompressedSize == null) {
  146. entry.uncompressedSize = uncompressedSizeCounter.byteCount;
  147. } else {
  148. if (entry.uncompressedSize !== uncompressedSizeCounter.byteCount) return self.emit("error", new Error("file data stream has unexpected number of bytes"));
  149. }
  150. entry.compressedSize = compressedSizeCounter.byteCount;
  151. self.outputStreamCursor += entry.compressedSize;
  152. writeToOutputStream(self, entry.getDataDescriptor());
  153. entry.state = Entry.FILE_DATA_DONE;
  154. pumpEntries(self);
  155. });
  156. }
  157. function pumpEntries(self) {
  158. if (self.allDone) return;
  159. // first check if finalSize is finally known
  160. if (self.ended && self.finalSizeCallback != null) {
  161. var finalSize = calculateFinalSize(self);
  162. if (finalSize != null) {
  163. // we have an answer
  164. self.finalSizeCallback(finalSize);
  165. self.finalSizeCallback = null;
  166. }
  167. }
  168. // pump entries
  169. var entry = getFirstNotDoneEntry();
  170. function getFirstNotDoneEntry() {
  171. for (var i = 0; i < self.entries.length; i++) {
  172. var entry = self.entries[i];
  173. if (entry.state < Entry.FILE_DATA_DONE) return entry;
  174. }
  175. return null;
  176. }
  177. if (entry != null) {
  178. // this entry is not done yet
  179. if (entry.state < Entry.READY_TO_PUMP_FILE_DATA) return; // input file not open yet
  180. if (entry.state === Entry.FILE_DATA_IN_PROGRESS) return; // we'll get there
  181. // start with local file header
  182. entry.relativeOffsetOfLocalHeader = self.outputStreamCursor;
  183. var localFileHeader = entry.getLocalFileHeader();
  184. writeToOutputStream(self, localFileHeader);
  185. entry.doFileDataPump();
  186. } else {
  187. // all cought up on writing entries
  188. if (self.ended) {
  189. // head for the exit
  190. self.offsetOfStartOfCentralDirectory = self.outputStreamCursor;
  191. self.entries.forEach(function(entry) {
  192. var centralDirectoryRecord = entry.getCentralDirectoryRecord();
  193. writeToOutputStream(self, centralDirectoryRecord);
  194. });
  195. writeToOutputStream(self, getEndOfCentralDirectoryRecord(self));
  196. self.outputStream.end();
  197. self.allDone = true;
  198. }
  199. }
  200. }
  201. function calculateFinalSize(self) {
  202. var pretendOutputCursor = 0;
  203. var centralDirectorySize = 0;
  204. for (var i = 0; i < self.entries.length; i++) {
  205. var entry = self.entries[i];
  206. // compression is too hard to predict
  207. if (entry.compress) return -1;
  208. if (entry.state >= Entry.READY_TO_PUMP_FILE_DATA) {
  209. // if addReadStream was called without providing the size, we can't predict the final size
  210. if (entry.uncompressedSize == null) return -1;
  211. } else {
  212. // if we're still waiting for fs.stat, we might learn the size someday
  213. if (entry.uncompressedSize == null) return null;
  214. }
  215. // we know this for sure, and this is important to know if we need ZIP64 format.
  216. entry.relativeOffsetOfLocalHeader = pretendOutputCursor;
  217. var useZip64Format = entry.useZip64Format();
  218. pretendOutputCursor += LOCAL_FILE_HEADER_FIXED_SIZE + entry.utf8FileName.length;
  219. pretendOutputCursor += entry.uncompressedSize;
  220. if (!entry.crcAndFileSizeKnown) {
  221. // use a data descriptor
  222. if (useZip64Format) {
  223. pretendOutputCursor += ZIP64_DATA_DESCRIPTOR_SIZE;
  224. } else {
  225. pretendOutputCursor += DATA_DESCRIPTOR_SIZE;
  226. }
  227. }
  228. centralDirectorySize += CENTRAL_DIRECTORY_RECORD_FIXED_SIZE + entry.utf8FileName.length + entry.fileComment.length;
  229. if (useZip64Format) {
  230. centralDirectorySize += ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE;
  231. }
  232. }
  233. var endOfCentralDirectorySize = 0;
  234. if (self.forceZip64Eocd ||
  235. self.entries.length >= 0xffff ||
  236. centralDirectorySize >= 0xffff ||
  237. pretendOutputCursor >= 0xffffffff) {
  238. // use zip64 end of central directory stuff
  239. endOfCentralDirectorySize += ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE;
  240. }
  241. endOfCentralDirectorySize += END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + self.comment.length;
  242. return pretendOutputCursor + centralDirectorySize + endOfCentralDirectorySize;
  243. }
  244. var ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 56;
  245. var ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE = 20;
  246. var END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 22;
  247. function getEndOfCentralDirectoryRecord(self, actuallyJustTellMeHowLongItWouldBe) {
  248. var needZip64Format = false;
  249. var normalEntriesLength = self.entries.length;
  250. if (self.forceZip64Eocd || self.entries.length >= 0xffff) {
  251. normalEntriesLength = 0xffff;
  252. needZip64Format = true;
  253. }
  254. var sizeOfCentralDirectory = self.outputStreamCursor - self.offsetOfStartOfCentralDirectory;
  255. var normalSizeOfCentralDirectory = sizeOfCentralDirectory;
  256. if (self.forceZip64Eocd || sizeOfCentralDirectory >= 0xffffffff) {
  257. normalSizeOfCentralDirectory = 0xffffffff;
  258. needZip64Format = true;
  259. }
  260. var normalOffsetOfStartOfCentralDirectory = self.offsetOfStartOfCentralDirectory;
  261. if (self.forceZip64Eocd || self.offsetOfStartOfCentralDirectory >= 0xffffffff) {
  262. normalOffsetOfStartOfCentralDirectory = 0xffffffff;
  263. needZip64Format = true;
  264. }
  265. if (actuallyJustTellMeHowLongItWouldBe) {
  266. if (needZip64Format) {
  267. return (
  268. ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE +
  269. ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE +
  270. END_OF_CENTRAL_DIRECTORY_RECORD_SIZE
  271. );
  272. } else {
  273. return END_OF_CENTRAL_DIRECTORY_RECORD_SIZE;
  274. }
  275. }
  276. var eocdrBuffer = bufferAlloc(END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + self.comment.length);
  277. // end of central dir signature 4 bytes (0x06054b50)
  278. eocdrBuffer.writeUInt32LE(0x06054b50, 0);
  279. // number of this disk 2 bytes
  280. eocdrBuffer.writeUInt16LE(0, 4);
  281. // number of the disk with the start of the central directory 2 bytes
  282. eocdrBuffer.writeUInt16LE(0, 6);
  283. // total number of entries in the central directory on this disk 2 bytes
  284. eocdrBuffer.writeUInt16LE(normalEntriesLength, 8);
  285. // total number of entries in the central directory 2 bytes
  286. eocdrBuffer.writeUInt16LE(normalEntriesLength, 10);
  287. // size of the central directory 4 bytes
  288. eocdrBuffer.writeUInt32LE(normalSizeOfCentralDirectory, 12);
  289. // offset of start of central directory with respect to the starting disk number 4 bytes
  290. eocdrBuffer.writeUInt32LE(normalOffsetOfStartOfCentralDirectory, 16);
  291. // .ZIP file comment length 2 bytes
  292. eocdrBuffer.writeUInt16LE(self.comment.length, 20);
  293. // .ZIP file comment (variable size)
  294. self.comment.copy(eocdrBuffer, 22);
  295. if (!needZip64Format) return eocdrBuffer;
  296. // ZIP64 format
  297. // ZIP64 End of Central Directory Record
  298. var zip64EocdrBuffer = bufferAlloc(ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE);
  299. // zip64 end of central dir signature 4 bytes (0x06064b50)
  300. zip64EocdrBuffer.writeUInt32LE(0x06064b50, 0);
  301. // size of zip64 end of central directory record 8 bytes
  302. writeUInt64LE(zip64EocdrBuffer, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE - 12, 4);
  303. // version made by 2 bytes
  304. zip64EocdrBuffer.writeUInt16LE(VERSION_MADE_BY, 12);
  305. // version needed to extract 2 bytes
  306. zip64EocdrBuffer.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT_ZIP64, 14);
  307. // number of this disk 4 bytes
  308. zip64EocdrBuffer.writeUInt32LE(0, 16);
  309. // number of the disk with the start of the central directory 4 bytes
  310. zip64EocdrBuffer.writeUInt32LE(0, 20);
  311. // total number of entries in the central directory on this disk 8 bytes
  312. writeUInt64LE(zip64EocdrBuffer, self.entries.length, 24);
  313. // total number of entries in the central directory 8 bytes
  314. writeUInt64LE(zip64EocdrBuffer, self.entries.length, 32);
  315. // size of the central directory 8 bytes
  316. writeUInt64LE(zip64EocdrBuffer, sizeOfCentralDirectory, 40);
  317. // offset of start of central directory with respect to the starting disk number 8 bytes
  318. writeUInt64LE(zip64EocdrBuffer, self.offsetOfStartOfCentralDirectory, 48);
  319. // zip64 extensible data sector (variable size)
  320. // nothing in the zip64 extensible data sector
  321. // ZIP64 End of Central Directory Locator
  322. var zip64EocdlBuffer = bufferAlloc(ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE);
  323. // zip64 end of central dir locator signature 4 bytes (0x07064b50)
  324. zip64EocdlBuffer.writeUInt32LE(0x07064b50, 0);
  325. // number of the disk with the start of the zip64 end of central directory 4 bytes
  326. zip64EocdlBuffer.writeUInt32LE(0, 4);
  327. // relative offset of the zip64 end of central directory record 8 bytes
  328. writeUInt64LE(zip64EocdlBuffer, self.outputStreamCursor, 8);
  329. // total number of disks 4 bytes
  330. zip64EocdlBuffer.writeUInt32LE(1, 16);
  331. return Buffer.concat([
  332. zip64EocdrBuffer,
  333. zip64EocdlBuffer,
  334. eocdrBuffer,
  335. ]);
  336. }
  337. function validateMetadataPath(metadataPath, isDirectory) {
  338. if (metadataPath === "") throw new Error("empty metadataPath");
  339. metadataPath = metadataPath.replace(/\\/g, "/");
  340. if (/^[a-zA-Z]:/.test(metadataPath) || /^\//.test(metadataPath)) throw new Error("absolute path: " + metadataPath);
  341. if (metadataPath.split("/").indexOf("..") !== -1) throw new Error("invalid relative path: " + metadataPath);
  342. var looksLikeDirectory = /\/$/.test(metadataPath);
  343. if (isDirectory) {
  344. // append a trailing '/' if necessary.
  345. if (!looksLikeDirectory) metadataPath += "/";
  346. } else {
  347. if (looksLikeDirectory) throw new Error("file path cannot end with '/': " + metadataPath);
  348. }
  349. return metadataPath;
  350. }
  351. var EMPTY_BUFFER = bufferAlloc(0);
  352. // this class is not part of the public API
  353. function Entry(metadataPath, isDirectory, options) {
  354. this.utf8FileName = bufferFrom(metadataPath);
  355. if (this.utf8FileName.length > 0xffff) throw new Error("utf8 file name too long. " + utf8FileName.length + " > " + 0xffff);
  356. this.isDirectory = isDirectory;
  357. this.state = Entry.WAITING_FOR_METADATA;
  358. this.setLastModDate(options.mtime != null ? options.mtime : new Date());
  359. if (options.mode != null) {
  360. this.setFileAttributesMode(options.mode);
  361. } else {
  362. this.setFileAttributesMode(isDirectory ? 0o40775 : 0o100664);
  363. }
  364. if (isDirectory) {
  365. this.crcAndFileSizeKnown = true;
  366. this.crc32 = 0;
  367. this.uncompressedSize = 0;
  368. this.compressedSize = 0;
  369. } else {
  370. // unknown so far
  371. this.crcAndFileSizeKnown = false;
  372. this.crc32 = null;
  373. this.uncompressedSize = null;
  374. this.compressedSize = null;
  375. if (options.size != null) this.uncompressedSize = options.size;
  376. }
  377. if (isDirectory) {
  378. this.compress = false;
  379. } else {
  380. this.compress = true; // default
  381. if (options.compress != null) this.compress = !!options.compress;
  382. }
  383. this.forceZip64Format = !!options.forceZip64Format;
  384. if (options.fileComment) {
  385. if (typeof options.fileComment === "string") {
  386. this.fileComment = bufferFrom(options.fileComment, "utf-8");
  387. } else {
  388. // It should be a Buffer
  389. this.fileComment = options.fileComment;
  390. }
  391. if (this.fileComment.length > 0xffff) throw new Error("fileComment is too large");
  392. } else {
  393. // no comment.
  394. this.fileComment = EMPTY_BUFFER;
  395. }
  396. }
  397. Entry.WAITING_FOR_METADATA = 0;
  398. Entry.READY_TO_PUMP_FILE_DATA = 1;
  399. Entry.FILE_DATA_IN_PROGRESS = 2;
  400. Entry.FILE_DATA_DONE = 3;
  401. Entry.prototype.setLastModDate = function(date) {
  402. var dosDateTime = dateToDosDateTime(date);
  403. this.lastModFileTime = dosDateTime.time;
  404. this.lastModFileDate = dosDateTime.date;
  405. };
  406. Entry.prototype.setFileAttributesMode = function(mode) {
  407. if ((mode & 0xffff) !== mode) throw new Error("invalid mode. expected: 0 <= " + mode + " <= " + 0xffff);
  408. // http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute/14727#14727
  409. this.externalFileAttributes = (mode << 16) >>> 0;
  410. };
  411. // doFileDataPump() should not call pumpEntries() directly. see issue #9.
  412. Entry.prototype.setFileDataPumpFunction = function(doFileDataPump) {
  413. this.doFileDataPump = doFileDataPump;
  414. this.state = Entry.READY_TO_PUMP_FILE_DATA;
  415. };
  416. Entry.prototype.useZip64Format = function() {
  417. return (
  418. (this.forceZip64Format) ||
  419. (this.uncompressedSize != null && this.uncompressedSize > 0xfffffffe) ||
  420. (this.compressedSize != null && this.compressedSize > 0xfffffffe) ||
  421. (this.relativeOffsetOfLocalHeader != null && this.relativeOffsetOfLocalHeader > 0xfffffffe)
  422. );
  423. }
  424. var LOCAL_FILE_HEADER_FIXED_SIZE = 30;
  425. var VERSION_NEEDED_TO_EXTRACT_UTF8 = 20;
  426. var VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45;
  427. // 3 = unix. 63 = spec version 6.3
  428. var VERSION_MADE_BY = (3 << 8) | 63;
  429. var FILE_NAME_IS_UTF8 = 1 << 11;
  430. var UNKNOWN_CRC32_AND_FILE_SIZES = 1 << 3;
  431. Entry.prototype.getLocalFileHeader = function() {
  432. var crc32 = 0;
  433. var compressedSize = 0;
  434. var uncompressedSize = 0;
  435. if (this.crcAndFileSizeKnown) {
  436. crc32 = this.crc32;
  437. compressedSize = this.compressedSize;
  438. uncompressedSize = this.uncompressedSize;
  439. }
  440. var fixedSizeStuff = bufferAlloc(LOCAL_FILE_HEADER_FIXED_SIZE);
  441. var generalPurposeBitFlag = FILE_NAME_IS_UTF8;
  442. if (!this.crcAndFileSizeKnown) generalPurposeBitFlag |= UNKNOWN_CRC32_AND_FILE_SIZES;
  443. // local file header signature 4 bytes (0x04034b50)
  444. fixedSizeStuff.writeUInt32LE(0x04034b50, 0);
  445. // version needed to extract 2 bytes
  446. fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT_UTF8, 4);
  447. // general purpose bit flag 2 bytes
  448. fixedSizeStuff.writeUInt16LE(generalPurposeBitFlag, 6);
  449. // compression method 2 bytes
  450. fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 8);
  451. // last mod file time 2 bytes
  452. fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 10);
  453. // last mod file date 2 bytes
  454. fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 12);
  455. // crc-32 4 bytes
  456. fixedSizeStuff.writeUInt32LE(crc32, 14);
  457. // compressed size 4 bytes
  458. fixedSizeStuff.writeUInt32LE(compressedSize, 18);
  459. // uncompressed size 4 bytes
  460. fixedSizeStuff.writeUInt32LE(uncompressedSize, 22);
  461. // file name length 2 bytes
  462. fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 26);
  463. // extra field length 2 bytes
  464. fixedSizeStuff.writeUInt16LE(0, 28);
  465. return Buffer.concat([
  466. fixedSizeStuff,
  467. // file name (variable size)
  468. this.utf8FileName,
  469. // extra field (variable size)
  470. // no extra fields
  471. ]);
  472. };
  473. var DATA_DESCRIPTOR_SIZE = 16;
  474. var ZIP64_DATA_DESCRIPTOR_SIZE = 24;
  475. Entry.prototype.getDataDescriptor = function() {
  476. if (this.crcAndFileSizeKnown) {
  477. // the Mac Archive Utility requires this not be present unless we set general purpose bit 3
  478. return EMPTY_BUFFER;
  479. }
  480. if (!this.useZip64Format()) {
  481. var buffer = bufferAlloc(DATA_DESCRIPTOR_SIZE);
  482. // optional signature (required according to Archive Utility)
  483. buffer.writeUInt32LE(0x08074b50, 0);
  484. // crc-32 4 bytes
  485. buffer.writeUInt32LE(this.crc32, 4);
  486. // compressed size 4 bytes
  487. buffer.writeUInt32LE(this.compressedSize, 8);
  488. // uncompressed size 4 bytes
  489. buffer.writeUInt32LE(this.uncompressedSize, 12);
  490. return buffer;
  491. } else {
  492. // ZIP64 format
  493. var buffer = bufferAlloc(ZIP64_DATA_DESCRIPTOR_SIZE);
  494. // optional signature (unknown if anyone cares about this)
  495. buffer.writeUInt32LE(0x08074b50, 0);
  496. // crc-32 4 bytes
  497. buffer.writeUInt32LE(this.crc32, 4);
  498. // compressed size 8 bytes
  499. writeUInt64LE(buffer, this.compressedSize, 8);
  500. // uncompressed size 8 bytes
  501. writeUInt64LE(buffer, this.uncompressedSize, 16);
  502. return buffer;
  503. }
  504. };
  505. var CENTRAL_DIRECTORY_RECORD_FIXED_SIZE = 46;
  506. var ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE = 28;
  507. Entry.prototype.getCentralDirectoryRecord = function() {
  508. var fixedSizeStuff = bufferAlloc(CENTRAL_DIRECTORY_RECORD_FIXED_SIZE);
  509. var generalPurposeBitFlag = FILE_NAME_IS_UTF8;
  510. if (!this.crcAndFileSizeKnown) generalPurposeBitFlag |= UNKNOWN_CRC32_AND_FILE_SIZES;
  511. var normalCompressedSize = this.compressedSize;
  512. var normalUncompressedSize = this.uncompressedSize;
  513. var normalRelativeOffsetOfLocalHeader = this.relativeOffsetOfLocalHeader;
  514. var versionNeededToExtract;
  515. var zeiefBuffer;
  516. if (this.useZip64Format()) {
  517. normalCompressedSize = 0xffffffff;
  518. normalUncompressedSize = 0xffffffff;
  519. normalRelativeOffsetOfLocalHeader = 0xffffffff;
  520. versionNeededToExtract = VERSION_NEEDED_TO_EXTRACT_ZIP64;
  521. // ZIP64 extended information extra field
  522. zeiefBuffer = bufferAlloc(ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE);
  523. // 0x0001 2 bytes Tag for this "extra" block type
  524. zeiefBuffer.writeUInt16LE(0x0001, 0);
  525. // Size 2 bytes Size of this "extra" block
  526. zeiefBuffer.writeUInt16LE(ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE - 4, 2);
  527. // Original Size 8 bytes Original uncompressed file size
  528. writeUInt64LE(zeiefBuffer, this.uncompressedSize, 4);
  529. // Compressed Size 8 bytes Size of compressed data
  530. writeUInt64LE(zeiefBuffer, this.compressedSize, 12);
  531. // Relative Header Offset 8 bytes Offset of local header record
  532. writeUInt64LE(zeiefBuffer, this.relativeOffsetOfLocalHeader, 20);
  533. // Disk Start Number 4 bytes Number of the disk on which this file starts
  534. // (omit)
  535. } else {
  536. versionNeededToExtract = VERSION_NEEDED_TO_EXTRACT_UTF8;
  537. zeiefBuffer = EMPTY_BUFFER;
  538. }
  539. // central file header signature 4 bytes (0x02014b50)
  540. fixedSizeStuff.writeUInt32LE(0x02014b50, 0);
  541. // version made by 2 bytes
  542. fixedSizeStuff.writeUInt16LE(VERSION_MADE_BY, 4);
  543. // version needed to extract 2 bytes
  544. fixedSizeStuff.writeUInt16LE(versionNeededToExtract, 6);
  545. // general purpose bit flag 2 bytes
  546. fixedSizeStuff.writeUInt16LE(generalPurposeBitFlag, 8);
  547. // compression method 2 bytes
  548. fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 10);
  549. // last mod file time 2 bytes
  550. fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 12);
  551. // last mod file date 2 bytes
  552. fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 14);
  553. // crc-32 4 bytes
  554. fixedSizeStuff.writeUInt32LE(this.crc32, 16);
  555. // compressed size 4 bytes
  556. fixedSizeStuff.writeUInt32LE(normalCompressedSize, 20);
  557. // uncompressed size 4 bytes
  558. fixedSizeStuff.writeUInt32LE(normalUncompressedSize, 24);
  559. // file name length 2 bytes
  560. fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 28);
  561. // extra field length 2 bytes
  562. fixedSizeStuff.writeUInt16LE(zeiefBuffer.length, 30);
  563. // file comment length 2 bytes
  564. fixedSizeStuff.writeUInt16LE(this.fileComment.length, 32);
  565. // disk number start 2 bytes
  566. fixedSizeStuff.writeUInt16LE(0, 34);
  567. // internal file attributes 2 bytes
  568. fixedSizeStuff.writeUInt16LE(0, 36);
  569. // external file attributes 4 bytes
  570. fixedSizeStuff.writeUInt32LE(this.externalFileAttributes, 38);
  571. // relative offset of local header 4 bytes
  572. fixedSizeStuff.writeUInt32LE(normalRelativeOffsetOfLocalHeader, 42);
  573. return Buffer.concat([
  574. fixedSizeStuff,
  575. // file name (variable size)
  576. this.utf8FileName,
  577. // extra field (variable size)
  578. zeiefBuffer,
  579. // file comment (variable size)
  580. this.fileComment,
  581. ]);
  582. };
  583. Entry.prototype.getCompressionMethod = function() {
  584. var NO_COMPRESSION = 0;
  585. var DEFLATE_COMPRESSION = 8;
  586. return this.compress ? DEFLATE_COMPRESSION : NO_COMPRESSION;
  587. };
  588. function dateToDosDateTime(jsDate) {
  589. var date = 0;
  590. date |= jsDate.getDate() & 0x1f; // 1-31
  591. date |= ((jsDate.getMonth() + 1) & 0xf) << 5; // 0-11, 1-12
  592. date |= ((jsDate.getFullYear() - 1980) & 0x7f) << 9; // 0-128, 1980-2108
  593. var time = 0;
  594. time |= Math.floor(jsDate.getSeconds() / 2); // 0-59, 0-29 (lose odd numbers)
  595. time |= (jsDate.getMinutes() & 0x3f) << 5; // 0-59
  596. time |= (jsDate.getHours() & 0x1f) << 11; // 0-23
  597. return {date: date, time: time};
  598. }
  599. function writeUInt64LE(buffer, n, offset) {
  600. // can't use bitshift here, because JavaScript only allows bitshifting on 32-bit integers.
  601. var high = Math.floor(n / 0x100000000);
  602. var low = n % 0x100000000;
  603. buffer.writeUInt32LE(low, offset);
  604. buffer.writeUInt32LE(high, offset + 4);
  605. }
  606. function defaultCallback(err) {
  607. if (err) throw err;
  608. }
  609. util.inherits(ByteCounter, Transform);
  610. function ByteCounter(options) {
  611. Transform.call(this, options);
  612. this.byteCount = 0;
  613. }
  614. ByteCounter.prototype._transform = function(chunk, encoding, cb) {
  615. this.byteCount += chunk.length;
  616. cb(null, chunk);
  617. };
  618. util.inherits(Crc32Watcher, Transform);
  619. function Crc32Watcher(options) {
  620. Transform.call(this, options);
  621. this.crc32 = 0;
  622. }
  623. Crc32Watcher.prototype._transform = function(chunk, encoding, cb) {
  624. this.crc32 = crc32.unsigned(chunk, this.crc32);
  625. cb(null, chunk);
  626. };
  627. var cp437 = '\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ';
  628. if (cp437.length !== 256) throw new Error("assertion failure");
  629. var reverseCp437 = null;
  630. function encodeCp437(string) {
  631. if (/^[\x20-\x7e]*$/.test(string)) {
  632. // CP437, ASCII, and UTF-8 overlap in this range.
  633. return bufferFrom(string, "utf-8");
  634. }
  635. // This is the slow path.
  636. if (reverseCp437 == null) {
  637. // cache this once
  638. reverseCp437 = {};
  639. for (var i = 0; i < cp437.length; i++) {
  640. reverseCp437[cp437[i]] = i;
  641. }
  642. }
  643. var result = bufferAlloc(string.length);
  644. for (var i = 0; i < string.length; i++) {
  645. var b = reverseCp437[string[i]];
  646. if (b == null) throw new Error("character not encodable in CP437: " + JSON.stringify(string[i]));
  647. result[i] = b;
  648. }
  649. return result;
  650. }
  651. function bufferAlloc(size) {
  652. bufferAlloc = modern;
  653. try {
  654. return bufferAlloc(size);
  655. } catch (e) {
  656. bufferAlloc = legacy;
  657. return bufferAlloc(size);
  658. }
  659. function modern(size) {
  660. return Buffer.allocUnsafe(size);
  661. }
  662. function legacy(size) {
  663. return new Buffer(size);
  664. }
  665. }
  666. function bufferFrom(something, encoding) {
  667. bufferFrom = modern;
  668. try {
  669. return bufferFrom(something, encoding);
  670. } catch (e) {
  671. bufferFrom = legacy;
  672. return bufferFrom(something, encoding);
  673. }
  674. function modern(something, encoding) {
  675. return Buffer.from(something, encoding);
  676. }
  677. function legacy(something, encoding) {
  678. return new Buffer(something, encoding);
  679. }
  680. }
  681. function bufferIncludes(buffer, content) {
  682. bufferIncludes = modern;
  683. try {
  684. return bufferIncludes(buffer, content);
  685. } catch (e) {
  686. bufferIncludes = legacy;
  687. return bufferIncludes(buffer, content);
  688. }
  689. function modern(buffer, content) {
  690. return buffer.includes(content);
  691. }
  692. function legacy(buffer, content) {
  693. for (var i = 0; i <= buffer.length - content.length; i++) {
  694. for (var j = 0;; j++) {
  695. if (j === content.length) return true;
  696. if (buffer[i + j] !== content[j]) break;
  697. }
  698. }
  699. return false;
  700. }
  701. }