diff --git a/README.md b/README.md index e5fc7d9..12d9f8e 100644 --- a/README.md +++ b/README.md @@ -301,5 +301,16 @@ async function main() { main(); ``` +### Open.[method].extract() + +The directory object returned from `Open.[method]` provides an `extract` method which extracts all the files to a specified `path`, with an optional `concurrency` (default: 1). + +Example (with concurrency of 5): + +```js +unzip.Open.file('path/to/archive.zip') + .then(d => d.extract({path: '/extraction/path', concurrency: 5})); +``` + ## Licenses See LICENCE diff --git a/lib/Open/directory.js b/lib/Open/directory.js index 85287d4..9022ab6 100644 --- a/lib/Open/directory.js +++ b/lib/Open/directory.js @@ -5,6 +5,8 @@ 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 signature = Buffer.alloc(4); signature.writeUInt32LE(0x06054b50,0); @@ -71,6 +73,32 @@ module.exports = function centralDirectory(source, options) { source.stream(vars.offsetToStartOfCentralDirectory).pipe(records); + vars.extract = function(opts) { + if (!opts || !opts.path) throw new Error('PATH_MISSING'); + 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}); + }); + }; + vars.files = Promise.mapSeries(Array(vars.numberOfRecords),function() { return records.pull(46).then(function(data) { var vars = binary.parse(data) diff --git a/test/open-extract.js b/test/open-extract.js new file mode 100644 index 0000000..7c97198 --- /dev/null +++ b/test/open-extract.js @@ -0,0 +1,34 @@ +'use strict'; + +var test = require('tap').test; +var path = require('path'); +var temp = require('temp'); +var dirdiff = require('dirdiff'); +var unzip = require('../'); + + +test("extract compressed archive with open.file.extract", function (t) { + var archive = path.join(__dirname, '../testData/compressed-standard/archive.zip'); + + temp.mkdir('node-unzip-2', function (err, dirPath) { + if (err) { + throw err; + } + unzip.Open.file(archive) + .then(function(d) { + return d.extract({path: dirPath}); + }) + .then(function() { + dirdiff(path.join(__dirname, '../testData/compressed-standard/inflated'), dirPath, { + fileContents: true + }, function (err, diffs) { + if (err) { + throw err; + } + t.equal(diffs.length, 0, 'extracted directory contents'); + t.end(); + }); + }); + + }); +}); \ No newline at end of file