extract-zip
Advanced tools
Comparing version 1.7.0 to 2.0.0
11
cli.js
#!/usr/bin/env node | ||
/* eslint-disable no-process-exit */ | ||
var extract = require('./') | ||
@@ -13,9 +15,6 @@ | ||
extract(source, {dir: dest}, function (err, results) { | ||
if (err) { | ||
extract(source, { dir: dest }) | ||
.catch(function (err) { | ||
console.error('error!', err) | ||
process.exit(1) | ||
} else { | ||
process.exit(0) | ||
} | ||
}) | ||
}) |
297
index.js
@@ -1,53 +0,43 @@ | ||
var fs = require('fs') | ||
var path = require('path') | ||
var yauzl = require('yauzl') | ||
var mkdirp = require('mkdirp') | ||
var concat = require('concat-stream') | ||
var debug = require('debug')('extract-zip') | ||
const debug = require('debug')('extract-zip') | ||
// eslint-disable-next-line node/no-unsupported-features/node-builtins | ||
const { createWriteStream, promises: fs } = require('fs') | ||
const getStream = require('get-stream') | ||
const path = require('path') | ||
const { promisify } = require('util') | ||
const stream = require('stream') | ||
const yauzl = require('yauzl') | ||
module.exports = function (zipPath, opts, cb) { | ||
debug('creating target directory', opts.dir) | ||
const openZip = promisify(yauzl.open) | ||
const pipeline = promisify(stream.pipeline) | ||
if (path.isAbsolute(opts.dir) === false) { | ||
return cb(new Error('Target directory is expected to be absolute')) | ||
class Extractor { | ||
constructor (zipPath, opts) { | ||
this.zipPath = zipPath | ||
this.opts = opts | ||
} | ||
mkdirp(opts.dir, function (err) { | ||
if (err) return cb(err) | ||
async extract () { | ||
debug('opening', this.zipPath, 'with opts', this.opts) | ||
fs.realpath(opts.dir, function (err, canonicalDir) { | ||
if (err) return cb(err) | ||
this.zipfile = await openZip(this.zipPath, { lazyEntries: true }) | ||
this.canceled = false | ||
opts.dir = canonicalDir | ||
openZip(opts) | ||
}) | ||
}) | ||
function openZip () { | ||
debug('opening', zipPath, 'with opts', opts) | ||
yauzl.open(zipPath, {lazyEntries: true}, function (err, zipfile) { | ||
if (err) return cb(err) | ||
var cancelled = false | ||
zipfile.on('error', function (err) { | ||
if (err) { | ||
cancelled = true | ||
return cb(err) | ||
} | ||
return new Promise((resolve, reject) => { | ||
this.zipfile.on('error', err => { | ||
this.canceled = true | ||
reject(err) | ||
}) | ||
zipfile.readEntry() | ||
this.zipfile.readEntry() | ||
zipfile.on('close', function () { | ||
if (!cancelled) { | ||
this.zipfile.on('close', () => { | ||
if (!this.canceled) { | ||
debug('zip extraction complete') | ||
cb() | ||
resolve() | ||
} | ||
}) | ||
zipfile.on('entry', function (entry) { | ||
if (cancelled) { | ||
debug('skipping entry', entry.fileName, {cancelled: cancelled}) | ||
this.zipfile.on('entry', async entry => { | ||
/* istanbul ignore if */ | ||
if (this.canceled) { | ||
debug('skipping entry', entry.fileName, { cancelled: this.canceled }) | ||
return | ||
@@ -58,155 +48,130 @@ } | ||
if (/^__MACOSX\//.test(entry.fileName)) { | ||
// dir name starts with __MACOSX/ | ||
zipfile.readEntry() | ||
if (entry.fileName.startsWith('__MACOSX/')) { | ||
this.zipfile.readEntry() | ||
return | ||
} | ||
var destDir = path.dirname(path.join(opts.dir, entry.fileName)) | ||
const destDir = path.dirname(path.join(this.opts.dir, entry.fileName)) | ||
mkdirp(destDir, function (err) { | ||
if (err) { | ||
cancelled = true | ||
zipfile.close() | ||
return cb(err) | ||
} | ||
try { | ||
await fs.mkdir(destDir, { recursive: true }) | ||
fs.realpath(destDir, function (err, canonicalDestDir) { | ||
if (err) { | ||
cancelled = true | ||
zipfile.close() | ||
return cb(err) | ||
} | ||
const canonicalDestDir = await fs.realpath(destDir) | ||
const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir) | ||
var relativeDestDir = path.relative(opts.dir, canonicalDestDir) | ||
if (relativeDestDir.split(path.sep).includes('..')) { | ||
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`) | ||
} | ||
if (relativeDestDir.split(path.sep).indexOf('..') !== -1) { | ||
cancelled = true | ||
zipfile.close() | ||
return cb(new Error('Out of bound path "' + canonicalDestDir + '" found while processing file ' + entry.fileName)) | ||
} | ||
extractEntry(entry, function (err) { | ||
// if any extraction fails then abort everything | ||
if (err) { | ||
cancelled = true | ||
zipfile.close() | ||
return cb(err) | ||
} | ||
debug('finished processing', entry.fileName) | ||
zipfile.readEntry() | ||
}) | ||
}) | ||
}) | ||
await this.extractEntry(entry) | ||
debug('finished processing', entry.fileName) | ||
this.zipfile.readEntry() | ||
} catch (err) { | ||
this.canceled = true | ||
this.zipfile.close() | ||
reject(err) | ||
} | ||
}) | ||
}) | ||
} | ||
function extractEntry (entry, done) { | ||
if (cancelled) { | ||
debug('skipping entry extraction', entry.fileName, {cancelled: cancelled}) | ||
return setImmediate(done) | ||
} | ||
async extractEntry (entry) { | ||
/* istanbul ignore if */ | ||
if (this.canceled) { | ||
debug('skipping entry extraction', entry.fileName, { cancelled: this.canceled }) | ||
return | ||
} | ||
if (opts.onEntry) { | ||
opts.onEntry(entry, zipfile) | ||
} | ||
if (this.opts.onEntry) { | ||
this.opts.onEntry(entry, this.zipfile) | ||
} | ||
var dest = path.join(opts.dir, entry.fileName) | ||
const dest = path.join(this.opts.dir, entry.fileName) | ||
// convert external file attr int into a fs stat mode int | ||
var mode = (entry.externalFileAttributes >> 16) & 0xFFFF | ||
// check if it's a symlink or dir (using stat mode constants) | ||
var IFMT = 61440 | ||
var IFDIR = 16384 | ||
var IFLNK = 40960 | ||
var symlink = (mode & IFMT) === IFLNK | ||
var isDir = (mode & IFMT) === IFDIR | ||
// convert external file attr int into a fs stat mode int | ||
const mode = (entry.externalFileAttributes >> 16) & 0xFFFF | ||
// check if it's a symlink or dir (using stat mode constants) | ||
const IFMT = 61440 | ||
const IFDIR = 16384 | ||
const IFLNK = 40960 | ||
const symlink = (mode & IFMT) === IFLNK | ||
let isDir = (mode & IFMT) === IFDIR | ||
// Failsafe, borrowed from jsZip | ||
if (!isDir && entry.fileName.slice(-1) === '/') { | ||
isDir = true | ||
} | ||
// Failsafe, borrowed from jsZip | ||
if (!isDir && entry.fileName.endsWith('/')) { | ||
isDir = true | ||
} | ||
// check for windows weird way of specifying a directory | ||
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566 | ||
var madeBy = entry.versionMadeBy >> 8 | ||
if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16) | ||
// check for windows weird way of specifying a directory | ||
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566 | ||
const madeBy = entry.versionMadeBy >> 8 | ||
if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16) | ||
// if no mode then default to default modes | ||
if (mode === 0) { | ||
if (isDir) { | ||
if (opts.defaultDirMode) mode = parseInt(opts.defaultDirMode, 10) | ||
if (!mode) mode = 493 // Default to 0755 | ||
} else { | ||
if (opts.defaultFileMode) mode = parseInt(opts.defaultFileMode, 10) | ||
if (!mode) mode = 420 // Default to 0644 | ||
} | ||
} | ||
debug('extracting entry', { filename: entry.fileName, isDir: isDir, isSymlink: symlink }) | ||
debug('extracting entry', { filename: entry.fileName, isDir: isDir, isSymlink: symlink }) | ||
// reverse umask first (~) | ||
const umask = ~process.umask() | ||
// & with processes umask to override invalid perms | ||
const procMode = this.getExtractedMode(mode, isDir) & umask | ||
// reverse umask first (~) | ||
var umask = ~process.umask() | ||
// & with processes umask to override invalid perms | ||
var procMode = mode & umask | ||
// always ensure folders are created | ||
const destDir = isDir ? dest : path.dirname(dest) | ||
// always ensure folders are created | ||
var destDir = dest | ||
if (!isDir) destDir = path.dirname(dest) | ||
const mkdirOptions = { recursive: true } | ||
if (isDir) { | ||
mkdirOptions.mode = procMode | ||
} | ||
debug('mkdir', { dir: destDir, ...mkdirOptions }) | ||
await fs.mkdir(destDir, mkdirOptions) | ||
if (isDir) return | ||
debug('mkdirp', {dir: destDir}) | ||
mkdirp(destDir, function (err) { | ||
if (err) { | ||
debug('mkdirp error', destDir, {error: err}) | ||
cancelled = true | ||
return done(err) | ||
} | ||
debug('opening read stream', dest) | ||
const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry) | ||
if (isDir) return done() | ||
if (symlink) { | ||
const link = await getStream(readStream) | ||
debug('creating symlink', link, dest) | ||
await fs.symlink(link, dest) | ||
} else { | ||
await pipeline(readStream, createWriteStream(dest, { mode: procMode })) | ||
} | ||
} | ||
debug('opening read stream', dest) | ||
zipfile.openReadStream(entry, function (err, readStream) { | ||
if (err) { | ||
debug('openReadStream error', err) | ||
cancelled = true | ||
return done(err) | ||
} | ||
getExtractedMode (entryMode, isDir) { | ||
let mode = entryMode | ||
// Set defaults, if necessary | ||
if (mode === 0) { | ||
if (isDir) { | ||
if (this.opts.defaultDirMode) { | ||
mode = parseInt(this.opts.defaultDirMode, 10) | ||
} | ||
readStream.on('error', function (err) { | ||
console.log('read err', err) | ||
}) | ||
if (!mode) { | ||
mode = 0o755 | ||
} | ||
} else { | ||
if (this.opts.defaultFileMode) { | ||
mode = parseInt(this.opts.defaultFileMode, 10) | ||
} | ||
if (symlink) writeSymlink() | ||
else writeStream() | ||
if (!mode) { | ||
mode = 0o644 | ||
} | ||
} | ||
} | ||
function writeStream () { | ||
var writeStream = fs.createWriteStream(dest, {mode: procMode}) | ||
readStream.pipe(writeStream) | ||
return mode | ||
} | ||
} | ||
writeStream.on('finish', function () { | ||
done() | ||
}) | ||
module.exports = async function (zipPath, opts) { | ||
debug('creating target directory', opts.dir) | ||
writeStream.on('error', function (err) { | ||
debug('write error', {error: err}) | ||
cancelled = true | ||
return done(err) | ||
}) | ||
} | ||
if (!path.isAbsolute(opts.dir)) { | ||
throw new Error('Target directory is expected to be absolute') | ||
} | ||
// AFAICT the content of the symlink file itself is the symlink target filename string | ||
function writeSymlink () { | ||
readStream.pipe(concat(function (data) { | ||
var link = data.toString() | ||
debug('creating symlink', link, dest) | ||
fs.symlink(link, dest, function (err) { | ||
if (err) cancelled = true | ||
done(err) | ||
}) | ||
})) | ||
} | ||
}) | ||
}) | ||
} | ||
}) | ||
} | ||
await fs.mkdir(opts.dir, { recursive: true }) | ||
opts.dir = await fs.realpath(opts.dir) | ||
return new Extractor(zipPath, opts).extract() | ||
} |
{ | ||
"name": "extract-zip", | ||
"version": "1.7.0", | ||
"version": "2.0.0", | ||
"description": "unzip a zip file into a directory using 100% javascript", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"bin": { | ||
@@ -10,4 +11,14 @@ "extract-zip": "cli.js" | ||
"scripts": { | ||
"test": "standard && node test/test.js" | ||
"ava": "ava", | ||
"coverage": "nyc ava", | ||
"lint": "yarn lint:js && yarn lint:ts && yarn tsd", | ||
"lint:js": "eslint .", | ||
"lint:ts": "eslint --config .eslintrc.typescript.js --ext .ts .", | ||
"test": "yarn lint && ava", | ||
"tsd": "tsd" | ||
}, | ||
"files": [ | ||
"*.js", | ||
"index.d.ts" | ||
], | ||
"author": "max ogden", | ||
@@ -21,17 +32,51 @@ "license": "BSD-2-Clause", | ||
], | ||
"engines": { | ||
"node": ">= 10.12.0" | ||
}, | ||
"dependencies": { | ||
"concat-stream": "^1.6.2", | ||
"debug": "^2.6.9", | ||
"mkdirp": "^0.5.4", | ||
"debug": "^4.1.1", | ||
"get-stream": "^5.1.0", | ||
"yauzl": "^2.10.0" | ||
}, | ||
"optionalDependencies": { | ||
"@types/yauzl": "^2.9.1" | ||
}, | ||
"devDependencies": { | ||
"rimraf": "^2.2.8", | ||
"standard": "^5.2.2", | ||
"tape": "^4.2.0", | ||
"temp": "^0.8.3" | ||
"@typescript-eslint/eslint-plugin": "^2.25.0", | ||
"@typescript-eslint/parser": "^2.25.0", | ||
"ava": "^3.5.1", | ||
"eslint": "^6.8.0", | ||
"eslint-config-standard": "^14.1.1", | ||
"eslint-plugin-ava": "^10.2.0", | ||
"eslint-plugin-import": "^2.20.1", | ||
"eslint-plugin-node": "^11.0.0", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"eslint-plugin-standard": "^4.0.1", | ||
"fs-extra": "^9.0.0", | ||
"husky": "^4.2.3", | ||
"lint-staged": "^10.0.9", | ||
"nyc": "^15.0.0", | ||
"tsd": "^0.11.0", | ||
"typescript": "^3.8.3" | ||
}, | ||
"directories": { | ||
"test": "test" | ||
"eslintConfig": { | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:ava/recommended", | ||
"plugin:import/errors", | ||
"plugin:import/warnings", | ||
"plugin:node/recommended", | ||
"plugin:promise/recommended", | ||
"standard" | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.js": "yarn lint:js --fix", | ||
"*.ts": "yarn lint:ts --fix" | ||
} | ||
} |
@@ -7,8 +7,10 @@ # extract-zip | ||
[![NPM](https://nodei.co/npm/extract-zip.png?global=true)](https://nodei.co/npm/extract-zip/) | ||
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) | ||
[![Build Status](https://travis-ci.org/maxogden/extract-zip.svg?branch=master)](https://travis-ci.org/maxogden/extract-zip) | ||
[![NPM](https://nodei.co/npm/extract-zip.png?global=true)](https://npm.im/extract-zip) | ||
[![Uses JS Standard Style](https://cdn.jsdelivr.net/gh/standard/standard/badge.svg)](https://github.com/standard/standard) | ||
[![Build Status](https://github.com/maxogden/extract-zip/workflows/CI/badge.svg)](https://github.com/maxogden/extract-zip/actions?query=workflow%3ACI) | ||
## Installation | ||
Make sure you have Node 10 or greater installed. | ||
Get the library: | ||
@@ -28,7 +30,13 @@ | ||
```js | ||
var extract = require('extract-zip') | ||
extract(source, {dir: target}, function (err) { | ||
// extraction is complete. make sure to handle the err | ||
}) | ||
```javascript | ||
const extract = require('extract-zip') | ||
async main () { | ||
try { | ||
await extract(source, { dir: target }) | ||
console.log('Extraction complete') | ||
} catch (err) { | ||
// handle any errors | ||
} | ||
} | ||
``` | ||
@@ -38,5 +46,5 @@ | ||
- `dir` - defaults to `process.cwd()` | ||
- `defaultDirMode` - integer - Directory Mode (permissions) will default to `493` (octal `0755` in integer) | ||
- `defaultFileMode` - integer - File Mode (permissions) will default to `420` (octal `0644` in integer) | ||
- `dir` (required) - the path to the directory where the extracted files are written | ||
- `defaultDirMode` - integer - Directory Mode (permissions), defaults to `0o755` | ||
- `defaultFileMode` - integer - File Mode (permissions), defaults to `0o644` | ||
- `onEntry` - function - if present, will be called with `(entry, zipfile)`, entry is every entry from the zip file forwarded from the `entry` event from yauzl. `zipfile` is the `yauzl` instance | ||
@@ -43,0 +51,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11321
58
16
188
1
+ Addedget-stream@^5.1.0
+ Added@types/node@22.7.4(transitive)
+ Added@types/yauzl@2.10.3(transitive)
+ Addeddebug@4.3.7(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedget-stream@5.2.0(transitive)
+ Addedms@2.1.3(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedundici-types@6.19.8(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removedconcat-stream@^1.6.2
- Removedmkdirp@^0.5.4
- Removedbuffer-from@1.1.2(transitive)
- Removedconcat-stream@1.6.2(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddebug@2.6.9(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@1.0.0(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp@0.5.6(transitive)
- Removedms@2.0.0(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedreadable-stream@2.3.8(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedtypedarray@0.0.6(transitive)
- Removedutil-deprecate@1.0.2(transitive)
Updateddebug@^4.1.1