diff options
Diffstat (limited to 'desktop/node_modules/extract-zip/index.js')
-rw-r--r-- | desktop/node_modules/extract-zip/index.js | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/desktop/node_modules/extract-zip/index.js b/desktop/node_modules/extract-zip/index.js new file mode 100644 index 0000000..23384ea --- /dev/null +++ b/desktop/node_modules/extract-zip/index.js @@ -0,0 +1,173 @@ +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') + +const openZip = promisify(yauzl.open) +const pipeline = promisify(stream.pipeline) + +class Extractor { + constructor (zipPath, opts) { + this.zipPath = zipPath + this.opts = opts + } + + async extract () { + debug('opening', this.zipPath, 'with opts', this.opts) + + this.zipfile = await openZip(this.zipPath, { lazyEntries: true }) + this.canceled = false + + return new Promise((resolve, reject) => { + this.zipfile.on('error', err => { + this.canceled = true + reject(err) + }) + this.zipfile.readEntry() + + this.zipfile.on('close', () => { + if (!this.canceled) { + debug('zip extraction complete') + resolve() + } + }) + + this.zipfile.on('entry', async entry => { + /* istanbul ignore if */ + if (this.canceled) { + debug('skipping entry', entry.fileName, { cancelled: this.canceled }) + return + } + + debug('zipfile entry', entry.fileName) + + if (entry.fileName.startsWith('__MACOSX/')) { + this.zipfile.readEntry() + return + } + + const destDir = path.dirname(path.join(this.opts.dir, entry.fileName)) + + try { + await fs.mkdir(destDir, { recursive: true }) + + const canonicalDestDir = await fs.realpath(destDir) + const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir) + + if (relativeDestDir.split(path.sep).includes('..')) { + throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`) + } + + await this.extractEntry(entry) + debug('finished processing', entry.fileName) + this.zipfile.readEntry() + } catch (err) { + this.canceled = true + this.zipfile.close() + reject(err) + } + }) + }) + } + + async extractEntry (entry) { + /* istanbul ignore if */ + if (this.canceled) { + debug('skipping entry extraction', entry.fileName, { cancelled: this.canceled }) + return + } + + if (this.opts.onEntry) { + this.opts.onEntry(entry, this.zipfile) + } + + const dest = path.join(this.opts.dir, entry.fileName) + + // 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.endsWith('/')) { + isDir = true + } + + // 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) + + debug('extracting entry', { filename: entry.fileName, isDir: isDir, isSymlink: symlink }) + + const procMode = this.getExtractedMode(mode, isDir) & 0o777 + + // always ensure folders are created + const destDir = isDir ? dest : 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('opening read stream', dest) + const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry) + + 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 })) + } + } + + 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) + } + + if (!mode) { + mode = 0o755 + } + } else { + if (this.opts.defaultFileMode) { + mode = parseInt(this.opts.defaultFileMode, 10) + } + + if (!mode) { + mode = 0o644 + } + } + } + + return mode + } +} + +module.exports = async function (zipPath, opts) { + debug('creating target directory', opts.dir) + + if (!path.isAbsolute(opts.dir)) { + throw new Error('Target directory is expected to be absolute') + } + + await fs.mkdir(opts.dir, { recursive: true }) + opts.dir = await fs.realpath(opts.dir) + return new Extractor(zipPath, opts).extract() +} |