1
0

index.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. var fs = require('fs')
  2. var path = require('path')
  3. var yauzl = require('yauzl')
  4. var mkdirp = require('mkdirp')
  5. var concat = require('concat-stream')
  6. module.exports = function (zipPath, opts, cb) {
  7. if (path.isAbsolute(opts.dir) === false) {
  8. return cb(new Error('Target directory is expected to be absolute'))
  9. }
  10. mkdirp(opts.dir, function (err) {
  11. if (err) return cb(err)
  12. fs.realpath(opts.dir, function (err, canonicalDir) {
  13. if (err) return cb(err)
  14. opts.dir = canonicalDir
  15. openZip(opts)
  16. })
  17. })
  18. function openZip () {
  19. yauzl.open(zipPath, {lazyEntries: true}, function (err, zipfile) {
  20. if (err) return cb(err)
  21. var cancelled = false
  22. zipfile.readEntry()
  23. zipfile.on('close', function () {
  24. if (!cancelled) {
  25. cb()
  26. }
  27. })
  28. zipfile.on('entry', function (entry) {
  29. if (cancelled) {
  30. return
  31. }
  32. if (/^__MACOSX\//.test(entry.fileName)) {
  33. // dir name starts with __MACOSX/
  34. zipfile.readEntry()
  35. return
  36. }
  37. var destDir = path.dirname(path.join(opts.dir, entry.fileName))
  38. mkdirp(destDir, function (err) {
  39. if (err) {
  40. cancelled = true
  41. zipfile.close()
  42. return cb(err)
  43. }
  44. fs.realpath(destDir, function (err, canonicalDestDir) {
  45. if (err) {
  46. cancelled = true
  47. zipfile.close()
  48. return cb(err)
  49. }
  50. var relativeDestDir = path.relative(opts.dir, canonicalDestDir)
  51. if (relativeDestDir.split(path.sep).indexOf('..') !== -1) {
  52. cancelled = true
  53. zipfile.close()
  54. return cb(new Error('Out of bound path "' + canonicalDestDir + '" found while processing file ' + entry.fileName))
  55. }
  56. extractEntry(entry, function (err) {
  57. // if any extraction fails then abort everything
  58. if (err) {
  59. cancelled = true
  60. zipfile.close()
  61. return cb(err)
  62. }
  63. zipfile.readEntry()
  64. })
  65. })
  66. })
  67. })
  68. function extractEntry (entry, done) {
  69. if (cancelled) {
  70. return setImmediate(done)
  71. }
  72. if (opts.onEntry) {
  73. opts.onEntry(entry, zipfile)
  74. }
  75. var dest = path.join(opts.dir, entry.fileName)
  76. // convert external file attr int into a fs stat mode int
  77. var mode = (entry.externalFileAttributes >> 16) & 0xFFFF
  78. // check if it's a symlink or dir (using stat mode constants)
  79. var IFMT = 61440
  80. var IFDIR = 16384
  81. var IFLNK = 40960
  82. var symlink = (mode & IFMT) === IFLNK
  83. var isDir = (mode & IFMT) === IFDIR
  84. // Failsafe, borrowed from jsZip
  85. if (!isDir && entry.fileName.slice(-1) === '/') {
  86. isDir = true
  87. }
  88. // check for windows weird way of specifying a directory
  89. // https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
  90. var madeBy = entry.versionMadeBy >> 8
  91. if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16)
  92. // if no mode then default to default modes
  93. if (mode === 0) {
  94. if (isDir) {
  95. if (opts.defaultDirMode) mode = parseInt(opts.defaultDirMode, 10)
  96. if (!mode) mode = 493 // Default to 0755
  97. } else {
  98. if (opts.defaultFileMode) mode = parseInt(opts.defaultFileMode, 10)
  99. if (!mode) mode = 420 // Default to 0644
  100. }
  101. }
  102. // reverse umask first (~)
  103. var umask = ~process.umask()
  104. // & with processes umask to override invalid perms
  105. var procMode = mode & umask
  106. // always ensure folders are created
  107. var destDir = dest
  108. if (!isDir) destDir = path.dirname(dest)
  109. mkdirp(destDir, function (err) {
  110. if (err) {
  111. cancelled = true
  112. return done(err)
  113. }
  114. if (isDir) return done()
  115. zipfile.openReadStream(entry, function (err, readStream) {
  116. if (err) {
  117. cancelled = true
  118. return done(err)
  119. }
  120. readStream.on('error', function (err) {
  121. console.log('read err', err)
  122. })
  123. if (symlink) writeSymlink()
  124. else writeStream()
  125. function writeStream () {
  126. var writeStream = fs.createWriteStream(dest, {mode: procMode})
  127. readStream.pipe(writeStream)
  128. writeStream.on('finish', function () {
  129. done()
  130. })
  131. writeStream.on('error', function (err) {
  132. cancelled = true
  133. return done(err)
  134. })
  135. }
  136. // AFAICT the content of the symlink file itself is the symlink target filename string
  137. function writeSymlink () {
  138. readStream.pipe(concat(function (data) {
  139. var link = data.toString()
  140. fs.symlink(link, dest, function (err) {
  141. if (err) cancelled = true
  142. done(err)
  143. })
  144. }))
  145. }
  146. })
  147. })
  148. }
  149. })
  150. }
  151. }