1'use strict' 2// wrapper around mkdirp for tar's needs. 3 4// TODO: This should probably be a class, not functionally 5// passing around state in a gazillion args. 6 7const mkdirp = require('mkdirp') 8const fs = require('fs') 9const path = require('path') 10const chownr = require('chownr') 11const normPath = require('./normalize-windows-path.js') 12 13class SymlinkError extends Error { 14 constructor (symlink, path) { 15 super('Cannot extract through symbolic link') 16 this.path = path 17 this.symlink = symlink 18 } 19 20 get name () { 21 return 'SylinkError' 22 } 23} 24 25class CwdError extends Error { 26 constructor (path, code) { 27 super(code + ': Cannot cd into \'' + path + '\'') 28 this.path = path 29 this.code = code 30 } 31 32 get name () { 33 return 'CwdError' 34 } 35} 36 37const cGet = (cache, key) => cache.get(normPath(key)) 38const cSet = (cache, key, val) => cache.set(normPath(key), val) 39 40const checkCwd = (dir, cb) => { 41 fs.stat(dir, (er, st) => { 42 if (er || !st.isDirectory()) 43 er = new CwdError(dir, er && er.code || 'ENOTDIR') 44 cb(er) 45 }) 46} 47 48module.exports = (dir, opt, cb) => { 49 dir = normPath(dir) 50 51 // if there's any overlap between mask and mode, 52 // then we'll need an explicit chmod 53 const umask = opt.umask 54 const mode = opt.mode | 0o0700 55 const needChmod = (mode & umask) !== 0 56 57 const uid = opt.uid 58 const gid = opt.gid 59 const doChown = typeof uid === 'number' && 60 typeof gid === 'number' && 61 ( uid !== opt.processUid || gid !== opt.processGid ) 62 63 const preserve = opt.preserve 64 const unlink = opt.unlink 65 const cache = opt.cache 66 const cwd = normPath(opt.cwd) 67 68 const done = (er, created) => { 69 if (er) 70 cb(er) 71 else { 72 cSet(cache, dir, true) 73 if (created && doChown) 74 chownr(created, uid, gid, er => done(er)) 75 else if (needChmod) 76 fs.chmod(dir, mode, cb) 77 else 78 cb() 79 } 80 } 81 82 if (cache && cGet(cache, dir) === true) 83 return done() 84 85 if (dir === cwd) 86 return checkCwd(dir, done) 87 88 if (preserve) 89 return mkdirp(dir, mode, done) 90 91 const sub = normPath(path.relative(cwd, dir)) 92 const parts = sub.split('/') 93 mkdir_(cwd, parts, mode, cache, unlink, cwd, null, done) 94} 95 96const mkdir_ = (base, parts, mode, cache, unlink, cwd, created, cb) => { 97 if (!parts.length) 98 return cb(null, created) 99 const p = parts.shift() 100 const part = normPath(path.resolve(base + '/' + p)) 101 if (cGet(cache, part)) 102 return mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 103 fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb)) 104} 105 106const onmkdir = (part, parts, mode, cache, unlink, cwd, created, cb) => er => { 107 if (er) { 108 fs.lstat(part, (statEr, st) => { 109 if (statEr) { 110 statEr.path = statEr.path && normPath(statEr.path) 111 cb(statEr) 112 } else if (st.isDirectory()) 113 mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 114 else if (unlink) 115 fs.unlink(part, er => { 116 if (er) 117 return cb(er) 118 fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb)) 119 }) 120 else if (st.isSymbolicLink()) 121 return cb(new SymlinkError(part, part + '/' + parts.join('/'))) 122 else 123 cb(er) 124 }) 125 } else { 126 created = created || part 127 mkdir_(part, parts, mode, cache, unlink, cwd, created, cb) 128 } 129} 130 131const checkCwdSync = dir => { 132 let ok = false 133 let code = 'ENOTDIR' 134 try { 135 ok = fs.statSync(dir).isDirectory() 136 } catch (er) { 137 code = er.code 138 } finally { 139 if (!ok) 140 throw new CwdError(dir, code) 141 } 142} 143 144module.exports.sync = (dir, opt) => { 145 dir = normPath(dir) 146 // if there's any overlap between mask and mode, 147 // then we'll need an explicit chmod 148 const umask = opt.umask 149 const mode = opt.mode | 0o0700 150 const needChmod = (mode & umask) !== 0 151 152 const uid = opt.uid 153 const gid = opt.gid 154 const doChown = typeof uid === 'number' && 155 typeof gid === 'number' && 156 ( uid !== opt.processUid || gid !== opt.processGid ) 157 158 const preserve = opt.preserve 159 const unlink = opt.unlink 160 const cache = opt.cache 161 const cwd = normPath(opt.cwd) 162 163 const done = (created) => { 164 cSet(cache, dir, true) 165 if (created && doChown) 166 chownr.sync(created, uid, gid) 167 if (needChmod) 168 fs.chmodSync(dir, mode) 169 } 170 171 if (cache && cGet(cache, dir) === true) 172 return done() 173 174 if (dir === cwd) { 175 checkCwdSync(cwd) 176 return done() 177 } 178 179 if (preserve) 180 return done(mkdirp.sync(dir, mode)) 181 182 const sub = normPath(path.relative(cwd, dir)) 183 const parts = sub.split('/') 184 let created = null 185 for (let p = parts.shift(), part = cwd; 186 p && (part += '/' + p); 187 p = parts.shift()) { 188 part = normPath(path.resolve(part)) 189 if (cGet(cache, part)) 190 continue 191 192 try { 193 fs.mkdirSync(part, mode) 194 created = created || part 195 cSet(cache, part, true) 196 } catch (er) { 197 const st = fs.lstatSync(part) 198 if (st.isDirectory()) { 199 cSet(cache, part, true) 200 continue 201 } else if (unlink) { 202 fs.unlinkSync(part) 203 fs.mkdirSync(part, mode) 204 created = created || part 205 cSet(cache, part, true) 206 continue 207 } else if (st.isSymbolicLink()) 208 return new SymlinkError(part, part + '/' + parts.join('/')) 209 } 210 } 211 212 return done(created) 213} 214