2 var path = require('path')
3 var yauzl = require('yauzl')
4 var mkdirp = require('mkdirp')
5 var concat = require('concat-stream')
6 var debug = require('debug')('extract-zip')
8 module.exports = function (zipPath, opts, cb) {
9 debug('creating target directory', opts.dir)
11 mkdirp(opts.dir, function (err) {
12 if (err) return cb(err)
17 debug('opening', zipPath, 'with opts', opts)
19 yauzl.open(zipPath, {lazyEntries: true}, function (err, zipfile) {
20 if (err) return cb(err)
26 zipfile.on('close', function () {
28 debug('zip extraction complete')
33 zipfile.on('entry', function (entry) {
35 debug('skipping entry', entry.fileName, {cancelled: cancelled})
39 debug('zipfile entry', entry.fileName)
41 if (/^__MACOSX\//.test(entry.fileName)) {
42 // dir name starts with __MACOSX/
47 extractEntry(entry, function (err) {
48 // if any extraction fails then abort everything
54 debug('finished processing', entry.fileName)
59 function extractEntry (entry, done) {
61 debug('skipping entry extraction', entry.fileName, {cancelled: cancelled})
62 return setImmediate(done)
69 var dest = path.join(opts.dir, entry.fileName)
71 // convert external file attr int into a fs stat mode int
72 var mode = (entry.externalFileAttributes >> 16) & 0xFFFF
73 // check if it's a symlink or dir (using stat mode constants)
77 var symlink = (mode & IFMT) === IFLNK
78 var isDir = (mode & IFMT) === IFDIR
80 // check for windows weird way of specifying a directory
81 // https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
82 var madeBy = entry.versionMadeBy >> 8
83 if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16)
85 // if no mode then default to default modes
88 if (opts.defaultDirMode) mode = parseInt(opts.defaultDirMode, 10)
89 if (!mode) mode = 493 // Default to 0755
91 if (opts.defaultFileMode) mode = parseInt(opts.defaultFileMode, 10)
92 if (!mode) mode = 420 // Default to 0644
96 debug('extracting entry', { filename: entry.fileName, isDir: isDir, isSymlink: symlink })
98 // reverse umask first (~)
99 var umask = ~process.umask()
100 // & with processes umask to override invalid perms
101 var procMode = mode & umask
103 // always ensure folders are created
105 if (!isDir) destDir = path.dirname(dest)
107 debug('mkdirp', {dir: destDir})
108 mkdirp(destDir, function (err) {
110 debug('mkdirp error', destDir, {error: err})
115 if (isDir) return done()
117 debug('opening read stream', dest)
118 zipfile.openReadStream(entry, function (err, readStream) {
120 debug('openReadStream error', err)
125 readStream.on('error', function (err) {
126 console.log('read err', err)
129 if (symlink) writeSymlink()
132 function writeStream () {
133 var writeStream = fs.createWriteStream(dest, {mode: procMode})
134 readStream.pipe(writeStream)
136 writeStream.on('finish', function () {
140 writeStream.on('error', function (err) {
141 debug('write error', {error: err})
147 // AFAICT the content of the symlink file itself is the symlink target filename string
148 function writeSymlink () {
149 readStream.pipe(concat(function (data) {
150 var link = data.toString()
151 debug('creating symlink', link, dest)
152 fs.symlink(link, dest, function (err) {
153 if (err) cancelled = true