Archive extraction library supporting ZIP, TAR, TAR.GZ, TAR.BZ2, and BZIP2 formats with Promise-based API and security protections
npx @tessl/cli install tessl/npm-decompress@4.2.0Decompress is an archive extraction library for Node.js that simplifies extracting files from various archive formats including ZIP, TAR, TAR.GZ, TAR.BZ2, and BZIP2. It provides a Promise-based API with advanced features like file filtering, path mapping, directory stripping, and built-in security protections against path traversal attacks.
npm install decompressconst decompress = require('decompress');For ES modules:
import decompress from 'decompress';const decompress = require('decompress');
// Extract archive to memory (returns file objects)
decompress('archive.zip').then(files => {
console.log(`Extracted ${files.length} files`);
files.forEach(file => {
console.log(file.path, file.type, file.data.length);
});
});
// Extract archive to directory
decompress('archive.zip', 'dist').then(files => {
console.log('Files extracted to dist directory');
});
// Extract with options
decompress('archive.tar.gz', 'output', {
strip: 1, // Remove first directory level
filter: file => file.path.endsWith('.js') // Only JS files
}).then(files => {
console.log('JavaScript files extracted');
});Main function for extracting archives with comprehensive format support and security protections.
/**
* Extract files from various archive formats
* @param {string|Buffer} input - Path to archive file or Buffer containing archive data
* @param {string} [output] - Output directory path (optional, omit to extract to memory only)
* @param {DecompressOptions} [opts] - Configuration options
* @returns {Promise<FileObject[]>} Promise resolving to array of extracted file objects
* @throws {TypeError} When input is neither string nor Buffer
*/
function decompress(input, output, opts);Supported Archive Formats:
Processing Pipeline:
Files are processed in the following order:
Usage Examples:
// Extract from file path to memory
const files = await decompress('data.zip');
// Extract from Buffer to memory
const buffer = fs.readFileSync('data.zip');
const files = await decompress(buffer);
// Extract to specific directory
await decompress('data.tar.gz', './extracted');
// Extract with custom options
await decompress('data.zip', 'output', {
strip: 2,
filter: file => !file.path.includes('test'),
map: file => {
file.path = 'prefix-' + file.path;
return file;
}
});Filter files during extraction to selectively extract only desired files.
/**
* Filter function to selectively extract files
* @param {FileObject} file - File object being processed
* @returns {boolean} true to extract the file, false to skip
*/
opts.filter = function(file) { return boolean; };Usage Example:
// Extract only JavaScript files
await decompress('project.zip', 'src', {
filter: file => file.path.endsWith('.js') || file.path.endsWith('.ts')
});
// Skip hidden files and directories
await decompress('backup.tar.gz', 'restore', {
filter: file => !file.path.startsWith('.')
});Transform file objects during extraction to modify paths, metadata, or content.
/**
* Map function to transform files during extraction
* @param {FileObject} file - File object being processed
* @returns {FileObject} Modified file object
*/
opts.map = function(file) { return FileObject; };Usage Example:
// Add prefix to all extracted file paths
await decompress('archive.zip', 'output', {
map: file => {
file.path = 'extracted-' + file.path;
return file;
}
});
// Modify file metadata
await decompress('data.tar', 'output', {
map: file => {
if (file.type === 'file') {
file.mode = file.mode | 0o644; // Set read permissions
}
return file;
}
});Remove leading directory components from extracted file paths.
/**
* Number of leading directory components to strip from file paths
* @type {number}
* @default 0
*/
opts.strip = number;Usage Example:
// Remove first directory level (e.g., "folder/file.txt" becomes "file.txt")
await decompress('nested-archive.tar.gz', 'flat-output', {
strip: 1
});
// Remove two directory levels
await decompress('deeply/nested/archive.zip', 'output', {
strip: 2
});Override default decompression plugins with custom handlers. Each plugin is a function that receives input buffer and options, returning a Promise of file objects.
/**
* Array of decompression plugins to use
* @type {Function[]}
* @default [decompressTar(), decompressTarbz2(), decompressTargz(), decompressUnzip()]
*/
opts.plugins = Array<Function>;
/**
* Plugin function interface
* @param {Buffer} input - Archive data as Buffer
* @param {DecompressOptions} opts - Configuration options
* @returns {Promise<FileObject[]>} Promise resolving to array of file objects
*/
function plugin(input, opts);Plugin Behavior:
Usage Example:
const decompressUnzip = require('decompress-unzip');
// Use only ZIP decompression
await decompress('data.zip', 'output', {
plugins: [decompressUnzip()]
});
// No plugins (useful for getting empty array when no plugins match)
const result = await decompress('unknown.xyz', {
plugins: []
});
// result will be []/**
* Configuration options for decompress function
*/
interface DecompressOptions {
/** Filter function to selectively extract files */
filter?: (file: FileObject) => boolean;
/** Map function to transform files during extraction */
map?: (file: FileObject) => FileObject;
/** Array of decompression plugins */
plugins?: Function[];
/** Number of leading directory components to strip */
strip?: number;
}
/**
* File object representing an extracted file
*/
interface FileObject {
/** File contents as Buffer */
data: Buffer;
/** File permissions/mode as number (affected by process umask when written) */
mode: number;
/** File modification time as Date object */
mtime: Date;
/** Relative file path within the archive */
path: string;
/** File type: 'file', 'directory', 'link', or 'symlink' */
type: 'file' | 'directory' | 'link' | 'symlink';
/** Target path for links and symlinks (only present when type is 'link' or 'symlink') */
linkname?: string;
}Different file types are handled as follows when extracting to disk:
fs.writeFile() with mode applied (affected by umask)makeDir() with timestamps set via fs.utimes()fs.link() to link to existing filesfs.symlink() (or fs.link() on Windows for compatibility)Platform Differences:
The decompress function throws errors with specific messages:
TypeError: "Input file required" when input is neither string nor BufferENOENT error when specified archive file doesn't exist"Refusing to create a directory outside the output path." for directory escapes"Refusing to write into a symlink" when attempting to write through symlinks"Refusing to write outside output directory: <path>" for file path escapesError Handling Example:
try {
const files = await decompress('archive.zip', 'output');
console.log('Extraction successful');
} catch (error) {
if (error instanceof TypeError && error.message === 'Input file required') {
console.error('Invalid input provided');
} else if (error.message.includes('Refusing')) {
console.error('Security violation detected:', error.message);
} else if (error.code === 'ENOENT') {
console.error('Archive file not found');
} else {
console.error('Extraction failed:', error.message);
}
}Decompress includes built-in security protections against malicious archives:
fs.realpath() to validate all paths are within allowed boundaries before file operationsSecurity Implementation Details:
These protections help prevent malicious archives from compromising the system through directory traversal attacks, symlink-based escapes, and other archive-based security vulnerabilities.