| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- var util = require('util');
- var zlib = require('zlib');
- var Stream = require('stream');
- var binary = require('binary');
- var Promise = require('bluebird');
- var PullStream = require('./PullStream');
- var NoopStream = require('./NoopStream');
- var BufferStream = require('./BufferStream');
- var parseExtraField = require('./parseExtraField');
- var Buffer = require('./Buffer');
- var parseDateTime = require('./parseDateTime');
- // Backwards compatibility for node versions < 8
- if (!Stream.Writable || !Stream.Writable.prototype.destroy)
- Stream = require('readable-stream');
- var endDirectorySignature = Buffer.alloc(4);
- endDirectorySignature.writeUInt32LE(0x06054b50, 0);
- function Parse(opts) {
- if (!(this instanceof Parse)) {
- return new Parse(opts);
- }
- var 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 () {
- var self = this;
- return self.pull(4).then(function(data) {
- if (data.length === 0)
- return;
- var 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
- var includeEof = true;
- return self.pull(endDirectorySignature, includeEof).then(function() {
- return self._readEndOfCentralDirectoryRecord();
- });
- }
- else
- self.emit('error', new Error('invalid signature: 0x' + signature.toString(16)));
- });
- };
- Parse.prototype._readCrxHeader = function() {
- var self = this;
- return self.pull(12).then(function(data) {
- self.crxHeader = binary.parse(data)
- .word32lu('version')
- .word32lu('pubKeyLength')
- .word32lu('signatureLength')
- .vars;
- 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 self._readRecord();
- });
- };
- Parse.prototype._readFile = function () {
- var self = this;
- return self.pull(26).then(function(data) {
- var vars = binary.parse(data)
- .word16lu('versionsNeededToExtract')
- .word16lu('flags')
- .word16lu('compressionMethod')
- .word16lu('lastModifiedTime')
- .word16lu('lastModifiedDate')
- .word32lu('crc32')
- .word32lu('compressedSize')
- .word32lu('uncompressedSize')
- .word16lu('fileNameLength')
- .word16lu('extraFieldLength')
- .vars;
- vars.lastModifiedDateTime = parseDateTime(vars.lastModifiedDate, vars.lastModifiedTime);
- if (self.crxHeader) vars.crxHeader = self.crxHeader;
- return self.pull(vars.fileNameLength).then(function(fileNameBuffer) {
- var fileName = fileNameBuffer.toString('utf8');
- var entry = Stream.PassThrough();
- var __autodraining = false;
- entry.autodrain = function() {
- __autodraining = true;
- var 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) {
- var 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
- });
- var fileSizeKnown = !(vars.flags & 0x08) || vars.compressedSize > 0,
- eof;
- entry.__autodraining = __autodraining; // expose __autodraining for test purposes
- var 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) {
- self.stream(eof)
- .pipe(inflater)
- .on('error',function(err) { self.emit('error',err);})
- .pipe(entry)
- .on('finish', function() {
- return fileSizeKnown ?
- self._readRecord().then(resolve).catch(reject) :
- self._processDataDescriptor(entry).then(resolve).catch(reject);
- });
- });
- });
- });
- });
- };
- Parse.prototype._processDataDescriptor = function (entry) {
- var self = this;
- return self.pull(16).then(function(data) {
- var vars = binary.parse(data)
- .word32lu('dataDescriptorSignature')
- .word32lu('crc32')
- .word32lu('compressedSize')
- .word32lu('uncompressedSize')
- .vars;
- entry.size = vars.uncompressedSize;
- return self._readRecord();
- });
- };
- Parse.prototype._readCentralDirectoryFileHeader = function () {
- var self = this;
- return self.pull(42).then(function(data) {
- var vars = binary.parse(data)
- .word16lu('versionMadeBy')
- .word16lu('versionsNeededToExtract')
- .word16lu('flags')
- .word16lu('compressionMethod')
- .word16lu('lastModifiedTime')
- .word16lu('lastModifiedDate')
- .word32lu('crc32')
- .word32lu('compressedSize')
- .word32lu('uncompressedSize')
- .word16lu('fileNameLength')
- .word16lu('extraFieldLength')
- .word16lu('fileCommentLength')
- .word16lu('diskNumber')
- .word16lu('internalFileAttributes')
- .word32lu('externalFileAttributes')
- .word32lu('offsetToLocalFileHeader')
- .vars;
- return self.pull(vars.fileNameLength).then(function(fileName) {
- vars.fileName = fileName.toString('utf8');
- return self.pull(vars.extraFieldLength);
- })
- .then(function(extraField) {
- return self.pull(vars.fileCommentLength);
- })
- .then(function(fileComment) {
- return self._readRecord();
- });
- });
- };
- Parse.prototype._readEndOfCentralDirectoryRecord = function() {
- var self = this;
- return self.pull(18).then(function(data) {
- var vars = binary.parse(data)
- .word16lu('diskNumber')
- .word16lu('diskStart')
- .word16lu('numberOfRecordsOnDisk')
- .word16lu('numberOfRecords')
- .word32lu('sizeOfCentralDirectory')
- .word32lu('offsetToStartOfCentralDirectory')
- .word16lu('commentLength')
- .vars;
- return self.pull(vars.commentLength).then(function(comment) {
- comment = comment.toString('utf8');
- self.end();
- self.push(null);
- });
- });
- };
- Parse.prototype.promise = function() {
- var self = this;
- return new Promise(function(resolve,reject) {
- self.on('finish',resolve);
- self.on('error',reject);
- });
- };
- module.exports = Parse;
|