/** | |
* node-compress-commons | |
* | |
* Copyright (c) 2014 Chris Talkington, contributors. | |
* Licensed under the MIT license. | |
* https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT | |
*/ | |
var inherits = require('util').inherits; | |
var crc32 = require('buffer-crc32'); | |
var CRC32Stream = require('crc32-stream'); | |
var DeflateCRC32Stream = CRC32Stream.DeflateCRC32Stream; | |
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 = options || {}; | |
options.zlib = this._zlibDefaults(options.zlib); | |
ArchiveOutputStream.call(this, options); | |
this._entry = null; | |
this._entries = []; | |
this._archive = { | |
centralLength: 0, | |
centralOffset: 0, | |
comment: '', | |
finish: false, | |
finished: false, | |
processing: false | |
}; | |
}; | |
inherits(ZipArchiveOutputStream, ArchiveOutputStream); | |
ZipArchiveOutputStream.prototype._afterAppend = function(zae) { | |
this._entries.push(zae); | |
this._writeDataDescriptor(zae); | |
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); | |
this._writeLocalFileHeader(ae); | |
var smart = this._smartStream(ae, callback); | |
source.pipe(smart); | |
}; | |
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; | |
this._writeCentralDirectoryEnd(); | |
this._archive.processing = false; | |
this._archive.finish = true; | |
this._archive.finished = true; | |
this.end(); | |
}; | |
ZipArchiveOutputStream.prototype._setDefaults = function(ae) { | |
if (ae.getMethod() === -1) { | |
ae.setMethod(constants.METHOD_DEFLATED); | |
} | |
if (ae.getMethod() === constants.METHOD_DEFLATED) { | |
ae.getGeneralPurposeBit().useDataDescriptor(true); | |
ae.setVersionNeededToExtract(constants.DATA_DESCRIPTOR_MIN_VERSION); | |
} | |
if (ae.getTime() === -1) { | |
ae.setTime(new Date()); | |
} | |
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(); | |
function handleStuff(err) { | |
ae.setCrc(process.digest()); | |
ae.setSize(process.size()); | |
ae.setCompressedSize(process.size(true)); | |
this._afterAppend(ae); | |
callback(null, ae); | |
} | |
process.once('error', callback); | |
process.once('end', handleStuff.bind(this)); | |
process.pipe(this, { end: false }); | |
return process; | |
}; | |
ZipArchiveOutputStream.prototype._writeCentralDirectoryEnd = function() { | |
// 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(this._entries.length)); | |
this.write(zipUtil.getShortBytes(this._entries.length)); | |
// length and location of CD | |
this.write(zipUtil.getLongBytes(this._archive.centralLength)); | |
this.write(zipUtil.getLongBytes(this._archive.centralOffset)); | |
// archive comment | |
var comment = this.getComment(); | |
var commentLength = Buffer.byteLength(comment); | |
this.write(zipUtil.getShortBytes(commentLength)); | |
this.write(comment); | |
}; | |
ZipArchiveOutputStream.prototype._writeCentralFileHeader = function(ae) { | |
var gpb = ae.getGeneralPurposeBit(); | |
var method = ae.getMethod(); | |
var offsets = ae._offsets; | |
// signature | |
this.write(zipUtil.getLongBytes(constants.SIG_CFH)); | |
// version made by | |
this.write(zipUtil.getShortBytes( | |
(ae.getPlatform() << 8) | constants.DATA_DESCRIPTOR_MIN_VERSION | |
)); | |
// version to extract and general bit flag | |
this._writeVersionGeneral(ae); | |
// 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(ae.getCompressedSize())); | |
this.write(zipUtil.getLongBytes(ae.getSize())); | |
var name = ae.getName(); | |
var comment = ae.getComment(); | |
var extra = ae.getCentralDirectoryExtra(); | |
if (gpb.usesUTF8ForNames()) { | |
name = new Buffer(name); | |
comment = new Buffer(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 | |
this.write(zipUtil.getLongBytes(offsets.file)); | |
// name | |
this.write(name); | |
// extra | |
this.write(extra); | |
// comment | |
this.write(comment); | |
}; | |
ZipArchiveOutputStream.prototype._writeDataDescriptor = function(ae) { | |
if (!ae.getGeneralPurposeBit().usesDataDescriptor()) { | |
return; | |
} | |
// signature | |
this.write(zipUtil.getLongBytes(constants.SIG_DD)); | |
// crc32 checksum | |
this.write(zipUtil.getLongBytes(ae.getCrc())); | |
// sizes | |
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 (gpb.usesUTF8ForNames()) { | |
name = new Buffer(name); | |
} | |
ae._offsets.file = this.offset; | |
// signature | |
this.write(zipUtil.getLongBytes(constants.SIG_LFH)); | |
// version to extract and general bit flag | |
this._writeVersionGeneral(ae); | |
// compression method | |
this.write(zipUtil.getShortBytes(method)); | |
// datetime | |
this.write(zipUtil.getLongBytes(ae.getTimeDos())); | |
ae._offsets.data = this.offset; | |
if (ae.getGeneralPurposeBit().usesDataDescriptor()) { | |
// zero fill and set later | |
this.write(constants.LONG_ZERO); | |
this.write(constants.LONG_ZERO); | |
this.write(constants.LONG_ZERO); | |
} else { | |
// crc32 checksum and sizes | |
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._writeVersionGeneral = function(ae) { | |
this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract())); | |
this.write(ae.getGeneralPurposeBit().encode()); | |
}; | |
ZipArchiveOutputStream.prototype._zlibDefaults = function(o) { | |
if (typeof o !== 'object') { | |
o = {}; | |
} | |
if (typeof o.level !== 'number') { | |
o.level = constants.ZLIB_BEST_SPEED; | |
} | |
return o; | |
}; | |
ZipArchiveOutputStream.prototype.getComment = function(comment) { | |
return this._archive.comment !== null ? this._archive.comment : ''; | |
}; | |
ZipArchiveOutputStream.prototype.setComment = function(comment) { | |
this._archive.comment = comment; | |
}; |