UNPKG

unzip

Version:

Unzip cross-platform streaming API compatible with fstream and fs.ReadStream

230 lines (199 loc) 6.27 kB
'use strict'; module.exports = Parse.create = Parse; var PullStream = require('pullstream'); var Transform = require('readable-stream/transform'); var inherits = require('util').inherits; var zlib = require('zlib'); var binary = require('binary'); var Entry = require('./entry'); inherits(Parse, Transform); function Parse() { var self = this; if (!(this instanceof Parse)) { return new Parse(); } Transform.call(this, { lowWaterMark: 0 }); this._pullStream = new PullStream({ lowWaterMark: 0}); this._pullStream.on("error", function (e) { self.emit('error', e); }); this._pullStream.once("end", function () { self._streamEnd = true; }); this._pullStream.once("finish", function () { self._streamFinish = true; }); this._readRecord(); } Parse.prototype._readRecord = function () { var self = this; this._pullStream.pull(4, function (err, data) { if (err) { return self.emit('error', err); } if (data.length === 0) { return; } var signature = data.readUInt32LE(0); if (signature === 0x04034b50) { self._readFile(); } else if (signature === 0x02014b50) { self._readCentralDirectoryFileHeader(); } else if (signature === 0x06054b50) { self._readEndOfCentralDirectoryRecord(); } else { err = new Error('invalid signature: 0x' + signature.toString(16)); self.emit('error', err); } }); }; Parse.prototype._readFile = function () { var self = this; this._pullStream.pull(26, function (err, data) { if (err) { return self.emit('error', err); } 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; return self._pullStream.pull(vars.fileNameLength, function (err, fileName) { if (err) { return self.emit('error', err); } fileName = fileName.toString('utf8'); var entry = new Entry(); entry.path = fileName; entry.props.path = fileName; entry.type = (vars.compressedSize === 0 && /[\/\\]$/.test(fileName)) ? 'Directory' : 'File'; entry.size = vars.uncompressedSize; self.emit('entry', entry); self._pullStream.pull(vars.extraFieldLength, function (err, extraField) { if (err) { return self.emit('error', err); } if (vars.compressionMethod === 0) { self._pullStream.pull(vars.compressedSize, function (err, compressedData) { if (err) { return self.emit('error', err); } entry.emit('data', compressedData); entry.emit('end'); return self._readRecord(); }); } else { var inflater = zlib.createInflateRaw(); inflater.on('error', function (err) { self.emit('error', err); }); inflater.on('end', function () { entry.emit('end'); self._readRecord(); }); inflater.on('data', function (uncompressedData) { entry.emit('data', uncompressedData); }); self._pullStream.pipe(vars.compressedSize, inflater); } }); }); }); }; Parse.prototype._readCentralDirectoryFileHeader = function () { var self = this; this._pullStream.pull(42, function (err, data) { if (err) { return self.emit('error', err); } 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._pullStream.pull(vars.fileNameLength, function (err, fileName) { if (err) { return self.emit('error', err); } fileName = fileName.toString('utf8'); self._pullStream.pull(vars.extraFieldLength, function (err, extraField) { if (err) { return self.emit('error', err); } self._pullStream.pull(vars.fileCommentLength, function (err, fileComment) { if (err) { return self.emit('error', err); } return self._readRecord(); }); }); }); }); }; Parse.prototype._readEndOfCentralDirectoryRecord = function () { var self = this; this._pullStream.pull(18, function (err, data) { if (err) { return self.emit('error', err); } var vars = binary.parse(data) .word16lu('diskNumber') .word16lu('diskStart') .word16lu('numberOfRecordsOnDisk') .word16lu('numberOfRecords') .word32lu('sizeOfCentralDirectory') .word32lu('offsetToStartOfCentralDirectory') .word16lu('commentLength') .vars; return self._pullStream.pull(vars.commentLength, function (err, comment) { if (err) { return self.emit('error', err); } comment = comment.toString('utf8'); return self._pullStream.end(); }); }); }; Parse.prototype._transform = function (chunk, outputFn, callback) { if (this._pullStream.write(chunk)) { return callback(); } this._pullStream.once('drain', callback); }; Parse.prototype.pipe = function (dest, opts) { var self = this; if (typeof dest.add === "function") { self.on("entry", function (entry) { dest.add(entry); }) } return Transform.prototype.pipe.apply(this, arguments); }; Parse.prototype._flush = function (outputFn, callback) { var self = this; if (!this._streamEnd || !this._streamFinish) { return process.nextTick(self._flush.bind(self, outputFn, callback)); } this.emit('close'); return callback(); };