You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							211 lines
						
					
					
						
							6.1 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							211 lines
						
					
					
						
							6.1 KiB
						
					
					
				
								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)
							 | 
						|
								                })
							 | 
						|
								              }))
							 | 
						|
								            }
							 | 
						|
								          })
							 | 
						|
								        })
							 | 
						|
								      }
							 | 
						|
								    })
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 |