UNPKG

@obsidize/tar-browserify

Version:

Browser-based tar utility for packing and unpacking tar files (stream-capable)

252 lines (251 loc) 8.86 kB
import { __awaiter } from "tslib"; import { TarUtility } from '../common/tar-utility'; import { TarHeader } from '../header/tar-header'; /** * Container for metadata and content of a tarball entry. * * Here, we consider an "entry" to be a tuple of: * 1. The parsed USTAR header sector content (AKA TarHeader) * 2. The aggregate of the proceeding file content sectors, based on the header's file size attribute */ export class TarEntry { constructor(options = {}) { this.initialize(options); } static isTarEntry(v) { return !!(v && v instanceof TarEntry); } /** * Convenience parser * @param attrs - partial header data POJO * @param content - content of the entry (if it is a file) */ static from(attrs, content = null) { return new TarEntry({ header: TarHeader.from(attrs), content }); } initialize(options) { let { header, content, offset, context } = options; if (!header) header = TarHeader.seeded(); if (!content) content = null; if (!offset) offset = 0; if (!context) context = null; const contentLength = TarUtility.sizeofUint8Array(content); // The fileSize field metadata must always be in sync between the content and the header if (!header.pax && header.fileSize !== contentLength && contentLength > 0) { header.ustar.fileSize = contentLength; header.ustar.updateChecksum(); } this.mHeader = header; this.mContent = content; this.mOffset = offset; this.mContext = context; return this; } // ================================================================= // TarHeader Interface Fields // ================================================================= get fileName() { return this.header.fileName; } get fileSize() { return this.header.fileSize; } get fileMode() { return this.header.fileMode; } get ownerUserId() { return this.header.ownerUserId; } get groupUserId() { return this.header.groupUserId; } get lastModified() { return this.header.lastModified; } get headerChecksum() { return this.header.headerChecksum; } get linkedFileName() { return this.header.linkedFileName; } get typeFlag() { return this.header.typeFlag; } get ustarIndicator() { return this.header.ustarIndicator; } get ustarVersion() { return this.header.ustarVersion; } get ownerUserName() { return this.header.ownerUserName; } get ownerGroupName() { return this.header.ownerGroupName; } get deviceMajorNumber() { return this.header.deviceMajorNumber; } get deviceMinorNumber() { return this.header.deviceMinorNumber; } get fileNamePrefix() { return this.header.fileNamePrefix; } // ================================================================= // Introspection Fields // ================================================================= /** * The header metadata parsed out for this entry. * See TarHeaderFieldDefinition for details. */ get header() { return this.mHeader; } /** * The file content for this entry. * This may be null for entries loaded asynchronously, or * for non-file entries like directories. */ get content() { return this.mContent; } /** * The context (if any) from which this entry was parsed. * The context will include global data about things such as * the origin of the archive and global pax headers. */ get context() { return this.mContext; } /** * The starting absolute index (inclusive) in the source buffer that this entry was parsed from. * Returns zero by default if this was not parsed by a source buffer. */ get bufferStartIndex() { return this.mOffset; } /** * The ending absolute index (exclusive) in the source buffer that this entry was parsed from. * Returns sectorByteLength by default if this was not parsed by a source buffer. */ get bufferEndIndex() { return this.bufferStartIndex + this.sectorByteLength; } /** * The total exact byte length of this entry, including the header. */ get byteLength() { return this.header.byteLength + this.fileSize; } /** * The total byte length of this entry, including the header, * which is a multiple of the standard tar sector size. */ get sectorByteLength() { return TarUtility.roundUpSectorOffset(this.byteLength); } /** * The starting index (inclusive) of the content of this entry. * Note that this will always be the first index of the header, regardless of * whether or not this is a file. */ get contentStartIndex() { return this.header.byteLength + this.bufferStartIndex; } /** * The ending index (exclusive) of the content of this entry. * If this entry is not a file, or the file is empty, this will be * the same as the content starting index. */ get contentEndIndex() { return this.contentStartIndex + this.fileSize; } /** * Convenience for decoding the current content buffer as a string. * Note that if the content was not loaded for whatever reason, this * will return an empty string. * @returns The decoded string data from the currently assigned content, * or an empty string if there is no content assigned. */ getContentAsText() { return TarUtility.decodeString(this.content); } isDirectory() { return this.header.isDirectoryHeader; } isFile() { return this.header.isFileHeader; } /** * Only necessary if this entry was extracted from an async buffer, since the entry * does not hold the content of async buffers by default. * * If the entry was extracted synchronously, its content will be available via the "content" property. */ readContentFrom(buffer, offset = 0, length = 0) { return __awaiter(this, void 0, void 0, function* () { const { contentStartIndex, contentEndIndex, fileSize } = this; const normalizedOffset = TarUtility.clamp(offset, 0, fileSize) + contentStartIndex; const bytesRemaining = Math.max(0, contentEndIndex - normalizedOffset); const normalizedLength = length > 0 ? Math.min(length, bytesRemaining) : bytesRemaining; return buffer.read(normalizedOffset, normalizedLength); }); } /** * Writes the header and content of this entry to the given output * @param output - the buffer to be written to * @param offset - the offset in the buffer to start writing entry data * @returns true if this entry was successfully written to the output */ writeTo(output, offset) { if (!TarUtility.isUint8Array(output) || output.byteLength < (offset + this.sectorByteLength)) { return false; } const headerBytes = this.header.toUint8Array(); output.set(headerBytes, offset); offset += headerBytes.byteLength; if (this.content) { output.set(this.content, offset); } return true; } /** * @returns This instance serialized as a single slice for a tar buffer */ toUint8Array() { const headerBytes = this.header.toUint8Array(); const result = new Uint8Array(this.sectorByteLength); result.set(headerBytes, 0); if (this.content) { result.set(this.content, headerBytes.byteLength); } return result; } /** * Overridden to prevent circular reference errors / huge memory spikes that would * include the underlying content by default. */ toJSON() { const { header, fileName: name, fileSize: size, content } = this; const isFile = this.isFile(); const isDirectory = this.isDirectory(); const type = isFile ? 'file' : isDirectory ? 'directory' : 'complex'; const contentType = content ? ('Uint8Array[' + content.byteLength + ']') : 'null'; return { name, size, type, header, contentType, content: TarUtility.getDebugHexString(content) }; } }