GT2/GT2-Android/node_modules/decompress-zip/lib/extractors.js

185 lines
6.4 KiB
JavaScript
Raw Normal View History

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;