123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- 'use strict';
- const path = require('path');
- const fs = require('graceful-fs');
- const decompressTar = require('decompress-tar');
- const decompressTarbz2 = require('decompress-tarbz2');
- const decompressTargz = require('decompress-targz');
- const decompressUnzip = require('decompress-unzip');
- const makeDir = require('make-dir');
- const pify = require('pify');
- const stripDirs = require('strip-dirs');
- const fsP = pify(fs);
- const runPlugins = (input, opts) => {
- if (opts.plugins.length === 0) {
- return Promise.resolve([]);
- }
- return Promise.all(opts.plugins.map(x => x(input, opts))).then(files => files.reduce((a, b) => a.concat(b)));
- };
- const safeMakeDir = (dir, realOutputPath) => {
- return fsP.realpath(dir)
- .catch(_ => {
- const parent = path.dirname(dir);
- return safeMakeDir(parent, realOutputPath);
- })
- .then(realParentPath => {
- if (realParentPath.indexOf(realOutputPath) !== 0) {
- throw (new Error('Refusing to create a directory outside the output path.'));
- }
- return makeDir(dir).then(fsP.realpath);
- });
- };
- const preventWritingThroughSymlink = (destination, realOutputPath) => {
- return fsP.readlink(destination)
- .catch(_ => {
- // Either no file exists, or it's not a symlink. In either case, this is
- // not an escape we need to worry about in this phase.
- return null;
- })
- .then(symlinkPointsTo => {
- if (symlinkPointsTo) {
- throw new Error('Refusing to write into a symlink');
- }
- // No symlink exists at `destination`, so we can continue
- return realOutputPath;
- });
- };
- const extractFile = (input, output, opts) => runPlugins(input, opts).then(files => {
- if (opts.strip > 0) {
- files = files
- .map(x => {
- x.path = stripDirs(x.path, opts.strip);
- return x;
- })
- .filter(x => x.path !== '.');
- }
- if (typeof opts.filter === 'function') {
- files = files.filter(opts.filter);
- }
- if (typeof opts.map === 'function') {
- files = files.map(opts.map);
- }
- if (!output) {
- return files;
- }
- return Promise.all(files.map(x => {
- const dest = path.join(output, x.path);
- const mode = x.mode & ~process.umask();
- const now = new Date();
- if (x.type === 'directory') {
- return makeDir(output)
- .then(outputPath => fsP.realpath(outputPath))
- .then(realOutputPath => safeMakeDir(dest, realOutputPath))
- .then(() => fsP.utimes(dest, now, x.mtime))
- .then(() => x);
- }
- return makeDir(output)
- .then(outputPath => fsP.realpath(outputPath))
- .then(realOutputPath => {
- // Attempt to ensure parent directory exists (failing if it's outside the output dir)
- return safeMakeDir(path.dirname(dest), realOutputPath)
- .then(() => realOutputPath);
- })
- .then(realOutputPath => {
- if (x.type === 'file') {
- return preventWritingThroughSymlink(dest, realOutputPath);
- }
- return realOutputPath;
- })
- .then(realOutputPath => {
- return fsP.realpath(path.dirname(dest))
- .then(realDestinationDir => {
- if (realDestinationDir.indexOf(realOutputPath) !== 0) {
- throw (new Error('Refusing to write outside output directory: ' + realDestinationDir));
- }
- });
- })
- .then(() => {
- if (x.type === 'link') {
- return fsP.link(x.linkname, dest);
- }
- if (x.type === 'symlink' && process.platform === 'win32') {
- return fsP.link(x.linkname, dest);
- }
- if (x.type === 'symlink') {
- return fsP.symlink(x.linkname, dest);
- }
- return fsP.writeFile(dest, x.data, {mode});
- })
- .then(() => x.type === 'file' && fsP.utimes(dest, now, x.mtime))
- .then(() => x);
- }));
- });
- module.exports = (input, output, opts) => {
- if (typeof input !== 'string' && !Buffer.isBuffer(input)) {
- return Promise.reject(new TypeError('Input file required'));
- }
- if (typeof output === 'object') {
- opts = output;
- output = null;
- }
- opts = Object.assign({plugins: [
- decompressTar(),
- decompressTarbz2(),
- decompressTargz(),
- decompressUnzip()
- ]}, opts);
- const read = typeof input === 'string' ? fsP.readFile(input) : Promise.resolve(input);
- return read.then(buf => extractFile(buf, output, opts));
- };
|