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.
		
		
		
		
		
			
		
			
				
					
					
						
							288 lines
						
					
					
						
							8.4 KiB
						
					
					
				
			
		
		
	
	
							288 lines
						
					
					
						
							8.4 KiB
						
					
					
				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;
 |