1'use strict' 2 3// tar -x 4const hlo = require('./high-level-opt.js') 5const Unpack = require('./unpack.js') 6const fs = require('fs') 7const fsm = require('fs-minipass') 8const path = require('path') 9const stripSlash = require('./strip-trailing-slashes.js') 10 11module.exports = (opt_, files, cb) => { 12 if (typeof opt_ === 'function') { 13 cb = opt_, files = null, opt_ = {} 14 } else if (Array.isArray(opt_)) { 15 files = opt_, opt_ = {} 16 } 17 18 if (typeof files === 'function') { 19 cb = files, files = null 20 } 21 22 if (!files) { 23 files = [] 24 } else { 25 files = Array.from(files) 26 } 27 28 const opt = hlo(opt_) 29 30 if (opt.sync && typeof cb === 'function') { 31 throw new TypeError('callback not supported for sync tar functions') 32 } 33 34 if (!opt.file && typeof cb === 'function') { 35 throw new TypeError('callback only supported with file option') 36 } 37 38 if (files.length) { 39 filesFilter(opt, files) 40 } 41 42 return opt.file && opt.sync ? extractFileSync(opt) 43 : opt.file ? extractFile(opt, cb) 44 : opt.sync ? extractSync(opt) 45 : extract(opt) 46} 47 48// construct a filter that limits the file entries listed 49// include child entries if a dir is included 50const filesFilter = (opt, files) => { 51 const map = new Map(files.map(f => [stripSlash(f), true])) 52 const filter = opt.filter 53 54 const mapHas = (file, r) => { 55 const root = r || path.parse(file).root || '.' 56 const ret = file === root ? false 57 : map.has(file) ? map.get(file) 58 : mapHas(path.dirname(file), root) 59 60 map.set(file, ret) 61 return ret 62 } 63 64 opt.filter = filter 65 ? (file, entry) => filter(file, entry) && mapHas(stripSlash(file)) 66 : file => mapHas(stripSlash(file)) 67} 68 69const extractFileSync = opt => { 70 const u = new Unpack.Sync(opt) 71 72 const file = opt.file 73 const stat = fs.statSync(file) 74 // This trades a zero-byte read() syscall for a stat 75 // However, it will usually result in less memory allocation 76 const readSize = opt.maxReadSize || 16 * 1024 * 1024 77 const stream = new fsm.ReadStreamSync(file, { 78 readSize: readSize, 79 size: stat.size, 80 }) 81 stream.pipe(u) 82} 83 84const extractFile = (opt, cb) => { 85 const u = new Unpack(opt) 86 const readSize = opt.maxReadSize || 16 * 1024 * 1024 87 88 const file = opt.file 89 const p = new Promise((resolve, reject) => { 90 u.on('error', reject) 91 u.on('close', resolve) 92 93 // This trades a zero-byte read() syscall for a stat 94 // However, it will usually result in less memory allocation 95 fs.stat(file, (er, stat) => { 96 if (er) { 97 reject(er) 98 } else { 99 const stream = new fsm.ReadStream(file, { 100 readSize: readSize, 101 size: stat.size, 102 }) 103 stream.on('error', reject) 104 stream.pipe(u) 105 } 106 }) 107 }) 108 return cb ? p.then(cb, cb) : p 109} 110 111const extractSync = opt => new Unpack.Sync(opt) 112 113const extract = opt => new Unpack(opt) 114