123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- var stream = require('stream');
- if (!stream.Readable) {
- var stream = require('readable-stream');
- }
- var fs = require('graceful-fs');
- var Q = require('q');
- var path = require('path');
- var zlib = require('zlib');
- var touch = Q.denodeify(require('touch'));
- var mkpath = Q.denodeify(require('mkpath'));
- var writeFile = Q.denodeify(fs.writeFile);
- var inflateRaw = Q.denodeify(zlib.inflateRaw);
- var symlink = Q.denodeify(fs.symlink);
- var stat = Q.denodeify(fs.stat);
- // Use a cache of promises for building the directory tree. This allows us to
- // correctly queue up file extractions for after their path has been created,
- // avoid trying to create the path twice and still be async.
- var mkdir = function (dir, cache, mode) {
- dir = path.normalize(path.resolve(process.cwd(), dir) + path.sep);
- if (mode === undefined) {
- mode = parseInt('777', 8) & (~process.umask());
- }
- if (!cache[dir]) {
- var parent;
- if (fs.existsSync(dir)) {
- parent = new Q();
- } else {
- parent = mkdir(path.dirname(dir), cache, mode);
- }
- cache[dir] = parent.then(function () {
- return mkpath(dir, mode);
- });
- }
- return cache[dir];
- };
- // Utility methods for writing output files
- var extractors = {
- folder: function (folder, destination, zip) {
- return mkdir(destination, zip.dirCache, folder.mode)
- .then(function () {
- return {folder: folder.path};
- });
- },
- store: function (file, destination, zip) {
- var writer;
- if (file.uncompressedSize === 0) {
- writer = touch.bind(null, destination);
- } else if (file.uncompressedSize <= zip.chunkSize) {
- writer = function () {
- return zip.getBuffer(file._offset, file._offset + file.uncompressedSize)
- .then(function (buffer) {
- return writeFile(destination, buffer, { mode: file.mode });
- });
- };
- } else {
- var input = new stream.Readable();
- input.wrap(fs.createReadStream(zip.filename, {start: file._offset, end: file._offset + file.uncompressedSize - 1}));
- writer = pipePromise.bind(null, input, destination, { mode: file.mode });
- }
- return mkdir(path.dirname(destination), zip.dirCache)
- .then(writer)
- .then(function () {
- return {stored: file.path};
- });
- },
- deflate: function (file, destination, zip) {
- // For Deflate you don't actually need to specify the end offset - and
- // in fact many ZIP files don't include compressed file sizes for
- // Deflated files so we don't even know what the end offset is.
- return mkdir(path.dirname(destination), zip.dirCache)
- .then(function () {
- if (file._maxSize <= zip.chunkSize) {
- return zip.getBuffer(file._offset, file._offset + file._maxSize)
- .then(inflateRaw)
- .then(function (buffer) {
- return writeFile(destination, buffer, { mode: file.mode });
- });
- } else {
- // For node 0.8 we need to create the Zlib stream and attach
- // handlers in the same tick of the event loop, which is why we do
- // the creation in here
- var input = new stream.Readable();
- input.wrap(fs.createReadStream(zip.filename, {start: file._offset}));
- var inflater = input.pipe(zlib.createInflateRaw({highWaterMark: 32 * 1024}));
- return pipePromise(inflater, destination, { mode: file.mode });
- }
- })
- .then(function () {
- return {deflated: file.path};
- });
- },
- symlink: function (file, destination, zip, basePath) {
- var parent = path.dirname(destination);
- return mkdir(parent, zip.dirCache)
- .then(function () {
- return getLinkLocation(file, destination, zip, basePath);
- })
- .then(function (linkTo) {
- return symlink(path.resolve(parent, linkTo), destination)
- .then(function () {
- return {symlink: file.path, linkTo: linkTo};
- });
- });
- },
- // Make a shallow copy of the file/directory this symlink points to instead
- // of actually creating a link
- copy: function (file, destination, zip, basePath) {
- var type;
- var parent = path.dirname(destination);
- return mkdir(parent, zip.dirCache)
- .then(function () {
- return getLinkLocation(file, destination, zip, basePath);
- })
- .then(function (linkTo) {
- return stat(path.resolve(parent, linkTo))
- .then(function (stats) {
- if (stats.isFile()) {
- type = 'File';
- var input = new stream.Readable();
- input.wrap(fs.createReadStream(path.resolve(parent, linkTo)));
- return pipePromise(input, destination);
- } else if (stats.isDirectory()) {
- type = 'Directory';
- return mkdir(destination, zip.dirCache);
- } else {
- throw new Error('Could not follow symlink to unknown file type');
- }
- })
- .then(function () {
- return {copy: file.path, original: linkTo, type: type};
- });
- });
- }
- };
- var getLinkLocation = function (file, destination, zip, basePath) {
- var parent = path.dirname(destination);
- return zip.getBuffer(file._offset, file._offset + file.uncompressedSize)
- .then(function (buffer) {
- var linkTo = buffer.toString();
- var fullLink = path.resolve(parent, linkTo);
- if (path.relative(basePath, fullLink).slice(0, 2) === '..') {
- throw new Error('Symlink links outside archive');
- }
- return linkTo;
- });
- };
- var pipePromise = function (input, destination, options) {
- var deferred = Q.defer();
- var output = fs.createWriteStream(destination, options);
- var errorHandler = function (error) {
- deferred.reject(error);
- };
- input.on('error', errorHandler);
- output.on('error', errorHandler);
- // For node 0.8 we can't just use the 'finish' event of the pipe
- input.on('end', function () {
- output.end(function () {
- deferred.resolve();
- });
- });
- input.pipe(output, {end: false});
- return deferred.promise;
- };
- module.exports = extractors;
|