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