blob: a1c5e03f0f5b5ec0f6cd25d0c7b2c444ee13f38f [file] [log] [blame]
/**
* 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;
};