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.
		
		
		
		
		
			
		
			
				
					441 lines
				
				11 KiB
			
		
		
			
		
	
	
					441 lines
				
				11 KiB
			| 
											2 years ago
										 | /** | ||
|  |  * node-compress-commons | ||
|  |  * | ||
|  |  * Copyright (c) 2014 Chris Talkington, contributors. | ||
|  |  * Licensed under the MIT license. | ||
|  |  * https://github.com/archiverjs/node-compress-commons/blob/master/LICENSE-MIT
 | ||
|  |  */ | ||
|  | var inherits = require('util').inherits; | ||
|  | var crc32 = require('buffer-crc32'); | ||
|  | var {CRC32Stream} = require('crc32-stream'); | ||
|  | var {DeflateCRC32Stream} = require('crc32-stream'); | ||
|  | 
 | ||
|  | var ArchiveOutputStream = require('../archive-output-stream'); | ||
|  | var ZipArchiveEntry = require('./zip-archive-entry'); | ||
|  | var GeneralPurposeBit = require('./general-purpose-bit'); | ||
|  | 
 | ||
|  | var constants = require('./constants'); | ||
|  | var util = require('../../util'); | ||
|  | var zipUtil = require('./util'); | ||
|  | 
 | ||
|  | var ZipArchiveOutputStream = module.exports = function(options) { | ||
|  |   if (!(this instanceof ZipArchiveOutputStream)) { | ||
|  |     return new ZipArchiveOutputStream(options); | ||
|  |   } | ||
|  | 
 | ||
|  |   options = this.options = this._defaults(options); | ||
|  | 
 | ||
|  |   ArchiveOutputStream.call(this, options); | ||
|  | 
 | ||
|  |   this._entry = null; | ||
|  |   this._entries = []; | ||
|  |   this._archive = { | ||
|  |     centralLength: 0, | ||
|  |     centralOffset: 0, | ||
|  |     comment: '', | ||
|  |     finish: false, | ||
|  |     finished: false, | ||
|  |     processing: false, | ||
|  |     forceZip64: options.forceZip64, | ||
|  |     forceLocalTime: options.forceLocalTime | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | inherits(ZipArchiveOutputStream, ArchiveOutputStream); | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._afterAppend = function(ae) { | ||
|  |   this._entries.push(ae); | ||
|  | 
 | ||
|  |   if (ae.getGeneralPurposeBit().usesDataDescriptor()) { | ||
|  |     this._writeDataDescriptor(ae); | ||
|  |   } | ||
|  | 
 | ||
|  |   this._archive.processing = false; | ||
|  |   this._entry = null; | ||
|  | 
 | ||
|  |   if (this._archive.finish && !this._archive.finished) { | ||
|  |     this._finish(); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._appendBuffer = function(ae, source, callback) { | ||
|  |   if (source.length === 0) { | ||
|  |     ae.setMethod(constants.METHOD_STORED); | ||
|  |   } | ||
|  | 
 | ||
|  |   var method = ae.getMethod(); | ||
|  | 
 | ||
|  |   if (method === constants.METHOD_STORED) { | ||
|  |     ae.setSize(source.length); | ||
|  |     ae.setCompressedSize(source.length); | ||
|  |     ae.setCrc(crc32.unsigned(source)); | ||
|  |   } | ||
|  | 
 | ||
|  |   this._writeLocalFileHeader(ae); | ||
|  | 
 | ||
|  |   if (method === constants.METHOD_STORED) { | ||
|  |     this.write(source); | ||
|  |     this._afterAppend(ae); | ||
|  |     callback(null, ae); | ||
|  |     return; | ||
|  |   } else if (method === constants.METHOD_DEFLATED) { | ||
|  |     this._smartStream(ae, callback).end(source); | ||
|  |     return; | ||
|  |   } else { | ||
|  |     callback(new Error('compression method ' + method + ' not implemented')); | ||
|  |     return; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._appendStream = function(ae, source, callback) { | ||
|  |   ae.getGeneralPurposeBit().useDataDescriptor(true); | ||
|  |   ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR); | ||
|  | 
 | ||
|  |   this._writeLocalFileHeader(ae); | ||
|  | 
 | ||
|  |   var smart = this._smartStream(ae, callback); | ||
|  |   source.once('error', function(err) { | ||
|  |     smart.emit('error', err); | ||
|  |     smart.end(); | ||
|  |   }) | ||
|  |   source.pipe(smart); | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._defaults = function(o) { | ||
|  |   if (typeof o !== 'object') { | ||
|  |     o = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (typeof o.zlib !== 'object') { | ||
|  |     o.zlib = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (typeof o.zlib.level !== 'number') { | ||
|  |     o.zlib.level = constants.ZLIB_BEST_SPEED; | ||
|  |   } | ||
|  | 
 | ||
|  |   o.forceZip64 = !!o.forceZip64; | ||
|  |   o.forceLocalTime = !!o.forceLocalTime; | ||
|  | 
 | ||
|  |   return o; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._finish = function() { | ||
|  |   this._archive.centralOffset = this.offset; | ||
|  | 
 | ||
|  |   this._entries.forEach(function(ae) { | ||
|  |     this._writeCentralFileHeader(ae); | ||
|  |   }.bind(this)); | ||
|  | 
 | ||
|  |   this._archive.centralLength = this.offset - this._archive.centralOffset; | ||
|  | 
 | ||
|  |   if (this.isZip64()) { | ||
|  |     this._writeCentralDirectoryZip64(); | ||
|  |   } | ||
|  | 
 | ||
|  |   this._writeCentralDirectoryEnd(); | ||
|  | 
 | ||
|  |   this._archive.processing = false; | ||
|  |   this._archive.finish = true; | ||
|  |   this._archive.finished = true; | ||
|  |   this.end(); | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._normalizeEntry = function(ae) { | ||
|  |   if (ae.getMethod() === -1) { | ||
|  |     ae.setMethod(constants.METHOD_DEFLATED); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (ae.getMethod() === constants.METHOD_DEFLATED) { | ||
|  |     ae.getGeneralPurposeBit().useDataDescriptor(true); | ||
|  |     ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (ae.getTime() === -1) { | ||
|  |     ae.setTime(new Date(), this._archive.forceLocalTime); | ||
|  |   } | ||
|  | 
 | ||
|  |   ae._offsets = { | ||
|  |     file: 0, | ||
|  |     data: 0, | ||
|  |     contents: 0, | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._smartStream = function(ae, callback) { | ||
|  |   var deflate = ae.getMethod() === constants.METHOD_DEFLATED; | ||
|  |   var process = deflate ? new DeflateCRC32Stream(this.options.zlib) : new CRC32Stream(); | ||
|  |   var error = null; | ||
|  | 
 | ||
|  |   function handleStuff() { | ||
|  |     var digest = process.digest().readUInt32BE(0); | ||
|  |     ae.setCrc(digest); | ||
|  |     ae.setSize(process.size()); | ||
|  |     ae.setCompressedSize(process.size(true)); | ||
|  |     this._afterAppend(ae); | ||
|  |     callback(error, ae); | ||
|  |   } | ||
|  | 
 | ||
|  |   process.once('end', handleStuff.bind(this)); | ||
|  |   process.once('error', function(err) { | ||
|  |     error = err; | ||
|  |   }); | ||
|  | 
 | ||
|  |   process.pipe(this, { end: false }); | ||
|  | 
 | ||
|  |   return process; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._writeCentralDirectoryEnd = function() { | ||
|  |   var records = this._entries.length; | ||
|  |   var size = this._archive.centralLength; | ||
|  |   var offset = this._archive.centralOffset; | ||
|  | 
 | ||
|  |   if (this.isZip64()) { | ||
|  |     records = constants.ZIP64_MAGIC_SHORT; | ||
|  |     size = constants.ZIP64_MAGIC; | ||
|  |     offset = constants.ZIP64_MAGIC; | ||
|  |   } | ||
|  | 
 | ||
|  |   // signature
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_EOCD)); | ||
|  | 
 | ||
|  |   // disk numbers
 | ||
|  |   this.write(constants.SHORT_ZERO); | ||
|  |   this.write(constants.SHORT_ZERO); | ||
|  | 
 | ||
|  |   // number of entries
 | ||
|  |   this.write(zipUtil.getShortBytes(records)); | ||
|  |   this.write(zipUtil.getShortBytes(records)); | ||
|  | 
 | ||
|  |   // length and location of CD
 | ||
|  |   this.write(zipUtil.getLongBytes(size)); | ||
|  |   this.write(zipUtil.getLongBytes(offset)); | ||
|  | 
 | ||
|  |   // archive comment
 | ||
|  |   var comment = this.getComment(); | ||
|  |   var commentLength = Buffer.byteLength(comment); | ||
|  |   this.write(zipUtil.getShortBytes(commentLength)); | ||
|  |   this.write(comment); | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._writeCentralDirectoryZip64 = function() { | ||
|  |   // signature
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_ZIP64_EOCD)); | ||
|  | 
 | ||
|  |   // size of the ZIP64 EOCD record
 | ||
|  |   this.write(zipUtil.getEightBytes(44)); | ||
|  | 
 | ||
|  |   // version made by
 | ||
|  |   this.write(zipUtil.getShortBytes(constants.MIN_VERSION_ZIP64)); | ||
|  | 
 | ||
|  |   // version to extract
 | ||
|  |   this.write(zipUtil.getShortBytes(constants.MIN_VERSION_ZIP64)); | ||
|  | 
 | ||
|  |   // disk numbers
 | ||
|  |   this.write(constants.LONG_ZERO); | ||
|  |   this.write(constants.LONG_ZERO); | ||
|  | 
 | ||
|  |   // number of entries
 | ||
|  |   this.write(zipUtil.getEightBytes(this._entries.length)); | ||
|  |   this.write(zipUtil.getEightBytes(this._entries.length)); | ||
|  | 
 | ||
|  |   // length and location of CD
 | ||
|  |   this.write(zipUtil.getEightBytes(this._archive.centralLength)); | ||
|  |   this.write(zipUtil.getEightBytes(this._archive.centralOffset)); | ||
|  | 
 | ||
|  |   // extensible data sector
 | ||
|  |   // not implemented at this time
 | ||
|  | 
 | ||
|  |   // end of central directory locator
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_ZIP64_EOCD_LOC)); | ||
|  | 
 | ||
|  |   // disk number holding the ZIP64 EOCD record
 | ||
|  |   this.write(constants.LONG_ZERO); | ||
|  | 
 | ||
|  |   // relative offset of the ZIP64 EOCD record
 | ||
|  |   this.write(zipUtil.getEightBytes(this._archive.centralOffset + this._archive.centralLength)); | ||
|  | 
 | ||
|  |   // total number of disks
 | ||
|  |   this.write(zipUtil.getLongBytes(1)); | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._writeCentralFileHeader = function(ae) { | ||
|  |   var gpb = ae.getGeneralPurposeBit(); | ||
|  |   var method = ae.getMethod(); | ||
|  |   var offsets = ae._offsets; | ||
|  | 
 | ||
|  |   var size = ae.getSize(); | ||
|  |   var compressedSize = ae.getCompressedSize(); | ||
|  | 
 | ||
|  |   if (ae.isZip64() || offsets.file > constants.ZIP64_MAGIC) { | ||
|  |     size = constants.ZIP64_MAGIC; | ||
|  |     compressedSize = constants.ZIP64_MAGIC; | ||
|  | 
 | ||
|  |     ae.setVersionNeededToExtract(constants.MIN_VERSION_ZIP64); | ||
|  | 
 | ||
|  |     var extraBuf = Buffer.concat([ | ||
|  |       zipUtil.getShortBytes(constants.ZIP64_EXTRA_ID), | ||
|  |       zipUtil.getShortBytes(24), | ||
|  |       zipUtil.getEightBytes(ae.getSize()), | ||
|  |       zipUtil.getEightBytes(ae.getCompressedSize()), | ||
|  |       zipUtil.getEightBytes(offsets.file) | ||
|  |     ], 28); | ||
|  | 
 | ||
|  |     ae.setExtra(extraBuf); | ||
|  |   } | ||
|  | 
 | ||
|  |   // signature
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_CFH)); | ||
|  | 
 | ||
|  |   // version made by
 | ||
|  |   this.write(zipUtil.getShortBytes((ae.getPlatform() << 8) | constants.VERSION_MADEBY)); | ||
|  | 
 | ||
|  |   // version to extract and general bit flag
 | ||
|  |   this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract())); | ||
|  |   this.write(gpb.encode()); | ||
|  | 
 | ||
|  |   // compression method
 | ||
|  |   this.write(zipUtil.getShortBytes(method)); | ||
|  | 
 | ||
|  |   // datetime
 | ||
|  |   this.write(zipUtil.getLongBytes(ae.getTimeDos())); | ||
|  | 
 | ||
|  |   // crc32 checksum
 | ||
|  |   this.write(zipUtil.getLongBytes(ae.getCrc())); | ||
|  | 
 | ||
|  |   // sizes
 | ||
|  |   this.write(zipUtil.getLongBytes(compressedSize)); | ||
|  |   this.write(zipUtil.getLongBytes(size)); | ||
|  | 
 | ||
|  |   var name = ae.getName(); | ||
|  |   var comment = ae.getComment(); | ||
|  |   var extra = ae.getCentralDirectoryExtra(); | ||
|  | 
 | ||
|  |   if (gpb.usesUTF8ForNames()) { | ||
|  |     name = Buffer.from(name); | ||
|  |     comment = Buffer.from(comment); | ||
|  |   } | ||
|  | 
 | ||
|  |   // name length
 | ||
|  |   this.write(zipUtil.getShortBytes(name.length)); | ||
|  | 
 | ||
|  |   // extra length
 | ||
|  |   this.write(zipUtil.getShortBytes(extra.length)); | ||
|  | 
 | ||
|  |   // comments length
 | ||
|  |   this.write(zipUtil.getShortBytes(comment.length)); | ||
|  | 
 | ||
|  |   // disk number start
 | ||
|  |   this.write(constants.SHORT_ZERO); | ||
|  | 
 | ||
|  |   // internal attributes
 | ||
|  |   this.write(zipUtil.getShortBytes(ae.getInternalAttributes())); | ||
|  | 
 | ||
|  |   // external attributes
 | ||
|  |   this.write(zipUtil.getLongBytes(ae.getExternalAttributes())); | ||
|  | 
 | ||
|  |   // relative offset of LFH
 | ||
|  |   if (offsets.file > constants.ZIP64_MAGIC) { | ||
|  |     this.write(zipUtil.getLongBytes(constants.ZIP64_MAGIC)); | ||
|  |   } else { | ||
|  |     this.write(zipUtil.getLongBytes(offsets.file)); | ||
|  |   } | ||
|  | 
 | ||
|  |   // name
 | ||
|  |   this.write(name); | ||
|  | 
 | ||
|  |   // extra
 | ||
|  |   this.write(extra); | ||
|  | 
 | ||
|  |   // comment
 | ||
|  |   this.write(comment); | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._writeDataDescriptor = function(ae) { | ||
|  |   // signature
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_DD)); | ||
|  | 
 | ||
|  |   // crc32 checksum
 | ||
|  |   this.write(zipUtil.getLongBytes(ae.getCrc())); | ||
|  | 
 | ||
|  |   // sizes
 | ||
|  |   if (ae.isZip64()) { | ||
|  |     this.write(zipUtil.getEightBytes(ae.getCompressedSize())); | ||
|  |     this.write(zipUtil.getEightBytes(ae.getSize())); | ||
|  |   } else { | ||
|  |     this.write(zipUtil.getLongBytes(ae.getCompressedSize())); | ||
|  |     this.write(zipUtil.getLongBytes(ae.getSize())); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype._writeLocalFileHeader = function(ae) { | ||
|  |   var gpb = ae.getGeneralPurposeBit(); | ||
|  |   var method = ae.getMethod(); | ||
|  |   var name = ae.getName(); | ||
|  |   var extra = ae.getLocalFileDataExtra(); | ||
|  | 
 | ||
|  |   if (ae.isZip64()) { | ||
|  |     gpb.useDataDescriptor(true); | ||
|  |     ae.setVersionNeededToExtract(constants.MIN_VERSION_ZIP64); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (gpb.usesUTF8ForNames()) { | ||
|  |     name = Buffer.from(name); | ||
|  |   } | ||
|  | 
 | ||
|  |   ae._offsets.file = this.offset; | ||
|  | 
 | ||
|  |   // signature
 | ||
|  |   this.write(zipUtil.getLongBytes(constants.SIG_LFH)); | ||
|  | 
 | ||
|  |   // version to extract and general bit flag
 | ||
|  |   this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract())); | ||
|  |   this.write(gpb.encode()); | ||
|  | 
 | ||
|  |   // compression method
 | ||
|  |   this.write(zipUtil.getShortBytes(method)); | ||
|  | 
 | ||
|  |   // datetime
 | ||
|  |   this.write(zipUtil.getLongBytes(ae.getTimeDos())); | ||
|  | 
 | ||
|  |   ae._offsets.data = this.offset; | ||
|  | 
 | ||
|  |   // crc32 checksum and sizes
 | ||
|  |   if (gpb.usesDataDescriptor()) { | ||
|  |     this.write(constants.LONG_ZERO); | ||
|  |     this.write(constants.LONG_ZERO); | ||
|  |     this.write(constants.LONG_ZERO); | ||
|  |   } else { | ||
|  |     this.write(zipUtil.getLongBytes(ae.getCrc())); | ||
|  |     this.write(zipUtil.getLongBytes(ae.getCompressedSize())); | ||
|  |     this.write(zipUtil.getLongBytes(ae.getSize())); | ||
|  |   } | ||
|  | 
 | ||
|  |   // name length
 | ||
|  |   this.write(zipUtil.getShortBytes(name.length)); | ||
|  | 
 | ||
|  |   // extra length
 | ||
|  |   this.write(zipUtil.getShortBytes(extra.length)); | ||
|  | 
 | ||
|  |   // name
 | ||
|  |   this.write(name); | ||
|  | 
 | ||
|  |   // extra
 | ||
|  |   this.write(extra); | ||
|  | 
 | ||
|  |   ae._offsets.contents = this.offset; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype.getComment = function(comment) { | ||
|  |   return this._archive.comment !== null ? this._archive.comment : ''; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype.isZip64 = function() { | ||
|  |   return this._archive.forceZip64 || this._entries.length > constants.ZIP64_MAGIC_SHORT || this._archive.centralLength > constants.ZIP64_MAGIC || this._archive.centralOffset > constants.ZIP64_MAGIC; | ||
|  | }; | ||
|  | 
 | ||
|  | ZipArchiveOutputStream.prototype.setComment = function(comment) { | ||
|  |   this._archive.comment = comment; | ||
|  | }; |