diff options
Diffstat (limited to 'node_modules/tar/dist/esm/parse.js')
| -rw-r--r-- | node_modules/tar/dist/esm/parse.js | 651 |
1 files changed, 0 insertions, 651 deletions
diff --git a/node_modules/tar/dist/esm/parse.js b/node_modules/tar/dist/esm/parse.js deleted file mode 100644 index 95e42dc..0000000 --- a/node_modules/tar/dist/esm/parse.js +++ /dev/null @@ -1,651 +0,0 @@ -// this[BUFFER] is the remainder of a chunk if we're waiting for -// the full 512 bytes of a header to come in. We will Buffer.concat() -// it to the next write(), which is a mem copy, but a small one. -// -// this[QUEUE] is a list of entries that haven't been emitted -// yet this can only get filled up if the user keeps write()ing after -// a write() returns false, or does a write() with more than one entry -// -// We don't buffer chunks, we always parse them and either create an -// entry, or push it into the active entry. The ReadEntry class knows -// to throw data away if .ignore=true -// -// Shift entry off the buffer when it emits 'end', and emit 'entry' for -// the next one in the list. -// -// At any time, we're pushing body chunks into the entry at WRITEENTRY, -// and waiting for 'end' on the entry at READENTRY -// -// ignored entries get .resume() called on them straight away -import { EventEmitter as EE } from 'events'; -import { BrotliDecompress, Unzip, ZstdDecompress } from 'minizlib'; -import { Header } from './header.js'; -import { Pax } from './pax.js'; -import { ReadEntry } from './read-entry.js'; -import { warnMethod } from './warn-method.js'; -const maxMetaEntrySize = 1024 * 1024; -const gzipHeader = Buffer.from([0x1f, 0x8b]); -const zstdHeader = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]); -const ZIP_HEADER_LEN = Math.max(gzipHeader.length, zstdHeader.length); -const STATE = Symbol('state'); -const WRITEENTRY = Symbol('writeEntry'); -const READENTRY = Symbol('readEntry'); -const NEXTENTRY = Symbol('nextEntry'); -const PROCESSENTRY = Symbol('processEntry'); -const EX = Symbol('extendedHeader'); -const GEX = Symbol('globalExtendedHeader'); -const META = Symbol('meta'); -const EMITMETA = Symbol('emitMeta'); -const BUFFER = Symbol('buffer'); -const QUEUE = Symbol('queue'); -const ENDED = Symbol('ended'); -const EMITTEDEND = Symbol('emittedEnd'); -const EMIT = Symbol('emit'); -const UNZIP = Symbol('unzip'); -const CONSUMECHUNK = Symbol('consumeChunk'); -const CONSUMECHUNKSUB = Symbol('consumeChunkSub'); -const CONSUMEBODY = Symbol('consumeBody'); -const CONSUMEMETA = Symbol('consumeMeta'); -const CONSUMEHEADER = Symbol('consumeHeader'); -const CONSUMING = Symbol('consuming'); -const BUFFERCONCAT = Symbol('bufferConcat'); -const MAYBEEND = Symbol('maybeEnd'); -const WRITING = Symbol('writing'); -const ABORTED = Symbol('aborted'); -const DONE = Symbol('onDone'); -const SAW_VALID_ENTRY = Symbol('sawValidEntry'); -const SAW_NULL_BLOCK = Symbol('sawNullBlock'); -const SAW_EOF = Symbol('sawEOF'); -const CLOSESTREAM = Symbol('closeStream'); -const MAX_DECOMPRESSION_RATIO = 1000; -const COMPRESSEDBYTESREAD = Symbol('compressedBytesRead'); -const DECOMPRESSEDBYTESREAD = Symbol('decompressedBytesRead'); -const CHECKDECOMPRESSIONRATIO = Symbol('checkDecompressionRatio'); -const noop = () => true; -export class Parser extends EE { - file; - strict; - maxMetaEntrySize; - filter; - brotli; - zstd; - maxDecompressionRatio; - writable = true; - readable = false; - [QUEUE] = []; - [BUFFER]; - [READENTRY]; - [WRITEENTRY]; - [STATE] = 'begin'; - [META] = ''; - [EX]; - [GEX]; - [ENDED] = false; - [UNZIP]; - [ABORTED] = false; - [SAW_VALID_ENTRY]; - [SAW_NULL_BLOCK] = false; - [SAW_EOF] = false; - [WRITING] = false; - [CONSUMING] = false; - [EMITTEDEND] = false; - [COMPRESSEDBYTESREAD] = 0; - [DECOMPRESSEDBYTESREAD] = 0; - constructor(opt = {}) { - super(); - this.file = opt.file || ''; - // these BADARCHIVE errors can't be detected early. listen on DONE. - this.on(DONE, () => { - if (this[STATE] === 'begin' || this[SAW_VALID_ENTRY] === false) { - // either less than 1 block of data, or all entries were invalid. - // Either way, probably not even a tarball. - this.warn('TAR_BAD_ARCHIVE', 'Unrecognized archive format'); - } - }); - if (opt.ondone) { - this.on(DONE, opt.ondone); - } - else { - this.on(DONE, () => { - this.emit('prefinish'); - this.emit('finish'); - this.emit('end'); - }); - } - this.strict = !!opt.strict; - this.maxDecompressionRatio = - typeof opt.maxDecompressionRatio === 'number' ? - opt.maxDecompressionRatio - : MAX_DECOMPRESSION_RATIO; - this.maxMetaEntrySize = opt.maxMetaEntrySize || maxMetaEntrySize; - this.filter = typeof opt.filter === 'function' ? opt.filter : noop; - // Unlike gzip, brotli doesn't have any magic bytes to identify it - // Users need to explicitly tell us they're extracting a brotli file - // Or we infer from the file extension - const isTBR = opt.file && - (opt.file.endsWith('.tar.br') || opt.file.endsWith('.tbr')); - // if it's a tbr file it MIGHT be brotli, but we don't know until - // we look at it and verify it's not a valid tar file. - this.brotli = - !(opt.gzip || opt.zstd) && opt.brotli !== undefined ? opt.brotli - : isTBR ? undefined - : false; - // zstd has magic bytes to identify it, but we also support explicit options - // and file extension detection - const isTZST = opt.file && - (opt.file.endsWith('.tar.zst') || opt.file.endsWith('.tzst')); - this.zstd = - !(opt.gzip || opt.brotli) && opt.zstd !== undefined ? opt.zstd - : isTZST ? true - : undefined; - // have to set this so that streams are ok piping into it - this.on('end', () => this[CLOSESTREAM]()); - if (typeof opt.onwarn === 'function') { - this.on('warn', opt.onwarn); - } - if (typeof opt.onReadEntry === 'function') { - this.on('entry', opt.onReadEntry); - } - } - warn(code, message, data = {}) { - warnMethod(this, code, message, data); - } - [CONSUMEHEADER](chunk, position) { - if (this[SAW_VALID_ENTRY] === undefined) { - this[SAW_VALID_ENTRY] = false; - } - let header; - try { - header = new Header(chunk, position, this[EX], this[GEX]); - } - catch (er) { - return this.warn('TAR_ENTRY_INVALID', er); - } - if (header.nullBlock) { - if (this[SAW_NULL_BLOCK]) { - this[SAW_EOF] = true; - // ending an archive with no entries. pointless, but legal. - if (this[STATE] === 'begin') { - this[STATE] = 'header'; - } - this[EMIT]('eof'); - } - else { - this[SAW_NULL_BLOCK] = true; - this[EMIT]('nullBlock'); - } - } - else { - this[SAW_NULL_BLOCK] = false; - if (!header.cksumValid) { - this.warn('TAR_ENTRY_INVALID', 'checksum failure', { header }); - } - else if (!header.path) { - this.warn('TAR_ENTRY_INVALID', 'path is required', { header }); - } - else { - const type = header.type; - if (/^(Symbolic)?Link$/.test(type) && !header.linkpath) { - this.warn('TAR_ENTRY_INVALID', 'linkpath required', { - header, - }); - } - else if (!/^(Symbolic)?Link$/.test(type) && - !/^(Global)?ExtendedHeader$/.test(type) && - header.linkpath) { - this.warn('TAR_ENTRY_INVALID', 'linkpath forbidden', { - header, - }); - } - else { - const entry = (this[WRITEENTRY] = new ReadEntry(header, this[EX], this[GEX])); - // we do this for meta & ignored entries as well, because they - // are still valid tar, or else we wouldn't know to ignore them - if (!this[SAW_VALID_ENTRY]) { - if (entry.remain) { - // this might be the one! - const onend = () => { - if (!entry.invalid) { - this[SAW_VALID_ENTRY] = true; - } - }; - entry.on('end', onend); - } - else { - this[SAW_VALID_ENTRY] = true; - } - } - if (entry.meta) { - if (entry.size > this.maxMetaEntrySize) { - entry.ignore = true; - this[EMIT]('ignoredEntry', entry); - this[STATE] = 'ignore'; - entry.resume(); - } - else if (entry.size > 0) { - this[META] = ''; - entry.on('data', c => (this[META] += c)); - this[STATE] = 'meta'; - } - } - else { - this[EX] = undefined; - entry.ignore = entry.ignore || !this.filter(entry.path, entry); - if (entry.ignore) { - // probably valid, just not something we care about - this[EMIT]('ignoredEntry', entry); - this[STATE] = entry.remain ? 'ignore' : 'header'; - entry.resume(); - } - else { - if (entry.remain) { - this[STATE] = 'body'; - } - else { - this[STATE] = 'header'; - entry.end(); - } - if (!this[READENTRY]) { - this[QUEUE].push(entry); - this[NEXTENTRY](); - } - else { - this[QUEUE].push(entry); - } - } - } - } - } - } - } - [CLOSESTREAM]() { - queueMicrotask(() => this.emit('close')); - } - [PROCESSENTRY](entry) { - let go = true; - if (!entry) { - this[READENTRY] = undefined; - go = false; - } - else if (Array.isArray(entry)) { - const [ev, ...args] = entry; - this.emit(ev, ...args); - } - else { - this[READENTRY] = entry; - this.emit('entry', entry); - if (!entry.emittedEnd) { - entry.on('end', () => this[NEXTENTRY]()); - go = false; - } - } - return go; - } - [NEXTENTRY]() { - do { } while (this[PROCESSENTRY](this[QUEUE].shift())); - if (this[QUEUE].length === 0) { - // At this point, there's nothing in the queue, but we may have an - // entry which is being consumed (readEntry). - // If we don't, then we definitely can handle more data. - // If we do, and either it's flowing, or it has never had any data - // written to it, then it needs more. - // The only other possibility is that it has returned false from a - // write() call, so we wait for the next drain to continue. - const re = this[READENTRY]; - const drainNow = !re || re.flowing || re.size === re.remain; - if (drainNow) { - if (!this[WRITING]) { - this.emit('drain'); - } - } - else { - re.once('drain', () => this.emit('drain')); - } - } - } - [CONSUMEBODY](chunk, position) { - // write up to but no more than writeEntry.blockRemain - const entry = this[WRITEENTRY]; - /* c8 ignore start */ - if (!entry) { - throw new Error('attempt to consume body without entry??'); - } - const br = entry.blockRemain ?? 0; - /* c8 ignore stop */ - const c = br >= chunk.length && position === 0 ? - chunk - : chunk.subarray(position, position + br); - entry.write(c); - if (!entry.blockRemain) { - this[STATE] = 'header'; - this[WRITEENTRY] = undefined; - entry.end(); - } - return c.length; - } - [CONSUMEMETA](chunk, position) { - const entry = this[WRITEENTRY]; - const ret = this[CONSUMEBODY](chunk, position); - // if we finished, then the entry is reset - if (!this[WRITEENTRY] && entry) { - this[EMITMETA](entry); - } - return ret; - } - [EMIT](ev, data, extra) { - if (this[QUEUE].length === 0 && !this[READENTRY]) { - this.emit(ev, data, extra); - } - else { - this[QUEUE].push([ev, data, extra]); - } - } - [EMITMETA](entry) { - this[EMIT]('meta', this[META]); - switch (entry.type) { - case 'ExtendedHeader': - case 'OldExtendedHeader': - this[EX] = Pax.parse(this[META], this[EX], false); - break; - case 'GlobalExtendedHeader': - this[GEX] = Pax.parse(this[META], this[GEX], true); - break; - case 'NextFileHasLongPath': - case 'OldGnuLongPath': { - const ex = this[EX] ?? Object.create(null); - this[EX] = ex; - ex.path = this[META].replace(/\0.*/, ''); - break; - } - case 'NextFileHasLongLinkpath': { - const ex = this[EX] || Object.create(null); - this[EX] = ex; - ex.linkpath = this[META].replace(/\0.*/, ''); - break; - } - /* c8 ignore start */ - default: - throw new Error('unknown meta: ' + entry.type); - /* c8 ignore stop */ - } - } - abort(error) { - if (this[ABORTED]) { - return; - } - if (this[UNZIP]) { - // fully nerf the decompressor and close its underlying binding - const u = this[UNZIP]; - /* c8 ignore start */ - u.write = () => true; - u.end = () => u; - u.emit = () => false; - /* c8 ignore stop */ - u.destroy?.(); - } - this[ABORTED] = true; - this.emit('abort', error); - // always throws, even in non-strict mode - this.warn('TAR_ABORT', error, { recoverable: false }); - } - [CHECKDECOMPRESSIONRATIO](chunk) { - this[DECOMPRESSEDBYTESREAD] += chunk.length; - const ratio = this[DECOMPRESSEDBYTESREAD] / this[COMPRESSEDBYTESREAD]; - if (ratio > this.maxDecompressionRatio) { - this.abort(new Error(`max decompression ratio exceeded: ${ratio.toFixed(2)} > ${this.maxDecompressionRatio}`)); - return false; - } - return true; - } - write(chunk, encoding, cb) { - if (typeof encoding === 'function') { - cb = encoding; - encoding = undefined; - } - if (typeof chunk === 'string') { - chunk = Buffer.from(chunk, - /* c8 ignore next */ - typeof encoding === 'string' ? encoding : 'utf8'); - } - if (this[ABORTED]) { - /* c8 ignore next */ - cb?.(); - return false; - } - // first write, might be gzipped, zstd, or brotli compressed - const needSniff = this[UNZIP] === undefined || - (this.brotli === undefined && this[UNZIP] === false); - if (needSniff && chunk) { - if (this[BUFFER]) { - chunk = Buffer.concat([this[BUFFER], chunk]); - this[BUFFER] = undefined; - } - if (chunk.length < ZIP_HEADER_LEN) { - this[BUFFER] = chunk; - /* c8 ignore next */ - cb?.(); - return true; - } - // look for gzip header - for (let i = 0; this[UNZIP] === undefined && i < gzipHeader.length; i++) { - if (chunk[i] !== gzipHeader[i]) { - this[UNZIP] = false; - } - } - // look for zstd header if gzip header not found - let isZstd = false; - if (this[UNZIP] === false && this.zstd !== false) { - isZstd = true; - for (let i = 0; i < zstdHeader.length; i++) { - if (chunk[i] !== zstdHeader[i]) { - isZstd = false; - break; - } - } - } - const maybeBrotli = this.brotli === undefined && !isZstd; - if (this[UNZIP] === false && maybeBrotli) { - // read the first header to see if it's a valid tar file. If so, - // we can safely assume that it's not actually brotli, despite the - // .tbr or .tar.br file extension. - // if we ended before getting a full chunk, yes, def brotli - if (chunk.length < 512) { - if (this[ENDED]) { - this.brotli = true; - } - else { - this[BUFFER] = chunk; - /* c8 ignore next */ - cb?.(); - return true; - } - } - else { - // if it's tar, it's pretty reliably not brotli, chances of - // that happening are astronomical. - try { - new Header(chunk.subarray(0, 512)); - this.brotli = false; - } - catch (_) { - this.brotli = true; - } - } - } - if (this[UNZIP] === undefined || - (this[UNZIP] === false && (this.brotli || isZstd))) { - const ended = this[ENDED]; - this[ENDED] = false; - this[UNZIP] = - this[UNZIP] === undefined ? new Unzip({}) - : isZstd ? new ZstdDecompress({}) - : new BrotliDecompress({}); - this[UNZIP].on('data', chunk => { - if (this[CHECKDECOMPRESSIONRATIO](chunk)) { - this[CONSUMECHUNK](chunk); - } - }); - this[UNZIP].on('error', er => { - if (!this[ABORTED]) { - this.abort(er); - } - }); - this[UNZIP].on('end', () => { - this[ENDED] = true; - this[CONSUMECHUNK](); - }); - this[WRITING] = true; - this[COMPRESSEDBYTESREAD] += chunk.length; - const ret = !!this[UNZIP][ended ? 'end' : 'write'](chunk); - this[WRITING] = false; - cb?.(); - return ret; - } - } - this[WRITING] = true; - if (this[UNZIP]) { - this[COMPRESSEDBYTESREAD] += chunk.length; - this[UNZIP].write(chunk); - } - else { - this[CONSUMECHUNK](chunk); - } - this[WRITING] = false; - // return false if there's a queue, or if the current entry isn't flowing - const ret = this[QUEUE].length > 0 ? false - : this[READENTRY] ? this[READENTRY].flowing - : true; - // if we have no queue, then that means a clogged READENTRY - if (!ret && this[QUEUE].length === 0) { - this[READENTRY]?.once('drain', () => this.emit('drain')); - } - /* c8 ignore next */ - cb?.(); - return ret; - } - [BUFFERCONCAT](c) { - if (c && !this[ABORTED]) { - this[BUFFER] = this[BUFFER] ? Buffer.concat([this[BUFFER], c]) : c; - } - } - [MAYBEEND]() { - if (this[ENDED] && - !this[EMITTEDEND] && - !this[ABORTED] && - !this[CONSUMING]) { - this[EMITTEDEND] = true; - const entry = this[WRITEENTRY]; - if (entry?.blockRemain) { - // truncated, likely a damaged file - const have = this[BUFFER] ? this[BUFFER].length : 0; - this.warn('TAR_BAD_ARCHIVE', `Truncated input (needed ${entry.blockRemain} more bytes, only ${have} available)`, { entry }); - if (this[BUFFER]) { - entry.write(this[BUFFER]); - } - entry.end(); - } - this[EMIT](DONE); - } - } - [CONSUMECHUNK](chunk) { - if (this[CONSUMING] && chunk) { - this[BUFFERCONCAT](chunk); - } - else if (!chunk && !this[BUFFER]) { - this[MAYBEEND](); - } - else if (chunk) { - this[CONSUMING] = true; - if (this[BUFFER]) { - this[BUFFERCONCAT](chunk); - const c = this[BUFFER]; - this[BUFFER] = undefined; - this[CONSUMECHUNKSUB](c); - } - else { - this[CONSUMECHUNKSUB](chunk); - } - while (this[BUFFER] && - this[BUFFER]?.length >= 512 && - !this[ABORTED] && - !this[SAW_EOF]) { - const c = this[BUFFER]; - this[BUFFER] = undefined; - this[CONSUMECHUNKSUB](c); - } - this[CONSUMING] = false; - } - if (!this[BUFFER] || this[ENDED]) { - this[MAYBEEND](); - } - } - [CONSUMECHUNKSUB](chunk) { - // we know that we are in CONSUMING mode, so anything written goes into - // the buffer. Advance the position and put any remainder in the buffer. - let position = 0; - const length = chunk.length; - while (position + 512 <= length && !this[ABORTED] && !this[SAW_EOF]) { - switch (this[STATE]) { - case 'begin': - case 'header': - this[CONSUMEHEADER](chunk, position); - position += 512; - break; - case 'ignore': - case 'body': - position += this[CONSUMEBODY](chunk, position); - break; - case 'meta': - position += this[CONSUMEMETA](chunk, position); - break; - /* c8 ignore start */ - default: - throw new Error('invalid state: ' + this[STATE]); - /* c8 ignore stop */ - } - } - if (position < length) { - this[BUFFER] = - this[BUFFER] ? - Buffer.concat([chunk.subarray(position), this[BUFFER]]) - : chunk.subarray(position); - } - } - end(chunk, encoding, cb) { - 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); - } - if (cb) - this.once('finish', cb); - if (!this[ABORTED]) { - if (this[UNZIP]) { - /* c8 ignore start */ - if (chunk) { - this[COMPRESSEDBYTESREAD] += chunk.length; - this[UNZIP].write(chunk); - } - /* c8 ignore stop */ - this[UNZIP].end(); - } - else { - this[ENDED] = true; - if (this.brotli === undefined || this.zstd === undefined) - chunk = chunk || Buffer.alloc(0); - if (chunk) - this.write(chunk); - this[MAYBEEND](); - } - } - return this; - } -} -//# sourceMappingURL=parse.js.map
\ No newline at end of file |