thedesk/app/node_modules/unzip/lib/parse.js
2018-02-18 16:29:06 +09:00

315 lines
8.5 KiB
JavaScript

'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;