| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- const util = require('util');
- const zlib = require('zlib');
- const Stream = require('stream');
- const PullStream = require('./PullStream');
- const NoopStream = require('./NoopStream');
- const BufferStream = require('./BufferStream');
- const parseExtraField = require('./parseExtraField');
- const parseDateTime = require('./parseDateTime');
- const pipeline = Stream.pipeline;
- const parseBuffer = require('./parseBuffer');
- const endDirectorySignature = Buffer.alloc(4);
- endDirectorySignature.writeUInt32LE(0x06054b50, 0);
- function Parse(opts) {
- if (!(this instanceof Parse)) {
- return new Parse(opts);
- }
- const self = this;
- self._opts = opts || { verbose: false };
- PullStream.call(self, self._opts);
- self.on('finish', function() {
- self.emit('end');
- self.emit('close');
- });
- self._readRecord().catch(function(e) {
- if (!self.__emittedError || self.__emittedError !== e)
- self.emit('error', e);
- });
- }
- util.inherits(Parse, PullStream);
- Parse.prototype._readRecord = function () {
- const self = this;
- return self.pull(4).then(function(data) {
- if (data.length === 0)
- return;
- const signature = data.readUInt32LE(0);
- if (signature === 0x34327243) {
- return self._readCrxHeader();
- }
- if (signature === 0x04034b50) {
- return self._readFile();
- }
- else if (signature === 0x02014b50) {
- self.reachedCD = true;
- return self._readCentralDirectoryFileHeader();
- }
- else if (signature === 0x06054b50) {
- return self._readEndOfCentralDirectoryRecord();
- }
- else if (self.reachedCD) {
- // _readEndOfCentralDirectoryRecord expects the EOCD
- // signature to be consumed so set includeEof=true
- const includeEof = true;
- return self.pull(endDirectorySignature, includeEof).then(function() {
- return self._readEndOfCentralDirectoryRecord();
- });
- }
- else
- self.emit('error', new Error('invalid signature: 0x' + signature.toString(16)));
- }).then((function(loop) {
- if(loop) {
- return self._readRecord();
- }
- }));
- };
- Parse.prototype._readCrxHeader = function() {
- const self = this;
- return self.pull(12).then(function(data) {
- self.crxHeader = parseBuffer.parse(data, [
- ['version', 4],
- ['pubKeyLength', 4],
- ['signatureLength', 4],
- ]);
- return self.pull(self.crxHeader.pubKeyLength + self.crxHeader.signatureLength);
- }).then(function(data) {
- self.crxHeader.publicKey = data.slice(0, self.crxHeader.pubKeyLength);
- self.crxHeader.signature = data.slice(self.crxHeader.pubKeyLength);
- self.emit('crx-header', self.crxHeader);
- return true;
- });
- };
- Parse.prototype._readFile = function () {
- const self = this;
- return self.pull(26).then(function(data) {
- const vars = parseBuffer.parse(data, [
- ['versionsNeededToExtract', 2],
- ['flags', 2],
- ['compressionMethod', 2],
- ['lastModifiedTime', 2],
- ['lastModifiedDate', 2],
- ['crc32', 4],
- ['compressedSize', 4],
- ['uncompressedSize', 4],
- ['fileNameLength', 2],
- ['extraFieldLength', 2],
- ]);
- vars.lastModifiedDateTime = parseDateTime(vars.lastModifiedDate, vars.lastModifiedTime);
- if (self.crxHeader) vars.crxHeader = self.crxHeader;
- return self.pull(vars.fileNameLength).then(function(fileNameBuffer) {
- const fileName = fileNameBuffer.toString('utf8');
- const entry = Stream.PassThrough();
- let __autodraining = false;
- entry.autodrain = function() {
- __autodraining = true;
- const draining = entry.pipe(NoopStream());
- draining.promise = function() {
- return new Promise(function(resolve, reject) {
- draining.on('finish', resolve);
- draining.on('error', reject);
- });
- };
- return draining;
- };
- entry.buffer = function() {
- return BufferStream(entry);
- };
- entry.path = fileName;
- entry.props = {};
- entry.props.path = fileName;
- entry.props.pathBuffer = fileNameBuffer;
- entry.props.flags = {
- "isUnicode": (vars.flags & 0x800) != 0
- };
- entry.type = (vars.uncompressedSize === 0 && /[/\\]$/.test(fileName)) ? 'Directory' : 'File';
- if (self._opts.verbose) {
- if (entry.type === 'Directory') {
- console.log(' creating:', fileName);
- } else if (entry.type === 'File') {
- if (vars.compressionMethod === 0) {
- console.log(' extracting:', fileName);
- } else {
- console.log(' inflating:', fileName);
- }
- }
- }
- return self.pull(vars.extraFieldLength).then(function(extraField) {
- const extra = parseExtraField(extraField, vars);
- entry.vars = vars;
- entry.extra = extra;
- if (self._opts.forceStream) {
- self.push(entry);
- } else {
- self.emit('entry', entry);
- if (self._readableState.pipesCount || (self._readableState.pipes && self._readableState.pipes.length))
- self.push(entry);
- }
- if (self._opts.verbose)
- console.log({
- filename:fileName,
- vars: vars,
- extra: extra
- });
- const fileSizeKnown = !(vars.flags & 0x08) || vars.compressedSize > 0;
- let eof;
- entry.__autodraining = __autodraining; // expose __autodraining for test purposes
- const inflater = (vars.compressionMethod && !__autodraining) ? zlib.createInflateRaw() : Stream.PassThrough();
- if (fileSizeKnown) {
- entry.size = vars.uncompressedSize;
- eof = vars.compressedSize;
- } else {
- eof = Buffer.alloc(4);
- eof.writeUInt32LE(0x08074b50, 0);
- }
- return new Promise(function(resolve, reject) {
- pipeline(
- self.stream(eof),
- inflater,
- entry,
- function (err) {
- if (err) {
- return reject(err);
- }
- return fileSizeKnown ? resolve(fileSizeKnown) : self._processDataDescriptor(entry).then(resolve).catch(reject);
- }
- );
- });
- });
- });
- });
- };
- Parse.prototype._processDataDescriptor = function (entry) {
- const self = this;
- return self.pull(16).then(function(data) {
- const vars = parseBuffer.parse(data, [
- ['dataDescriptorSignature', 4],
- ['crc32', 4],
- ['compressedSize', 4],
- ['uncompressedSize', 4],
- ]);
- entry.size = vars.uncompressedSize;
- return true;
- });
- };
- Parse.prototype._readCentralDirectoryFileHeader = function () {
- const self = this;
- return self.pull(42).then(function(data) {
- const vars = parseBuffer.parse(data, [
- ['versionMadeBy', 2],
- ['versionsNeededToExtract', 2],
- ['flags', 2],
- ['compressionMethod', 2],
- ['lastModifiedTime', 2],
- ['lastModifiedDate', 2],
- ['crc32', 4],
- ['compressedSize', 4],
- ['uncompressedSize', 4],
- ['fileNameLength', 2],
- ['extraFieldLength', 2],
- ['fileCommentLength', 2],
- ['diskNumber', 2],
- ['internalFileAttributes', 2],
- ['externalFileAttributes', 4],
- ['offsetToLocalFileHeader', 4],
- ]);
- return self.pull(vars.fileNameLength).then(function(fileName) {
- vars.fileName = fileName.toString('utf8');
- return self.pull(vars.extraFieldLength);
- })
- .then(function() {
- return self.pull(vars.fileCommentLength);
- })
- .then(function() {
- return true;
- });
- });
- };
- Parse.prototype._readEndOfCentralDirectoryRecord = function() {
- const self = this;
- return self.pull(18).then(function(data) {
- const vars = parseBuffer.parse(data, [
- ['diskNumber', 2],
- ['diskStart', 2],
- ['numberOfRecordsOnDisk', 2],
- ['numberOfRecords', 2],
- ['sizeOfCentralDirectory', 4],
- ['offsetToStartOfCentralDirectory', 4],
- ['commentLength', 2],
- ]);
- return self.pull(vars.commentLength).then(function() {
- self.end();
- self.push(null);
- });
- });
- };
- Parse.prototype.promise = function() {
- const self = this;
- return new Promise(function(resolve, reject) {
- self.on('finish', resolve);
- self.on('error', reject);
- });
- };
- module.exports = Parse;
|