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
			| 
											2 years ago
										 | 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; |