• 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
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