parse.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. var util = require('util');
  2. var zlib = require('zlib');
  3. var Stream = require('stream');
  4. var binary = require('binary');
  5. var Promise = require('bluebird');
  6. var PullStream = require('./PullStream');
  7. var NoopStream = require('./NoopStream');
  8. var BufferStream = require('./BufferStream');
  9. var parseExtraField = require('./parseExtraField');
  10. var Buffer = require('./Buffer');
  11. var parseDateTime = require('./parseDateTime');
  12. // Backwards compatibility for node versions < 8
  13. if (!Stream.Writable || !Stream.Writable.prototype.destroy)
  14. Stream = require('readable-stream');
  15. var endDirectorySignature = Buffer.alloc(4);
  16. endDirectorySignature.writeUInt32LE(0x06054b50, 0);
  17. function Parse(opts) {
  18. if (!(this instanceof Parse)) {
  19. return new Parse(opts);
  20. }
  21. var self = this;
  22. self._opts = opts || { verbose: false };
  23. PullStream.call(self, self._opts);
  24. self.on('finish',function() {
  25. self.emit('end');
  26. self.emit('close');
  27. });
  28. self._readRecord().catch(function(e) {
  29. if (!self.__emittedError || self.__emittedError !== e)
  30. self.emit('error',e);
  31. });
  32. }
  33. util.inherits(Parse, PullStream);
  34. Parse.prototype._readRecord = function () {
  35. var self = this;
  36. return self.pull(4).then(function(data) {
  37. if (data.length === 0)
  38. return;
  39. var signature = data.readUInt32LE(0);
  40. if (signature === 0x34327243) {
  41. return self._readCrxHeader();
  42. }
  43. if (signature === 0x04034b50) {
  44. return self._readFile();
  45. }
  46. else if (signature === 0x02014b50) {
  47. self.reachedCD = true;
  48. return self._readCentralDirectoryFileHeader();
  49. }
  50. else if (signature === 0x06054b50) {
  51. return self._readEndOfCentralDirectoryRecord();
  52. }
  53. else if (self.reachedCD) {
  54. // _readEndOfCentralDirectoryRecord expects the EOCD
  55. // signature to be consumed so set includeEof=true
  56. var includeEof = true;
  57. return self.pull(endDirectorySignature, includeEof).then(function() {
  58. return self._readEndOfCentralDirectoryRecord();
  59. });
  60. }
  61. else
  62. self.emit('error', new Error('invalid signature: 0x' + signature.toString(16)));
  63. });
  64. };
  65. Parse.prototype._readCrxHeader = function() {
  66. var self = this;
  67. return self.pull(12).then(function(data) {
  68. self.crxHeader = binary.parse(data)
  69. .word32lu('version')
  70. .word32lu('pubKeyLength')
  71. .word32lu('signatureLength')
  72. .vars;
  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 self._readRecord();
  79. });
  80. };
  81. Parse.prototype._readFile = function () {
  82. var self = this;
  83. return self.pull(26).then(function(data) {
  84. var vars = binary.parse(data)
  85. .word16lu('versionsNeededToExtract')
  86. .word16lu('flags')
  87. .word16lu('compressionMethod')
  88. .word16lu('lastModifiedTime')
  89. .word16lu('lastModifiedDate')
  90. .word32lu('crc32')
  91. .word32lu('compressedSize')
  92. .word32lu('uncompressedSize')
  93. .word16lu('fileNameLength')
  94. .word16lu('extraFieldLength')
  95. .vars;
  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. var fileName = fileNameBuffer.toString('utf8');
  100. var entry = Stream.PassThrough();
  101. var __autodraining = false;
  102. entry.autodrain = function() {
  103. __autodraining = true;
  104. var 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. var 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. var fileSizeKnown = !(vars.flags & 0x08) || vars.compressedSize > 0,
  153. eof;
  154. entry.__autodraining = __autodraining; // expose __autodraining for test purposes
  155. var 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. self.stream(eof)
  165. .pipe(inflater)
  166. .on('error',function(err) { self.emit('error',err);})
  167. .pipe(entry)
  168. .on('finish', function() {
  169. return fileSizeKnown ?
  170. self._readRecord().then(resolve).catch(reject) :
  171. self._processDataDescriptor(entry).then(resolve).catch(reject);
  172. });
  173. });
  174. });
  175. });
  176. });
  177. };
  178. Parse.prototype._processDataDescriptor = function (entry) {
  179. var self = this;
  180. return self.pull(16).then(function(data) {
  181. var vars = binary.parse(data)
  182. .word32lu('dataDescriptorSignature')
  183. .word32lu('crc32')
  184. .word32lu('compressedSize')
  185. .word32lu('uncompressedSize')
  186. .vars;
  187. entry.size = vars.uncompressedSize;
  188. return self._readRecord();
  189. });
  190. };
  191. Parse.prototype._readCentralDirectoryFileHeader = function () {
  192. var self = this;
  193. return self.pull(42).then(function(data) {
  194. var vars = binary.parse(data)
  195. .word16lu('versionMadeBy')
  196. .word16lu('versionsNeededToExtract')
  197. .word16lu('flags')
  198. .word16lu('compressionMethod')
  199. .word16lu('lastModifiedTime')
  200. .word16lu('lastModifiedDate')
  201. .word32lu('crc32')
  202. .word32lu('compressedSize')
  203. .word32lu('uncompressedSize')
  204. .word16lu('fileNameLength')
  205. .word16lu('extraFieldLength')
  206. .word16lu('fileCommentLength')
  207. .word16lu('diskNumber')
  208. .word16lu('internalFileAttributes')
  209. .word32lu('externalFileAttributes')
  210. .word32lu('offsetToLocalFileHeader')
  211. .vars;
  212. return self.pull(vars.fileNameLength).then(function(fileName) {
  213. vars.fileName = fileName.toString('utf8');
  214. return self.pull(vars.extraFieldLength);
  215. })
  216. .then(function(extraField) {
  217. return self.pull(vars.fileCommentLength);
  218. })
  219. .then(function(fileComment) {
  220. return self._readRecord();
  221. });
  222. });
  223. };
  224. Parse.prototype._readEndOfCentralDirectoryRecord = function() {
  225. var self = this;
  226. return self.pull(18).then(function(data) {
  227. var vars = binary.parse(data)
  228. .word16lu('diskNumber')
  229. .word16lu('diskStart')
  230. .word16lu('numberOfRecordsOnDisk')
  231. .word16lu('numberOfRecords')
  232. .word32lu('sizeOfCentralDirectory')
  233. .word32lu('offsetToStartOfCentralDirectory')
  234. .word16lu('commentLength')
  235. .vars;
  236. return self.pull(vars.commentLength).then(function(comment) {
  237. comment = comment.toString('utf8');
  238. self.end();
  239. self.push(null);
  240. });
  241. });
  242. };
  243. Parse.prototype.promise = function() {
  244. var self = this;
  245. return new Promise(function(resolve,reject) {
  246. self.on('finish',resolve);
  247. self.on('error',reject);
  248. });
  249. };
  250. module.exports = Parse;