| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- var binary = require('binary');
- var PullStream = require('../PullStream');
- var unzip = require('./unzip');
- var Promise = require('bluebird');
- var BufferStream = require('../BufferStream');
- var parseExtraField = require('../parseExtraField');
- var Buffer = require('../Buffer');
- var path = require('path');
- var Writer = require('fstream').Writer;
- var parseDateTime = require('../parseDateTime');
- var signature = Buffer.alloc(4);
- signature.writeUInt32LE(0x06054b50,0);
- function getCrxHeader(source) {
- var sourceStream = source.stream(0).pipe(PullStream());
- return sourceStream.pull(4).then(function(data) {
- var signature = data.readUInt32LE(0);
- if (signature === 0x34327243) {
- var crxHeader;
- return sourceStream.pull(12).then(function(data) {
- crxHeader = binary.parse(data)
- .word32lu('version')
- .word32lu('pubKeyLength')
- .word32lu('signatureLength')
- .vars;
- }).then(function() {
- return sourceStream.pull(crxHeader.pubKeyLength +crxHeader.signatureLength);
- }).then(function(data) {
- crxHeader.publicKey = data.slice(0,crxHeader.pubKeyLength);
- crxHeader.signature = data.slice(crxHeader.pubKeyLength);
- crxHeader.size = 16 + crxHeader.pubKeyLength +crxHeader.signatureLength;
- return crxHeader;
- });
- }
- });
- }
- // Zip64 File Format Notes: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
- function getZip64CentralDirectory(source, zip64CDL) {
- var d64loc = binary.parse(zip64CDL)
- .word32lu('signature')
- .word32lu('diskNumber')
- .word64lu('offsetToStartOfCentralDirectory')
- .word32lu('numberOfDisks')
- .vars;
- if (d64loc.signature != 0x07064b50) {
- throw new Error('invalid zip64 end of central dir locator signature (0x07064b50): 0x' + d64loc.signature.toString(16));
- }
- var dir64 = PullStream();
- source.stream(d64loc.offsetToStartOfCentralDirectory).pipe(dir64);
- return dir64.pull(56)
- }
- // Zip64 File Format Notes: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
- function parseZip64DirRecord (dir64record) {
- var vars = binary.parse(dir64record)
- .word32lu('signature')
- .word64lu('sizeOfCentralDirectory')
- .word16lu('version')
- .word16lu('versionsNeededToExtract')
- .word32lu('diskNumber')
- .word32lu('diskStart')
- .word64lu('numberOfRecordsOnDisk')
- .word64lu('numberOfRecords')
- .word64lu('sizeOfCentralDirectory')
- .word64lu('offsetToStartOfCentralDirectory')
- .vars;
- if (vars.signature != 0x06064b50) {
- throw new Error('invalid zip64 end of central dir locator signature (0x06064b50): 0x0' + vars.signature.toString(16));
- }
- return vars
- }
- module.exports = function centralDirectory(source, options) {
- var endDir = PullStream(),
- records = PullStream(),
- tailSize = (options && options.tailSize) || 80,
- sourceSize,
- crxHeader,
- startOffset,
- vars;
- if (options && options.crx)
- crxHeader = getCrxHeader(source);
- return source.size()
- .then(function(size) {
- sourceSize = size;
- source.stream(Math.max(0,size-tailSize))
- .on('error', function (error) { endDir.emit('error', error) })
- .pipe(endDir);
- return endDir.pull(signature);
- })
- .then(function() {
- return Promise.props({directory: endDir.pull(22), crxHeader: crxHeader});
- })
- .then(function(d) {
- var data = d.directory;
- startOffset = d.crxHeader && d.crxHeader.size || 0;
- vars = binary.parse(data)
- .word32lu('signature')
- .word16lu('diskNumber')
- .word16lu('diskStart')
- .word16lu('numberOfRecordsOnDisk')
- .word16lu('numberOfRecords')
- .word32lu('sizeOfCentralDirectory')
- .word32lu('offsetToStartOfCentralDirectory')
- .word16lu('commentLength')
- .vars;
- // Is this zip file using zip64 format? Use same check as Go:
- // https://github.com/golang/go/blob/master/src/archive/zip/reader.go#L503
- // For zip64 files, need to find zip64 central directory locator header to extract
- // relative offset for zip64 central directory record.
- if (vars.numberOfRecords == 0xffff|| vars.numberOfRecords == 0xffff ||
- vars.offsetToStartOfCentralDirectory == 0xffffffff) {
- // Offset to zip64 CDL is 20 bytes before normal CDR
- const zip64CDLSize = 20
- const zip64CDLOffset = sourceSize - (tailSize - endDir.match + zip64CDLSize)
- const zip64CDLStream = PullStream();
- source.stream(zip64CDLOffset).pipe(zip64CDLStream);
- return zip64CDLStream.pull(zip64CDLSize)
- .then(function (d) { return getZip64CentralDirectory(source, d) })
- .then(function (dir64record) {
- vars = parseZip64DirRecord(dir64record)
- })
- } else {
- vars.offsetToStartOfCentralDirectory += startOffset;
- }
- })
- .then(function() {
- if (vars.commentLength) return endDir.pull(vars.commentLength).then(function(comment) {
- vars.comment = comment.toString('utf8');
- });
- })
- .then(function() {
- source.stream(vars.offsetToStartOfCentralDirectory).pipe(records);
- vars.extract = function(opts) {
- if (!opts || !opts.path) throw new Error('PATH_MISSING');
- // make sure path is normalized before using it
- opts.path = path.resolve(path.normalize(opts.path));
- return vars.files.then(function(files) {
- return Promise.map(files, function(entry) {
- if (entry.type == 'Directory') return;
- // to avoid zip slip (writing outside of the destination), we resolve
- // the target path, and make sure it's nested in the intended
- // destination, or not extract it otherwise.
- var extractPath = path.join(opts.path, entry.path);
- if (extractPath.indexOf(opts.path) != 0) {
- return;
- }
- var writer = opts.getWriter ? opts.getWriter({path: extractPath}) : Writer({ path: extractPath });
- return new Promise(function(resolve, reject) {
- entry.stream(opts.password)
- .on('error',reject)
- .pipe(writer)
- .on('close',resolve)
- .on('error',reject);
- });
- }, { concurrency: opts.concurrency > 1 ? opts.concurrency : 1 });
- });
- };
- vars.files = Promise.mapSeries(Array(vars.numberOfRecords),function() {
- return records.pull(46).then(function(data) {
- var vars = binary.parse(data)
- .word32lu('signature')
- .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;
- vars.offsetToLocalFileHeader += startOffset;
- vars.lastModifiedDateTime = parseDateTime(vars.lastModifiedDate, vars.lastModifiedTime);
- return records.pull(vars.fileNameLength).then(function(fileNameBuffer) {
- vars.pathBuffer = fileNameBuffer;
- vars.path = fileNameBuffer.toString('utf8');
- vars.isUnicode = (vars.flags & 0x800) != 0;
- return records.pull(vars.extraFieldLength);
- })
- .then(function(extraField) {
- vars.extra = parseExtraField(extraField, vars);
- return records.pull(vars.fileCommentLength);
- })
- .then(function(comment) {
- vars.comment = comment;
- vars.type = (vars.uncompressedSize === 0 && /[\/\\]$/.test(vars.path)) ? 'Directory' : 'File';
- vars.stream = function(_password) {
- return unzip(source, vars.offsetToLocalFileHeader,_password, vars);
- };
- vars.buffer = function(_password) {
- return BufferStream(vars.stream(_password));
- };
- return vars;
- });
- });
- });
- return Promise.props(vars);
- });
- };
|