123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- import fs from 'fs';
- import { Minipass } from 'minipass';
- import path from 'path';
- import { Header } from './header.js';
- import { modeFix } from './mode-fix.js';
- import { normalizeWindowsPath } from './normalize-windows-path.js';
- import { dealias, } from './options.js';
- import { Pax } from './pax.js';
- import { stripAbsolutePath } from './strip-absolute-path.js';
- import { stripTrailingSlashes } from './strip-trailing-slashes.js';
- import { warnMethod, } from './warn-method.js';
- import * as winchars from './winchars.js';
- const prefixPath = (path, prefix) => {
- if (!prefix) {
- return normalizeWindowsPath(path);
- }
- path = normalizeWindowsPath(path).replace(/^\.(\/|$)/, '');
- return 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');
- export class WriteEntry extends 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 = dealias(opt_);
- super();
- this.path = 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 = 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 ? 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] = 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 = normalizeWindowsPath(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,
- });
- }
- const cs = this.statCache.get(this.absolute);
- if (cs) {
- this[ONLSTAT](cs);
- }
- else {
- this[LSTAT]();
- }
- }
- warn(code, message, data = {}) {
- return warnMethod(this, code, message, data);
- }
- emit(ev, ...data) {
- if (ev === 'error') {
- this.#hadError = 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]() {
- /* 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({
- 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({
- 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.readlink(this.absolute, (er, linkpath) => {
- if (er) {
- return this.emit('error', er);
- }
- this[ONREADLINK](linkpath);
- });
- }
- [ONREADLINK](linkpath) {
- this.linkpath = 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 = normalizeWindowsPath(path.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.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.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.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]();
- }
- }
- export class WriteEntrySync extends WriteEntry {
- sync = true;
- [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;
- /* 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.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.closeSync(this.fd);
- cb();
- }
- }
- export class WriteEntryTar extends 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 warnMethod(this, code, message, data);
- }
- constructor(readEntry, opt_ = {}) {
- const opt = 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 = 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 ?
- normalizeWindowsPath(readEntry.linkpath)
- : undefined;
- if (typeof opt.onwarn === 'function') {
- this.on('warn', opt.onwarn);
- }
- let pathWarn = false;
- if (!this.preservePaths) {
- const [root, stripped] = 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({
- 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({
- 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 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;
- }
- }
- const getType = (stat) => stat.isFile() ? 'File'
- : stat.isDirectory() ? 'Directory'
- : stat.isSymbolicLink() ? 'SymbolicLink'
- : 'Unsupported';
- //# sourceMappingURL=write-entry.js.map
|