"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WriteEntryTar = exports.WriteEntrySync = exports.WriteEntry = void 0; const fs_1 = __importDefault(require("fs")); const minipass_1 = require("minipass"); const path_1 = __importDefault(require("path")); const header_js_1 = require("./header.js"); const mode_fix_js_1 = require("./mode-fix.js"); const normalize_windows_path_js_1 = require("./normalize-windows-path.js"); const options_js_1 = require("./options.js"); const pax_js_1 = require("./pax.js"); const strip_absolute_path_js_1 = require("./strip-absolute-path.js"); const strip_trailing_slashes_js_1 = require("./strip-trailing-slashes.js"); const warn_method_js_1 = require("./warn-method.js"); const winchars = __importStar(require("./winchars.js")); const prefixPath = (path, prefix) => { if (!prefix) { return (0, normalize_windows_path_js_1.normalizeWindowsPath)(path); } path = (0, normalize_windows_path_js_1.normalizeWindowsPath)(path).replace(/^\.(\/|$)/, ''); return (0, strip_trailing_slashes_js_1.stripTrailingSlashes)(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'); class WriteEntry extends minipass_1.Minipass { path; portable; myuid = (process.getuid && process.getuid()) || 0; // until node has builtin pwnam functions, this'll have to do myuser = process.env.USER || ''; maxReadSize; linkCache; statCache; preservePaths; cwd; strict; mtime; noPax; noMtime; prefix; fd; blockLen = 0; blockRemain = 0; buf; pos = 0; remain = 0; length = 0; offset = 0; win32; absolute; header; type; linkpath; stat; onWriteEntry; #hadError = false; constructor(p, opt_ = {}) { const opt = (0, options_js_1.dealias)(opt_); super(); this.path = (0, normalize_windows_path_js_1.normalizeWindowsPath)(p); // suppress atime, ctime, uid, gid, uname, gname this.portable = !!opt.portable; this.maxReadSize = opt.maxReadSize || maxReadSize; this.linkCache = opt.linkCache || new Map(); this.statCache = opt.statCache || new Map(); this.preservePaths = !!opt.preservePaths; this.cwd = (0, normalize_windows_path_js_1.normalizeWindowsPath)(opt.cwd || process.cwd()); this.strict = !!opt.strict; this.noPax = !!opt.noPax; this.noMtime = !!opt.noMtime; this.mtime = opt.mtime; this.prefix = opt.prefix ? (0, normalize_windows_path_js_1.normalizeWindowsPath)(opt.prefix) : undefined; this.onWriteEntry = opt.onWriteEntry; if (typeof opt.onwarn === 'function') { this.on('warn', opt.onwarn); } let pathWarn = false; if (!this.preservePaths) { const [root, stripped] = (0, strip_absolute_path_js_1.stripAbsolutePath)(this.path); if (root && typeof stripped === 'string') { 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 = (0, normalize_windows_path_js_1.normalizeWindowsPath)(opt.absolute || path_1.default.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, }); } const cs = this.statCache.get(this.absolute); if (cs) { this[ONLSTAT](cs); } else { this[LSTAT](); } } warn(code, message, data = {}) { return (0, warn_method_js_1.warnMethod)(this, code, message, data); } emit(ev, ...data) { if (ev === 'error') { this.#hadError = true; } return super.emit(ev, ...data); } [LSTAT]() { fs_1.default.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 (0, mode_fix_js_1.modeFix)(mode, this.type === 'Directory', this.portable); } [PREFIX](path) { return prefixPath(path, this.prefix); } [HEADER]() { /* c8 ignore start */ if (!this.stat) { throw new Error('cannot write header before stat'); } /* c8 ignore stop */ if (this.type === 'Directory' && this.portable) { this.noMtime = true; } this.onWriteEntry?.(this); this.header = new header_js_1.Header({ path: this[PREFIX](this.path), // only apply the prefix to hard links. linkpath: this.type === 'Link' && this.linkpath !== undefined ? 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 ? undefined : this.stat.uid, gid: this.portable ? undefined : this.stat.gid, size: this.stat.size, mtime: this.noMtime ? undefined : this.mtime || this.stat.mtime, /* c8 ignore next */ type: this.type === 'Unsupported' ? undefined : this.type, uname: this.portable ? undefined : this.stat.uid === this.myuid ? this.myuser : '', atime: this.portable ? undefined : this.stat.atime, ctime: this.portable ? undefined : this.stat.ctime, }); if (this.header.encode() && !this.noPax) { super.write(new pax_js_1.Pax({ atime: this.portable ? undefined : this.header.atime, ctime: this.portable ? undefined : this.header.ctime, gid: this.portable ? undefined : this.header.gid, mtime: this.noMtime ? undefined : (this.mtime || this.header.mtime), path: this[PREFIX](this.path), linkpath: this.type === 'Link' && this.linkpath !== undefined ? this[PREFIX](this.linkpath) : this.linkpath, size: this.header.size, uid: this.portable ? undefined : this.header.uid, uname: this.portable ? undefined : this.header.uname, dev: this.portable ? undefined : this.stat.dev, ino: this.portable ? undefined : this.stat.ino, nlink: this.portable ? undefined : this.stat.nlink, }).encode()); } const block = this.header?.block; /* c8 ignore start */ if (!block) { throw new Error('failed to encode header'); } /* c8 ignore stop */ super.write(block); } [DIRECTORY]() { /* c8 ignore start */ if (!this.stat) { throw new Error('cannot create directory entry without stat'); } /* c8 ignore stop */ if (this.path.slice(-1) !== '/') { this.path += '/'; } this.stat.size = 0; this[HEADER](); this.end(); } [SYMLINK]() { fs_1.default.readlink(this.absolute, (er, linkpath) => { if (er) { return this.emit('error', er); } this[ONREADLINK](linkpath); }); } [ONREADLINK](linkpath) { this.linkpath = (0, normalize_windows_path_js_1.normalizeWindowsPath)(linkpath); this[HEADER](); this.end(); } [HARDLINK](linkpath) { /* c8 ignore start */ if (!this.stat) { throw new Error('cannot create link entry without stat'); } /* c8 ignore stop */ this.type = 'Link'; this.linkpath = (0, normalize_windows_path_js_1.normalizeWindowsPath)(path_1.default.relative(this.cwd, linkpath)); this.stat.size = 0; this[HEADER](); this.end(); } [FILE]() { /* c8 ignore start */ if (!this.stat) { throw new Error('cannot create file entry without stat'); } /* c8 ignore stop */ if (this.stat.nlink > 1) { const linkKey = `${this.stat.dev}:${this.stat.ino}`; 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_1.default.open(this.absolute, 'r', (er, fd) => { if (er) { return this.emit('error', er); } this[ONOPENFILE](fd); }); } [ONOPENFILE](fd) { this.fd = fd; if (this.#hadError) { return this[CLOSE](); } /* c8 ignore start */ if (!this.stat) { throw new Error('should stat before calling onopenfile'); } /* c8 ignore start */ 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; if (fd === undefined || buf === undefined) { throw new Error('cannot read file without first opening'); } fs_1.default.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); }); } /* c8 ignore start */ [CLOSE](cb = () => { }) { /* c8 ignore stop */ if (this.fd !== undefined) fs_1.default.close(this.fd, cb); } [ONREAD](bytesRead) { if (bytesRead <= 0 && this.remain > 0) { const er = Object.assign(new Error('encountered unexpected EOF'), { path: this.absolute, syscall: 'read', code: 'EOF', }); return this[CLOSE](() => this.emit('error', er)); } if (bytesRead > this.remain) { const er = Object.assign(new Error('did not encounter expected EOF'), { path: this.absolute, syscall: 'read', code: 'EOF', }); return this[CLOSE](() => this.emit('error', er)); } /* c8 ignore start */ if (!this.buf) { throw new Error('should have created buffer prior to reading'); } /* c8 ignore stop */ // 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 chunk = this.offset === 0 && bytesRead === this.buf.length ? this.buf : this.buf.subarray(this.offset, this.offset + bytesRead); const flushed = this.write(chunk); if (!flushed) { this[AWAITDRAIN](() => this[ONDRAIN]()); } else { this[ONDRAIN](); } } [AWAITDRAIN](cb) { this.once('drain', cb); } write(chunk, encoding, cb) { /* c8 ignore start - just junk to comply with NodeJS.WritableStream */ if (typeof encoding === 'function') { cb = encoding; encoding = undefined; } if (typeof chunk === 'string') { chunk = Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8'); } /* c8 ignore stop */ if (this.blockRemain < chunk.length) { const er = Object.assign(new Error('writing more data than expected'), { path: this.absolute, }); return this.emit('error', er); } this.remain -= chunk.length; this.blockRemain -= chunk.length; this.pos += chunk.length; this.offset += chunk.length; return super.write(chunk, null, cb); } [ONDRAIN]() { if (!this.remain) { if (this.blockRemain) { super.write(Buffer.alloc(this.blockRemain)); } return this[CLOSE](er => er ? this.emit('error', er) : this.end()); } /* c8 ignore start */ if (!this.buf) { throw new Error('buffer lost somehow in ONDRAIN'); } /* c8 ignore stop */ 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](); } } exports.WriteEntry = WriteEntry; class WriteEntrySync extends WriteEntry { sync = true; [LSTAT]() { this[ONLSTAT](fs_1.default.lstatSync(this.absolute)); } [SYMLINK]() { this[ONREADLINK](fs_1.default.readlinkSync(this.absolute)); } [OPENFILE]() { this[ONOPENFILE](fs_1.default.openSync(this.absolute, 'r')); } [READ]() { let threw = true; try { const { fd, buf, offset, length, pos } = this; /* c8 ignore start */ if (fd === undefined || buf === undefined) { throw new Error('fd and buf must be set in READ method'); } /* c8 ignore stop */ const bytesRead = fs_1.default.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(); } /* c8 ignore start */ [CLOSE](cb = () => { }) { /* c8 ignore stop */ if (this.fd !== undefined) fs_1.default.closeSync(this.fd); cb(); } } exports.WriteEntrySync = WriteEntrySync; class WriteEntryTar extends minipass_1.Minipass { blockLen = 0; blockRemain = 0; buf = 0; pos = 0; remain = 0; length = 0; preservePaths; portable; strict; noPax; noMtime; readEntry; type; prefix; path; mode; uid; gid; uname; gname; header; mtime; atime; ctime; linkpath; size; onWriteEntry; warn(code, message, data = {}) { return (0, warn_method_js_1.warnMethod)(this, code, message, data); } constructor(readEntry, opt_ = {}) { const opt = (0, options_js_1.dealias)(opt_); super(); this.preservePaths = !!opt.preservePaths; this.portable = !!opt.portable; this.strict = !!opt.strict; this.noPax = !!opt.noPax; this.noMtime = !!opt.noMtime; this.onWriteEntry = opt.onWriteEntry; this.readEntry = readEntry; const { type } = readEntry; /* c8 ignore start */ if (type === 'Unsupported') { throw new Error('writing entry that should be ignored'); } /* c8 ignore stop */ this.type = type; if (this.type === 'Directory' && this.portable) { this.noMtime = true; } this.prefix = opt.prefix; this.path = (0, normalize_windows_path_js_1.normalizeWindowsPath)(readEntry.path); this.mode = readEntry.mode !== undefined ? this[MODE](readEntry.mode) : undefined; this.uid = this.portable ? undefined : readEntry.uid; this.gid = this.portable ? undefined : readEntry.gid; this.uname = this.portable ? undefined : readEntry.uname; this.gname = this.portable ? undefined : readEntry.gname; this.size = readEntry.size; this.mtime = this.noMtime ? undefined : opt.mtime || readEntry.mtime; this.atime = this.portable ? undefined : readEntry.atime; this.ctime = this.portable ? undefined : readEntry.ctime; this.linkpath = readEntry.linkpath !== undefined ? (0, normalize_windows_path_js_1.normalizeWindowsPath)(readEntry.linkpath) : undefined; if (typeof opt.onwarn === 'function') { this.on('warn', opt.onwarn); } let pathWarn = false; if (!this.preservePaths) { const [root, stripped] = (0, strip_absolute_path_js_1.stripAbsolutePath)(this.path); if (root && typeof stripped === 'string') { this.path = stripped; pathWarn = root; } } this.remain = readEntry.size; this.blockRemain = readEntry.startBlockSize; this.onWriteEntry?.(this); this.header = new header_js_1.Header({ path: this[PREFIX](this.path), linkpath: this.type === 'Link' && this.linkpath !== undefined ? 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 ? undefined : this.uid, gid: this.portable ? undefined : this.gid, size: this.size, mtime: this.noMtime ? undefined : this.mtime, type: this.type, uname: this.portable ? undefined : this.uname, atime: this.portable ? undefined : this.atime, ctime: this.portable ? undefined : 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_js_1.Pax({ atime: this.portable ? undefined : this.atime, ctime: this.portable ? undefined : this.ctime, gid: this.portable ? undefined : this.gid, mtime: this.noMtime ? undefined : this.mtime, path: this[PREFIX](this.path), linkpath: this.type === 'Link' && this.linkpath !== undefined ? this[PREFIX](this.linkpath) : this.linkpath, size: this.size, uid: this.portable ? undefined : this.uid, uname: this.portable ? undefined : this.uname, dev: this.portable ? undefined : this.readEntry.dev, ino: this.portable ? undefined : this.readEntry.ino, nlink: this.portable ? undefined : this.readEntry.nlink, }).encode()); } const b = this.header?.block; /* c8 ignore start */ if (!b) throw new Error('failed to encode header'); /* c8 ignore stop */ super.write(b); readEntry.pipe(this); } [PREFIX](path) { return prefixPath(path, this.prefix); } [MODE](mode) { return (0, mode_fix_js_1.modeFix)(mode, this.type === 'Directory', this.portable); } write(chunk, encoding, cb) { /* c8 ignore start - just junk to comply with NodeJS.WritableStream */ if (typeof encoding === 'function') { cb = encoding; encoding = undefined; } if (typeof chunk === 'string') { chunk = Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8'); } /* c8 ignore stop */ const writeLen = chunk.length; if (writeLen > this.blockRemain) { throw new Error('writing more to entry than is appropriate'); } this.blockRemain -= writeLen; return super.write(chunk, cb); } end(chunk, encoding, cb) { if (this.blockRemain) { super.write(Buffer.alloc(this.blockRemain)); } /* c8 ignore start - just junk to comply with NodeJS.WritableStream */ if (typeof chunk === 'function') { cb = chunk; encoding = undefined; chunk = undefined; } if (typeof encoding === 'function') { cb = encoding; encoding = undefined; } if (typeof chunk === 'string') { chunk = Buffer.from(chunk, encoding ?? 'utf8'); } if (cb) this.once('finish', cb); chunk ? super.end(chunk, cb) : super.end(cb); /* c8 ignore stop */ return this; } } exports.WriteEntryTar = WriteEntryTar; const getType = (stat) => stat.isFile() ? 'File' : stat.isDirectory() ? 'Directory' : stat.isSymbolicLink() ? 'SymbolicLink' : 'Unsupported'; //# sourceMappingURL=write-entry.js.map