parse.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. const util = require('util');
  2. const zlib = require('zlib');
  3. const Stream = require('stream');
  4. const PullStream = require('./PullStream');
  5. const NoopStream = require('./NoopStream');
  6. const BufferStream = require('./BufferStream');
  7. const parseExtraField = require('./parseExtraField');
  8. const parseDateTime = require('./parseDateTime');
  9. const pipeline = Stream.pipeline;
  10. const parseBuffer = require('./parseBuffer');
  11. const endDirectorySignature = Buffer.alloc(4);
  12. endDirectorySignature.writeUInt32LE(0x06054b50, 0);
  13. function Parse(opts) {
  14. if (!(this instanceof Parse)) {
  15. return new Parse(opts);
  16. }
  17. const self = this;
  18. self._opts = opts || { verbose: false };
  19. PullStream.call(self, self._opts);
  20. self.on('finish', function() {
  21. self.emit('end');
  22. self.emit('close');
  23. });
  24. self._readRecord().catch(function(e) {
  25. if (!self.__emittedError || self.__emittedError !== e)
  26. self.emit('error', e);
  27. });
  28. }
  29. util.inherits(Parse, PullStream);
  30. Parse.prototype._readRecord = function () {
  31. const self = this;
  32. return self.pull(4).then(function(data) {
  33. if (data.length === 0)
  34. return;
  35. const signature = data.readUInt32LE(0);
  36. if (signature === 0x34327243) {
  37. return self._readCrxHeader();
  38. }
  39. if (signature === 0x04034b50) {
  40. return self._readFile();
  41. }
  42. else if (signature === 0x02014b50) {
  43. self.reachedCD = true;
  44. return self._readCentralDirectoryFileHeader();
  45. }
  46. else if (signature === 0x06054b50) {
  47. return self._readEndOfCentralDirectoryRecord();
  48. }
  49. else if (self.reachedCD) {
  50. // _readEndOfCentralDirectoryRecord expects the EOCD
  51. // signature to be consumed so set includeEof=true
  52. const includeEof = true;
  53. return self.pull(endDirectorySignature, includeEof).then(function() {
  54. return self._readEndOfCentralDirectoryRecord();
  55. });
  56. }
  57. else
  58. self.emit('error', new Error('invalid signature: 0x' + signature.toString(16)));
  59. }).then((function(loop) {
  60. if(loop) {
  61. return self._readRecord();
  62. }
  63. }));
  64. };
  65. Parse.prototype._readCrxHeader = function() {
  66. const self = this;
  67. return self.pull(12).then(function(data) {
  68. self.crxHeader = parseBuffer.parse(data, [
  69. ['version', 4],
  70. ['pubKeyLength', 4],
  71. ['signatureLength', 4],
  72. ]);
  73. return self.pull(self.crxHeader.pubKeyLength + self.crxHeader.signatureLength);
  74. }).then(function(data) {
  75. self.crxHeader.publicKey = data.slice(0, self.crxHeader.pubKeyLength);
  76. self.crxHeader.signature = data.slice(self.crxHeader.pubKeyLength);
  77. self.emit('crx-header', self.crxHeader);
  78. return true;
  79. });
  80. };
  81. Parse.prototype._readFile = function () {
  82. const self = this;
  83. return self.pull(26).then(function(data) {
  84. const vars = parseBuffer.parse(data, [
  85. ['versionsNeededToExtract', 2],
  86. ['flags', 2],
  87. ['compressionMethod', 2],
  88. ['lastModifiedTime', 2],
  89. ['lastModifiedDate', 2],
  90. ['crc32', 4],
  91. ['compressedSize', 4],
  92. ['uncompressedSize', 4],
  93. ['fileNameLength', 2],
  94. ['extraFieldLength', 2],
  95. ]);
  96. vars.lastModifiedDateTime = parseDateTime(vars.lastModifiedDate, vars.lastModifiedTime);
  97. if (self.crxHeader) vars.crxHeader = self.crxHeader;
  98. return self.pull(vars.fileNameLength).then(function(fileNameBuffer) {
  99. const fileName = fileNameBuffer.toString('utf8');
  100. const entry = Stream.PassThrough();
  101. let __autodraining = false;
  102. entry.autodrain = function() {
  103. __autodraining = true;
  104. const draining = entry.pipe(NoopStream());
  105. draining.promise = function() {
  106. return new Promise(function(resolve, reject) {
  107. draining.on('finish', resolve);
  108. draining.on('error', reject);
  109. });
  110. };
  111. return draining;
  112. };
  113. entry.buffer = function() {
  114. return BufferStream(entry);
  115. };
  116. entry.path = fileName;
  117. entry.props = {};
  118. entry.props.path = fileName;
  119. entry.props.pathBuffer = fileNameBuffer;
  120. entry.props.flags = {
  121. "isUnicode": (vars.flags & 0x800) != 0
  122. };
  123. entry.type = (vars.uncompressedSize === 0 && /[/\\]$/.test(fileName)) ? 'Directory' : 'File';
  124. if (self._opts.verbose) {
  125. if (entry.type === 'Directory') {
  126. console.log(' creating:', fileName);
  127. } else if (entry.type === 'File') {
  128. if (vars.compressionMethod === 0) {
  129. console.log(' extracting:', fileName);
  130. } else {
  131. console.log(' inflating:', fileName);
  132. }
  133. }
  134. }
  135. return self.pull(vars.extraFieldLength).then(function(extraField) {
  136. const extra = parseExtraField(extraField, vars);
  137. entry.vars = vars;
  138. entry.extra = extra;
  139. if (self._opts.forceStream) {
  140. self.push(entry);
  141. } else {
  142. self.emit('entry', entry);
  143. if (self._readableState.pipesCount || (self._readableState.pipes && self._readableState.pipes.length))
  144. self.push(entry);
  145. }
  146. if (self._opts.verbose)
  147. console.log({
  148. filename:fileName,
  149. vars: vars,
  150. extra: extra
  151. });
  152. const fileSizeKnown = !(vars.flags & 0x08) || vars.compressedSize > 0;
  153. let eof;
  154. entry.__autodraining = __autodraining; // expose __autodraining for test purposes
  155. const inflater = (vars.compressionMethod && !__autodraining) ? zlib.createInflateRaw() : Stream.PassThrough();
  156. if (fileSizeKnown) {
  157. entry.size = vars.uncompressedSize;
  158. eof = vars.compressedSize;
  159. } else {
  160. eof = Buffer.alloc(4);
  161. eof.writeUInt32LE(0x08074b50, 0);
  162. }
  163. return new Promise(function(resolve, reject) {
  164. pipeline(
  165. self.stream(eof),
  166. inflater,
  167. entry,
  168. function (err) {
  169. if (err) {
  170. return reject(err);
  171. }
  172. return fileSizeKnown ? resolve(fileSizeKnown) : self._processDataDescriptor(entry).then(resolve).catch(reject);
  173. }
  174. );
  175. });
  176. });
  177. });
  178. });
  179. };
  180. Parse.prototype._processDataDescriptor = function (entry) {
  181. const self = this;
  182. return self.pull(16).then(function(data) {
  183. const vars = parseBuffer.parse(data, [
  184. ['dataDescriptorSignature', 4],
  185. ['crc32', 4],
  186. ['compressedSize', 4],
  187. ['uncompressedSize', 4],
  188. ]);
  189. entry.size = vars.uncompressedSize;
  190. return true;
  191. });
  192. };
  193. Parse.prototype._readCentralDirectoryFileHeader = function () {
  194. const self = this;
  195. return self.pull(42).then(function(data) {
  196. const vars = parseBuffer.parse(data, [
  197. ['versionMadeBy', 2],
  198. ['versionsNeededToExtract', 2],
  199. ['flags', 2],
  200. ['compressionMethod', 2],
  201. ['lastModifiedTime', 2],
  202. ['lastModifiedDate', 2],
  203. ['crc32', 4],
  204. ['compressedSize', 4],
  205. ['uncompressedSize', 4],
  206. ['fileNameLength', 2],
  207. ['extraFieldLength', 2],
  208. ['fileCommentLength', 2],
  209. ['diskNumber', 2],
  210. ['internalFileAttributes', 2],
  211. ['externalFileAttributes', 4],
  212. ['offsetToLocalFileHeader', 4],
  213. ]);
  214. return self.pull(vars.fileNameLength).then(function(fileName) {
  215. vars.fileName = fileName.toString('utf8');
  216. return self.pull(vars.extraFieldLength);
  217. })
  218. .then(function() {
  219. return self.pull(vars.fileCommentLength);
  220. })
  221. .then(function() {
  222. return true;
  223. });
  224. });
  225. };
  226. Parse.prototype._readEndOfCentralDirectoryRecord = function() {
  227. const self = this;
  228. return self.pull(18).then(function(data) {
  229. const vars = parseBuffer.parse(data, [
  230. ['diskNumber', 2],
  231. ['diskStart', 2],
  232. ['numberOfRecordsOnDisk', 2],
  233. ['numberOfRecords', 2],
  234. ['sizeOfCentralDirectory', 4],
  235. ['offsetToStartOfCentralDirectory', 4],
  236. ['commentLength', 2],
  237. ]);
  238. return self.pull(vars.commentLength).then(function() {
  239. self.end();
  240. self.push(null);
  241. });
  242. });
  243. };
  244. Parse.prototype.promise = function() {
  245. const self = this;
  246. return new Promise(function(resolve, reject) {
  247. self.on('finish', resolve);
  248. self.on('error', reject);
  249. });
  250. };
  251. module.exports = Parse;