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;
 |