Current File : //lib/node_modules/bower/lib/util/extract.js |
var path = require('path');
var fs = require('./fs');
var zlib = require('zlib');
var DecompressZip = require('decompress-zip');
var tar = require('tar-fs');
var Q = require('q');
var mout = require('mout');
var junk = require('junk');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
var tmp = require('tmp');
// This forces the default chunk size to something small in an attempt
// to avoid issue #314
zlib.Z_DEFAULT_CHUNK = 1024 * 8;
var extractors;
var extractorTypes;
extractors = {
'.zip': extractZip,
'.tar': extractTar,
'.tar.gz': extractTarGz,
'.tgz': extractTarGz,
'.gz': extractGz,
'application/zip': extractZip,
'application/x-zip': extractZip,
'application/x-zip-compressed': extractZip,
'application/x-tar': extractTar,
'application/x-tgz': extractTarGz,
'application/x-gzip': extractGz
};
extractorTypes = Object.keys(extractors);
function extractZip(archive, dst) {
var deferred = Q.defer();
new DecompressZip(archive)
.on('error', deferred.reject)
.on('extract', deferred.resolve.bind(deferred, dst))
.extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
});
return deferred.promise;
}
function extractTar(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function extractTarGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function extractGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(createWriteStream(dst))
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function isSymlink(_, entry) {
return entry.type === 'symlink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}
function getExtractor(archive) {
// Make the archive lower case to match against the types
// This ensures that upper-cased extensions work
archive = archive.toLowerCase();
var type = mout.array.find(extractorTypes, function(type) {
return mout.string.endsWith(archive, type);
});
return type ? extractors[type] : null;
}
function isSingleDir(dir) {
return Q.nfcall(fs.readdir, dir).then(function(files) {
var singleDir;
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
if (files.length !== 1) {
return false;
}
singleDir = path.join(dir, files[0]);
return Q.nfcall(fs.stat, singleDir).then(function(stat) {
return stat.isDirectory() ? singleDir : false;
});
});
}
function moveDirectory(srcDir, destDir) {
return Q.nfcall(fs.readdir, srcDir)
.then(function(files) {
var promises = files.map(function(file) {
var src = path.join(srcDir, file);
var dst = path.join(destDir, file);
return Q.nfcall(fs.rename, src, dst);
});
return Q.all(promises);
})
.then(function() {
return Q.nfcall(fs.rmdir, srcDir);
});
}
// -----------------------------
function canExtract(src, mimeType) {
if (mimeType && mimeType !== 'application/octet-stream') {
return !!getExtractor(mimeType);
}
return !!getExtractor(src);
}
// Available options:
// - keepArchive: true to keep the archive afterwards (defaults to false)
// - keepStructure: true to keep the extracted structure unchanged (defaults to false)
function extract(src, dst, opts) {
var extractor;
var promise;
opts = opts || {};
extractor = getExtractor(src);
// Try to get extractor from mime type
if (!extractor && opts.mimeType) {
extractor = getExtractor(opts.mimeType);
}
// If extractor is null, then the archive type is unknown
if (!extractor) {
return Q.reject(
createError(
'File ' + src + ' is not a known archive',
'ENOTARCHIVE'
)
);
}
// Extract to a temporary directory in case of file name clashes
return Q.nfcall(tmp.dir, {
template: dst + '-XXXXXX',
mode: 0777 & ~process.umask()
})
.then(function(tempDir) {
// nfcall may return multiple callback arguments as an array
return Array.isArray(tempDir) ? tempDir[0] : tempDir;
})
.then(function(tempDir) {
// Check archive file size
promise = Q.nfcall(fs.stat, src).then(function(stat) {
if (stat.size <= 8) {
throw createError(
'File ' + src + ' is an invalid archive',
'ENOTARCHIVE'
);
}
// Extract archive
return extractor(src, tempDir);
});
// Remove archive
if (!opts.keepArchive) {
promise = promise.then(function() {
return Q.nfcall(fs.unlink, src);
});
}
// Move contents from the temporary directory
// If the contents are a single directory (and we're not preserving structure),
// move its contents directly instead.
promise = promise
.then(function() {
return isSingleDir(tempDir);
})
.then(function(singleDir) {
if (singleDir && !opts.keepStructure) {
return moveDirectory(singleDir, dst);
} else {
return moveDirectory(tempDir, dst);
}
});
// Resolve promise to the dst dir
return promise.then(function() {
return dst;
});
});
}
module.exports = extract;
module.exports.canExtract = canExtract;