123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- 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')
-
- module.exports = function (zipPath, opts, cb) {
- debug('creating target directory', opts.dir)
-
- if (path.isAbsolute(opts.dir) === false) {
- return cb(new Error('Target directory is expected to be absolute'))
- }
-
- mkdirp(opts.dir, function (err) {
- if (err) return cb(err)
-
- fs.realpath(opts.dir, function (err, canonicalDir) {
- if (err) return cb(err)
-
- 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)
- }
- })
- zipfile.readEntry()
-
- zipfile.on('close', function () {
- if (!cancelled) {
- debug('zip extraction complete')
- cb()
- }
- })
-
- zipfile.on('entry', function (entry) {
- if (cancelled) {
- debug('skipping entry', entry.fileName, {cancelled: cancelled})
- return
- }
-
- debug('zipfile entry', entry.fileName)
-
- if (/^__MACOSX\//.test(entry.fileName)) {
- // dir name starts with __MACOSX/
- zipfile.readEntry()
- return
- }
-
- var destDir = path.dirname(path.join(opts.dir, entry.fileName))
-
- mkdirp(destDir, function (err) {
- if (err) {
- cancelled = true
- zipfile.close()
- return cb(err)
- }
-
- fs.realpath(destDir, function (err, canonicalDestDir) {
- if (err) {
- cancelled = true
- zipfile.close()
- return cb(err)
- }
-
- var relativeDestDir = path.relative(opts.dir, canonicalDestDir)
-
- 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()
- })
- })
- })
- })
-
- function extractEntry (entry, done) {
- if (cancelled) {
- debug('skipping entry extraction', entry.fileName, {cancelled: cancelled})
- return setImmediate(done)
- }
-
- if (opts.onEntry) {
- opts.onEntry(entry, zipfile)
- }
-
- var dest = path.join(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
-
- // Failsafe, borrowed from jsZip
- if (!isDir && entry.fileName.slice(-1) === '/') {
- 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)
-
- // 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 })
-
- // reverse umask first (~)
- var umask = ~process.umask()
- // & with processes umask to override invalid perms
- var procMode = mode & umask
-
- // always ensure folders are created
- var destDir = dest
- if (!isDir) destDir = path.dirname(dest)
-
- debug('mkdirp', {dir: destDir})
- mkdirp(destDir, function (err) {
- if (err) {
- debug('mkdirp error', destDir, {error: err})
- cancelled = true
- return done(err)
- }
-
- if (isDir) return done()
-
- debug('opening read stream', dest)
- zipfile.openReadStream(entry, function (err, readStream) {
- if (err) {
- debug('openReadStream error', err)
- cancelled = true
- return done(err)
- }
-
- readStream.on('error', function (err) {
- console.log('read err', err)
- })
-
- if (symlink) writeSymlink()
- else writeStream()
-
- function writeStream () {
- var writeStream = fs.createWriteStream(dest, {mode: procMode})
- readStream.pipe(writeStream)
-
- writeStream.on('finish', function () {
- done()
- })
-
- writeStream.on('error', function (err) {
- debug('write error', {error: err})
- cancelled = true
- return done(err)
- })
- }
-
- // 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)
- })
- }))
- }
- })
- })
- }
- })
- }
- }
|