| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- 'use strict';
- module.exports = Parse.create = Parse;
- require("setimmediate");
- var Transform = require('readable-stream/transform');
- var inherits = require('util').inherits;
- var zlib = require('zlib');
- var binary = require('binary');
- var PullStream = require('pullstream');
- var MatchStream = require('match-stream');
- var Entry = require('./entry');
- inherits(Parse, Transform);
- function Parse(opts) {
- var self = this;
- if (!(this instanceof Parse)) {
- return new Parse(opts);
- }
- Transform.call(this, { lowWaterMark: 0 });
- this._opts = opts || { verbose: false };
- this._hasEntryListener = false;
- this._pullStream = new PullStream();
- 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';
- if (self._opts.verbose) {
- if (entry.type === 'Directory') {
- console.log(' creating:', fileName);
- } else if (entry.type === 'File') {
- if (vars.compressionMethod === 0) {
- console.log(' extracting:', fileName);
- } else {
- console.log(' inflating:', fileName);
- }
- }
- }
- var hasEntryListener = self._hasEntryListener;
- if (hasEntryListener) {
- 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);
- }
- if (hasEntryListener) {
- entry.write(compressedData);
- entry.end();
- }
- return self._readRecord();
- });
- } else {
- var fileSizeKnown = !(vars.flags & 0x08);
- var inflater = zlib.createInflateRaw();
- inflater.on('error', function (err) {
- self.emit('error', err);
- });
- if (fileSizeKnown) {
- entry.size = vars.uncompressedSize;
- if (hasEntryListener) {
- entry.on('finish', self._readRecord.bind(self));
- self._pullStream.pipe(vars.compressedSize, inflater).pipe(entry);
- } else {
- self._pullStream.drain(vars.compressedSize, function (err) {
- if (err) {
- return self.emit('error', err);
- }
- self._readRecord();
- });
- }
- } else {
- var descriptorSig = new Buffer(4);
- descriptorSig.writeUInt32LE(0x08074b50, 0);
- var matchStream = new MatchStream({ pattern: descriptorSig }, function (buf, matched, extra) {
- if (hasEntryListener) {
- if (!matched) {
- return this.push(buf);
- }
- this.push(buf);
- }
- setImmediate(function() {
- self._pullStream.unpipe();
- self._pullStream.prepend(extra);
- self._processDataDescriptor(entry);
- });
- return this.push(null);
- });
- self._pullStream.pipe(matchStream);
- if (hasEntryListener) {
- matchStream.pipe(inflater).pipe(entry);
- }
- }
- }
- });
- });
- });
- };
- Parse.prototype._processDataDescriptor = function (entry) {
- var self = this;
- this._pullStream.pull(16, function (err, data) {
- if (err) {
- return self.emit('error', err);
- }
- var vars = binary.parse(data)
- .word32lu('dataDescriptorSignature')
- .word32lu('crc32')
- .word32lu('compressedSize')
- .word32lu('uncompressedSize')
- .vars;
- entry.size = vars.uncompressedSize;
- self._readRecord();
- });
- };
- 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;
- if (vars.commentLength) {
- setImmediate(function() {
- self._pullStream.pull(vars.commentLength, function (err, comment) {
- if (err) {
- return self.emit('error', err);
- }
- comment = comment.toString('utf8');
- return self._pullStream.end();
- });
- });
- } else {
- self._pullStream.end();
- }
- });
- };
- Parse.prototype._transform = function (chunk, encoding, 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 (callback) {
- if (!this._streamEnd || !this._streamFinish) {
- return setImmediate(this._flush.bind(this, callback));
- }
- this.emit('close');
- return callback();
- };
- Parse.prototype.addListener = function(type, listener) {
- if ('entry' === type) {
- this._hasEntryListener = true;
- }
- return Transform.prototype.addListener.call(this, type, listener);
- };
- Parse.prototype.on = Parse.prototype.addListener;
|