UNPKG

archiver-zip-encrypted

Version:
140 lines (116 loc) 4.64 kB
'use strict'; const inherits = require('util').inherits; const CryptoStream = require('./crypto-stream'); const ZipStream = require('zip-stream'); const {DeflateCRC32Stream, CRC32Stream} = require('crc32-stream'); // var crc32 = require('buffer-crc32'); const constants = require('compress-commons/lib/archivers/zip/constants'); /** * Overrides ZipStream with ZipCrypto/Zip2.0 encryption */ const ZipCryptoStream = function (options = {zlib: {}}) { if (!(this instanceof ZipCryptoStream)) { return new ZipCryptoStream(options); } this.key = options.password; if (!Buffer.isBuffer(this.key)) { this.key = Buffer.from(options.password); } ZipStream.call(this, options); }; inherits(ZipCryptoStream, ZipStream); ZipCryptoStream.prototype._writeLocalFileHeader = function (ae) { // set encryption ae.getGeneralPurposeBit().useEncryption(true); ZipStream.prototype._writeLocalFileHeader.call(this, ae); }; /** * As opposed to original implementation which streamed deflated data into target stream, * this implementation buffers deflated data. This is done for 2 reasons: * 1) encryption header is prepended to encrypted file datamust include bytes from file's CRC * 2) using data descriptor along with ZipCrypto encryption seems unsupported (this may be * the consequence of 1st reason but I don't know that for sure) * * The buffering currently done in memory. If a need arises, need to add disk buffering. */ ZipCryptoStream.prototype._smartStream = function (ae, callback) { let deflate = ae.getMethod() === constants.METHOD_DEFLATED; let compressionStream = deflate ? new DeflateCRC32Stream(this.options.zlib) : new CRC32Stream(); let error = null; let bufferedData = []; compressionStream.once('error', function (err) { error = err; }); compressionStream.on('data', (data) => { bufferedData.push(data); }); compressionStream.once('end', () => { let crc = compressionStream.digest(); // gather complete information for CRC and sizes ae.setCrc(crc.readUInt32BE(0)); ae.setSize(compressionStream.size()); ae.setCompressedSize(compressionStream.size(true) + 12); // write local header now this._writeLocalFileHeader(ae); // write all buffered data to encrypt stream let encrypt = new CryptoStream({key: this.key, crc}); let writes = []; for (let chunk of bufferedData) { writes.push(() => writeBufferToStream(encrypt, chunk)); } promiseSerial(writes).then(() => encrypt.end()); encrypt.once('end', () => { this._afterAppend(ae); callback(error, ae); }); encrypt.once('error', function (err) { error = err; }); encrypt.pipe(this, {end: false}); }); return compressionStream; }; ZipCryptoStream.prototype._appendBuffer = function(ae, source, callback) { ae.setSize(source.length); ae.setCompressedSize(source.length); ae.getGeneralPurposeBit().useDataDescriptor(false); // we will write local file header after we get CRC back // it seems as if using data descriptor is not supported when encrypting data with ZipCrypto // so we have to write CRC into local file header // this._writeLocalFileHeader(ae); this._smartStream(ae, callback).end(source); }; ZipCryptoStream.prototype._appendStream = function(ae, source, callback) { ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR); ae.getGeneralPurposeBit().useDataDescriptor(false); // we will write local file header after we get CRC back // it seems as if using data descriptor is not supported when encrypting data with ZipCrypto // so we have to write CRC into local file header // this._writeLocalFileHeader(ae); var smart = this._smartStream(ae, callback); source.once('error', function(err) { smart.emit('error', err); smart.end(); }); source.pipe(smart); }; function writeBufferToStream(writable, data) { return new Promise((resolve, reject) => { writable.write(data, (err) => { if (err) { reject(); } else { resolve(); } }); }); } /** * Execute promises sequentially. Input is an array of functions that return promises to execute. */ const promiseSerial = funcs => funcs.reduce((promise, func) => promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([])); module.exports = ZipCryptoStream;