| 'use strict' | 
| const MiniPass = require('minipass') | 
| const Pax = require('./pax.js') | 
| const Header = require('./header.js') | 
| const fs = require('fs') | 
| const path = require('path') | 
| const normPath = require('./normalize-windows-path.js') | 
| const stripSlash = require('./strip-trailing-slashes.js') | 
|   | 
| const prefixPath = (path, prefix) => { | 
|   if (!prefix) { | 
|     return normPath(path) | 
|   } | 
|   path = normPath(path).replace(/^\.(\/|$)/, '') | 
|   return stripSlash(prefix) + '/' + path | 
| } | 
|   | 
| const maxReadSize = 16 * 1024 * 1024 | 
| const PROCESS = Symbol('process') | 
| const FILE = Symbol('file') | 
| const DIRECTORY = Symbol('directory') | 
| const SYMLINK = Symbol('symlink') | 
| const HARDLINK = Symbol('hardlink') | 
| const HEADER = Symbol('header') | 
| const READ = Symbol('read') | 
| const LSTAT = Symbol('lstat') | 
| const ONLSTAT = Symbol('onlstat') | 
| const ONREAD = Symbol('onread') | 
| const ONREADLINK = Symbol('onreadlink') | 
| const OPENFILE = Symbol('openfile') | 
| const ONOPENFILE = Symbol('onopenfile') | 
| const CLOSE = Symbol('close') | 
| const MODE = Symbol('mode') | 
| const AWAITDRAIN = Symbol('awaitDrain') | 
| const ONDRAIN = Symbol('ondrain') | 
| const PREFIX = Symbol('prefix') | 
| const HAD_ERROR = Symbol('hadError') | 
| const warner = require('./warn-mixin.js') | 
| const winchars = require('./winchars.js') | 
| const stripAbsolutePath = require('./strip-absolute-path.js') | 
|   | 
| const modeFix = require('./mode-fix.js') | 
|   | 
| const WriteEntry = warner(class WriteEntry extends MiniPass { | 
|   constructor (p, opt) { | 
|     opt = opt || {} | 
|     super(opt) | 
|     if (typeof p !== 'string') { | 
|       throw new TypeError('path is required') | 
|     } | 
|     this.path = normPath(p) | 
|     // suppress atime, ctime, uid, gid, uname, gname | 
|     this.portable = !!opt.portable | 
|     // until node has builtin pwnam functions, this'll have to do | 
|     this.myuid = process.getuid && process.getuid() || 0 | 
|     this.myuser = process.env.USER || '' | 
|     this.maxReadSize = opt.maxReadSize || maxReadSize | 
|     this.linkCache = opt.linkCache || new Map() | 
|     this.statCache = opt.statCache || new Map() | 
|     this.preservePaths = !!opt.preservePaths | 
|     this.cwd = normPath(opt.cwd || process.cwd()) | 
|     this.strict = !!opt.strict | 
|     this.noPax = !!opt.noPax | 
|     this.noMtime = !!opt.noMtime | 
|     this.mtime = opt.mtime || null | 
|     this.prefix = opt.prefix ? normPath(opt.prefix) : null | 
|   | 
|     this.fd = null | 
|     this.blockLen = null | 
|     this.blockRemain = null | 
|     this.buf = null | 
|     this.offset = null | 
|     this.length = null | 
|     this.pos = null | 
|     this.remain = null | 
|   | 
|     if (typeof opt.onwarn === 'function') { | 
|       this.on('warn', opt.onwarn) | 
|     } | 
|   | 
|     let pathWarn = false | 
|     if (!this.preservePaths) { | 
|       const [root, stripped] = stripAbsolutePath(this.path) | 
|       if (root) { | 
|         this.path = stripped | 
|         pathWarn = root | 
|       } | 
|     } | 
|   | 
|     this.win32 = !!opt.win32 || process.platform === 'win32' | 
|     if (this.win32) { | 
|       // force the \ to / normalization, since we might not *actually* | 
|       // be on windows, but want \ to be considered a path separator. | 
|       this.path = winchars.decode(this.path.replace(/\\/g, '/')) | 
|       p = p.replace(/\\/g, '/') | 
|     } | 
|   | 
|     this.absolute = normPath(opt.absolute || path.resolve(this.cwd, p)) | 
|   | 
|     if (this.path === '') { | 
|       this.path = './' | 
|     } | 
|   | 
|     if (pathWarn) { | 
|       this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, { | 
|         entry: this, | 
|         path: pathWarn + this.path, | 
|       }) | 
|     } | 
|   | 
|     if (this.statCache.has(this.absolute)) { | 
|       this[ONLSTAT](this.statCache.get(this.absolute)) | 
|     } else { | 
|       this[LSTAT]() | 
|     } | 
|   } | 
|   | 
|   emit (ev, ...data) { | 
|     if (ev === 'error') { | 
|       this[HAD_ERROR] = true | 
|     } | 
|     return super.emit(ev, ...data) | 
|   } | 
|   | 
|   [LSTAT] () { | 
|     fs.lstat(this.absolute, (er, stat) => { | 
|       if (er) { | 
|         return this.emit('error', er) | 
|       } | 
|       this[ONLSTAT](stat) | 
|     }) | 
|   } | 
|   | 
|   [ONLSTAT] (stat) { | 
|     this.statCache.set(this.absolute, stat) | 
|     this.stat = stat | 
|     if (!stat.isFile()) { | 
|       stat.size = 0 | 
|     } | 
|     this.type = getType(stat) | 
|     this.emit('stat', stat) | 
|     this[PROCESS]() | 
|   } | 
|   | 
|   [PROCESS] () { | 
|     switch (this.type) { | 
|       case 'File': return this[FILE]() | 
|       case 'Directory': return this[DIRECTORY]() | 
|       case 'SymbolicLink': return this[SYMLINK]() | 
|       // unsupported types are ignored. | 
|       default: return this.end() | 
|     } | 
|   } | 
|   | 
|   [MODE] (mode) { | 
|     return modeFix(mode, this.type === 'Directory', this.portable) | 
|   } | 
|   | 
|   [PREFIX] (path) { | 
|     return prefixPath(path, this.prefix) | 
|   } | 
|   | 
|   [HEADER] () { | 
|     if (this.type === 'Directory' && this.portable) { | 
|       this.noMtime = true | 
|     } | 
|   | 
|     this.header = new Header({ | 
|       path: this[PREFIX](this.path), | 
|       // only apply the prefix to hard links. | 
|       linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) | 
|       : this.linkpath, | 
|       // only the permissions and setuid/setgid/sticky bitflags | 
|       // not the higher-order bits that specify file type | 
|       mode: this[MODE](this.stat.mode), | 
|       uid: this.portable ? null : this.stat.uid, | 
|       gid: this.portable ? null : this.stat.gid, | 
|       size: this.stat.size, | 
|       mtime: this.noMtime ? null : this.mtime || this.stat.mtime, | 
|       type: this.type, | 
|       uname: this.portable ? null : | 
|       this.stat.uid === this.myuid ? this.myuser : '', | 
|       atime: this.portable ? null : this.stat.atime, | 
|       ctime: this.portable ? null : this.stat.ctime, | 
|     }) | 
|   | 
|     if (this.header.encode() && !this.noPax) { | 
|       super.write(new Pax({ | 
|         atime: this.portable ? null : this.header.atime, | 
|         ctime: this.portable ? null : this.header.ctime, | 
|         gid: this.portable ? null : this.header.gid, | 
|         mtime: this.noMtime ? null : this.mtime || this.header.mtime, | 
|         path: this[PREFIX](this.path), | 
|         linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) | 
|         : this.linkpath, | 
|         size: this.header.size, | 
|         uid: this.portable ? null : this.header.uid, | 
|         uname: this.portable ? null : this.header.uname, | 
|         dev: this.portable ? null : this.stat.dev, | 
|         ino: this.portable ? null : this.stat.ino, | 
|         nlink: this.portable ? null : this.stat.nlink, | 
|       }).encode()) | 
|     } | 
|     super.write(this.header.block) | 
|   } | 
|   | 
|   [DIRECTORY] () { | 
|     if (this.path.slice(-1) !== '/') { | 
|       this.path += '/' | 
|     } | 
|     this.stat.size = 0 | 
|     this[HEADER]() | 
|     this.end() | 
|   } | 
|   | 
|   [SYMLINK] () { | 
|     fs.readlink(this.absolute, (er, linkpath) => { | 
|       if (er) { | 
|         return this.emit('error', er) | 
|       } | 
|       this[ONREADLINK](linkpath) | 
|     }) | 
|   } | 
|   | 
|   [ONREADLINK] (linkpath) { | 
|     this.linkpath = normPath(linkpath) | 
|     this[HEADER]() | 
|     this.end() | 
|   } | 
|   | 
|   [HARDLINK] (linkpath) { | 
|     this.type = 'Link' | 
|     this.linkpath = normPath(path.relative(this.cwd, linkpath)) | 
|     this.stat.size = 0 | 
|     this[HEADER]() | 
|     this.end() | 
|   } | 
|   | 
|   [FILE] () { | 
|     if (this.stat.nlink > 1) { | 
|       const linkKey = this.stat.dev + ':' + this.stat.ino | 
|       if (this.linkCache.has(linkKey)) { | 
|         const linkpath = this.linkCache.get(linkKey) | 
|         if (linkpath.indexOf(this.cwd) === 0) { | 
|           return this[HARDLINK](linkpath) | 
|         } | 
|       } | 
|       this.linkCache.set(linkKey, this.absolute) | 
|     } | 
|   | 
|     this[HEADER]() | 
|     if (this.stat.size === 0) { | 
|       return this.end() | 
|     } | 
|   | 
|     this[OPENFILE]() | 
|   } | 
|   | 
|   [OPENFILE] () { | 
|     fs.open(this.absolute, 'r', (er, fd) => { | 
|       if (er) { | 
|         return this.emit('error', er) | 
|       } | 
|       this[ONOPENFILE](fd) | 
|     }) | 
|   } | 
|   | 
|   [ONOPENFILE] (fd) { | 
|     this.fd = fd | 
|     if (this[HAD_ERROR]) { | 
|       return this[CLOSE]() | 
|     } | 
|   | 
|     this.blockLen = 512 * Math.ceil(this.stat.size / 512) | 
|     this.blockRemain = this.blockLen | 
|     const bufLen = Math.min(this.blockLen, this.maxReadSize) | 
|     this.buf = Buffer.allocUnsafe(bufLen) | 
|     this.offset = 0 | 
|     this.pos = 0 | 
|     this.remain = this.stat.size | 
|     this.length = this.buf.length | 
|     this[READ]() | 
|   } | 
|   | 
|   [READ] () { | 
|     const { fd, buf, offset, length, pos } = this | 
|     fs.read(fd, buf, offset, length, pos, (er, bytesRead) => { | 
|       if (er) { | 
|         // ignoring the error from close(2) is a bad practice, but at | 
|         // this point we already have an error, don't need another one | 
|         return this[CLOSE](() => this.emit('error', er)) | 
|       } | 
|       this[ONREAD](bytesRead) | 
|     }) | 
|   } | 
|   | 
|   [CLOSE] (cb) { | 
|     fs.close(this.fd, cb) | 
|   } | 
|   | 
|   [ONREAD] (bytesRead) { | 
|     if (bytesRead <= 0 && this.remain > 0) { | 
|       const er = new Error('encountered unexpected EOF') | 
|       er.path = this.absolute | 
|       er.syscall = 'read' | 
|       er.code = 'EOF' | 
|       return this[CLOSE](() => this.emit('error', er)) | 
|     } | 
|   | 
|     if (bytesRead > this.remain) { | 
|       const er = new Error('did not encounter expected EOF') | 
|       er.path = this.absolute | 
|       er.syscall = 'read' | 
|       er.code = 'EOF' | 
|       return this[CLOSE](() => this.emit('error', er)) | 
|     } | 
|   | 
|     // null out the rest of the buffer, if we could fit the block padding | 
|     // at the end of this loop, we've incremented bytesRead and this.remain | 
|     // to be incremented up to the blockRemain level, as if we had expected | 
|     // to get a null-padded file, and read it until the end.  then we will | 
|     // decrement both remain and blockRemain by bytesRead, and know that we | 
|     // reached the expected EOF, without any null buffer to append. | 
|     if (bytesRead === this.remain) { | 
|       for (let i = bytesRead; i < this.length && bytesRead < this.blockRemain; i++) { | 
|         this.buf[i + this.offset] = 0 | 
|         bytesRead++ | 
|         this.remain++ | 
|       } | 
|     } | 
|   | 
|     const writeBuf = this.offset === 0 && bytesRead === this.buf.length ? | 
|       this.buf : this.buf.slice(this.offset, this.offset + bytesRead) | 
|   | 
|     const flushed = this.write(writeBuf) | 
|     if (!flushed) { | 
|       this[AWAITDRAIN](() => this[ONDRAIN]()) | 
|     } else { | 
|       this[ONDRAIN]() | 
|     } | 
|   } | 
|   | 
|   [AWAITDRAIN] (cb) { | 
|     this.once('drain', cb) | 
|   } | 
|   | 
|   write (writeBuf) { | 
|     if (this.blockRemain < writeBuf.length) { | 
|       const er = new Error('writing more data than expected') | 
|       er.path = this.absolute | 
|       return this.emit('error', er) | 
|     } | 
|     this.remain -= writeBuf.length | 
|     this.blockRemain -= writeBuf.length | 
|     this.pos += writeBuf.length | 
|     this.offset += writeBuf.length | 
|     return super.write(writeBuf) | 
|   } | 
|   | 
|   [ONDRAIN] () { | 
|     if (!this.remain) { | 
|       if (this.blockRemain) { | 
|         super.write(Buffer.alloc(this.blockRemain)) | 
|       } | 
|       return this[CLOSE](er => er ? this.emit('error', er) : this.end()) | 
|     } | 
|   | 
|     if (this.offset >= this.length) { | 
|       // if we only have a smaller bit left to read, alloc a smaller buffer | 
|       // otherwise, keep it the same length it was before. | 
|       this.buf = Buffer.allocUnsafe(Math.min(this.blockRemain, this.buf.length)) | 
|       this.offset = 0 | 
|     } | 
|     this.length = this.buf.length - this.offset | 
|     this[READ]() | 
|   } | 
| }) | 
|   | 
| class WriteEntrySync extends WriteEntry { | 
|   [LSTAT] () { | 
|     this[ONLSTAT](fs.lstatSync(this.absolute)) | 
|   } | 
|   | 
|   [SYMLINK] () { | 
|     this[ONREADLINK](fs.readlinkSync(this.absolute)) | 
|   } | 
|   | 
|   [OPENFILE] () { | 
|     this[ONOPENFILE](fs.openSync(this.absolute, 'r')) | 
|   } | 
|   | 
|   [READ] () { | 
|     let threw = true | 
|     try { | 
|       const { fd, buf, offset, length, pos } = this | 
|       const bytesRead = fs.readSync(fd, buf, offset, length, pos) | 
|       this[ONREAD](bytesRead) | 
|       threw = false | 
|     } finally { | 
|       // ignoring the error from close(2) is a bad practice, but at | 
|       // this point we already have an error, don't need another one | 
|       if (threw) { | 
|         try { | 
|           this[CLOSE](() => {}) | 
|         } catch (er) {} | 
|       } | 
|     } | 
|   } | 
|   | 
|   [AWAITDRAIN] (cb) { | 
|     cb() | 
|   } | 
|   | 
|   [CLOSE] (cb) { | 
|     fs.closeSync(this.fd) | 
|     cb() | 
|   } | 
| } | 
|   | 
| const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { | 
|   constructor (readEntry, opt) { | 
|     opt = opt || {} | 
|     super(opt) | 
|     this.preservePaths = !!opt.preservePaths | 
|     this.portable = !!opt.portable | 
|     this.strict = !!opt.strict | 
|     this.noPax = !!opt.noPax | 
|     this.noMtime = !!opt.noMtime | 
|   | 
|     this.readEntry = readEntry | 
|     this.type = readEntry.type | 
|     if (this.type === 'Directory' && this.portable) { | 
|       this.noMtime = true | 
|     } | 
|   | 
|     this.prefix = opt.prefix || null | 
|   | 
|     this.path = normPath(readEntry.path) | 
|     this.mode = this[MODE](readEntry.mode) | 
|     this.uid = this.portable ? null : readEntry.uid | 
|     this.gid = this.portable ? null : readEntry.gid | 
|     this.uname = this.portable ? null : readEntry.uname | 
|     this.gname = this.portable ? null : readEntry.gname | 
|     this.size = readEntry.size | 
|     this.mtime = this.noMtime ? null : opt.mtime || readEntry.mtime | 
|     this.atime = this.portable ? null : readEntry.atime | 
|     this.ctime = this.portable ? null : readEntry.ctime | 
|     this.linkpath = normPath(readEntry.linkpath) | 
|   | 
|     if (typeof opt.onwarn === 'function') { | 
|       this.on('warn', opt.onwarn) | 
|     } | 
|   | 
|     let pathWarn = false | 
|     if (!this.preservePaths) { | 
|       const [root, stripped] = stripAbsolutePath(this.path) | 
|       if (root) { | 
|         this.path = stripped | 
|         pathWarn = root | 
|       } | 
|     } | 
|   | 
|     this.remain = readEntry.size | 
|     this.blockRemain = readEntry.startBlockSize | 
|   | 
|     this.header = new Header({ | 
|       path: this[PREFIX](this.path), | 
|       linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) | 
|       : this.linkpath, | 
|       // only the permissions and setuid/setgid/sticky bitflags | 
|       // not the higher-order bits that specify file type | 
|       mode: this.mode, | 
|       uid: this.portable ? null : this.uid, | 
|       gid: this.portable ? null : this.gid, | 
|       size: this.size, | 
|       mtime: this.noMtime ? null : this.mtime, | 
|       type: this.type, | 
|       uname: this.portable ? null : this.uname, | 
|       atime: this.portable ? null : this.atime, | 
|       ctime: this.portable ? null : this.ctime, | 
|     }) | 
|   | 
|     if (pathWarn) { | 
|       this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, { | 
|         entry: this, | 
|         path: pathWarn + this.path, | 
|       }) | 
|     } | 
|   | 
|     if (this.header.encode() && !this.noPax) { | 
|       super.write(new Pax({ | 
|         atime: this.portable ? null : this.atime, | 
|         ctime: this.portable ? null : this.ctime, | 
|         gid: this.portable ? null : this.gid, | 
|         mtime: this.noMtime ? null : this.mtime, | 
|         path: this[PREFIX](this.path), | 
|         linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) | 
|         : this.linkpath, | 
|         size: this.size, | 
|         uid: this.portable ? null : this.uid, | 
|         uname: this.portable ? null : this.uname, | 
|         dev: this.portable ? null : this.readEntry.dev, | 
|         ino: this.portable ? null : this.readEntry.ino, | 
|         nlink: this.portable ? null : this.readEntry.nlink, | 
|       }).encode()) | 
|     } | 
|   | 
|     super.write(this.header.block) | 
|     readEntry.pipe(this) | 
|   } | 
|   | 
|   [PREFIX] (path) { | 
|     return prefixPath(path, this.prefix) | 
|   } | 
|   | 
|   [MODE] (mode) { | 
|     return modeFix(mode, this.type === 'Directory', this.portable) | 
|   } | 
|   | 
|   write (data) { | 
|     const writeLen = data.length | 
|     if (writeLen > this.blockRemain) { | 
|       throw new Error('writing more to entry than is appropriate') | 
|     } | 
|     this.blockRemain -= writeLen | 
|     return super.write(data) | 
|   } | 
|   | 
|   end () { | 
|     if (this.blockRemain) { | 
|       super.write(Buffer.alloc(this.blockRemain)) | 
|     } | 
|     return super.end() | 
|   } | 
| }) | 
|   | 
| WriteEntry.Sync = WriteEntrySync | 
| WriteEntry.Tar = WriteEntryTar | 
|   | 
| const getType = stat => | 
|   stat.isFile() ? 'File' | 
|   : stat.isDirectory() ? 'Directory' | 
|   : stat.isSymbolicLink() ? 'SymbolicLink' | 
|   : 'Unsupported' | 
|   | 
| module.exports = WriteEntry |