UNPKG

@obsidize/tar-browserify

Version:

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

203 lines (202 loc) 7.54 kB
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 ArchiveEntry { constructor(options = {}) { let { header, headerAttributes, headerByteLength, content, offset, context } = options; if (!header) header = TarHeader.fromAttributes(headerAttributes || {}); 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; } // run this last since toUint8Array() also syncs checksums if (!headerByteLength) headerByteLength = header.toUint8Array().byteLength; this.mHeader = header; this.mHeaderByteLength = headerByteLength; this.mContent = content; this.mOffset = offset; this.mContext = context; } static isArchiveEntry(v) { return !!(v && v instanceof ArchiveEntry); } // ================================================================= // 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. * If you are attempting to read the content of this entry, * do not modify this instance. */ 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 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 sourceOffset() { return this.mOffset; } /** * The size in bytes of the header in the source buffer that this entry was parsed from. * Returns zero by default if this was not parsed by a source buffer. */ get sourceHeaderByteLength() { return this.mHeaderByteLength; } /** * 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 sourceContext() { return this.mContext; } isDirectory() { return this.header.isDirectoryHeader; } isFile() { return this.header.isFileHeader; } /** * 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. */ text() { return TarUtility.decodeString(this.content); } /** * 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. * * Do not use this on entries that have not been parsed from a source buffer, * otherwise it will very likely return garbage data. * * @param buffer - the source to read content from * @param offset - the _relative_ offset of the content to read; * setting this to 42 will start reading at the 42nd byte index within the content block * @param length - the number of bytes to read after the offset */ async readContentFrom(buffer, offset = 0, length = 0) { const fileSize = this.fileSize; const contentStartIndex = this.sourceOffset + this.sourceHeaderByteLength; const contentEndIndex = contentStartIndex + fileSize; const absoluteOffset = contentStartIndex + TarUtility.clamp(offset, 0, fileSize); const bytesRemaining = Math.max(0, contentEndIndex - absoluteOffset); const normalizedLength = length > 0 ? Math.min(length, bytesRemaining) : bytesRemaining; return buffer.read(absoluteOffset, normalizedLength); } /** * @returns This instance serialized as a single slice for a tar buffer */ toUint8Array() { const headerBytes = this.header.toUint8Array(); const contentLength = this.content?.byteLength ?? 0; const outputLength = TarUtility.roundUpSectorOffset(headerBytes.byteLength + contentLength); const result = new Uint8Array(outputLength); result.set(headerBytes, 0); if (contentLength > 0) { 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), }; } }