UNPKG

@obsidize/tar-browserify

Version:

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

228 lines (227 loc) 7.82 kB
import { TarUtility } from '../common/tar-utility'; import { PaxHeader } from './pax/pax-header'; import { UstarHeader } from './ustar/ustar-header'; import { UstarHeaderField } from './ustar/ustar-header-field'; import { UstarHeaderLinkIndicatorType } from './ustar/ustar-header-link-indicator-type'; /** * Facade over a backing Uint8Array buffer to consume/edit header data * at a specific location in the buffer. * * Does not perform any mutations or reads on creation, and * lazy loads/sets data via getters and setters. */ export class TarHeader { constructor(options) { const { ustar, pax, preamble, isPaxGlobal } = options; this.ustar = ustar; this.pax = pax; this.mPreamble = preamble; this.mIsGlobal = !!isPaxGlobal; this.trySyncPaxHeader(); } static isTarHeader(value) { return !!(value && value instanceof TarHeader); } /** * @returns A new `TarHeader` instance based on the given attributes (if they are a POJO). * Note that if the given value is already a TarHeader instance, this will return it as-is. */ static fromAttributes(attributes) { if (TarHeader.isTarHeader(attributes)) { return attributes; } const ustar = new UstarHeader(attributes); const paxRequiredAttributes = TarHeader.collectPaxRequiredAttributes(attributes); let pax; if (paxRequiredAttributes) { // The path property is the only reason we fall back to PAX as of now. // This block may need to be wrapped in a check for the path property if other attributes are added later on. const [directoryName, fileName] = TarHeader.splitBaseFileName(paxRequiredAttributes.path); ustar.fileName = fileName; ustar.fileNamePrefix = directoryName; pax = PaxHeader.fromAttributes(paxRequiredAttributes); } return new TarHeader({ ustar, pax }); } /** * Short-hand for constructing a new `TarHeader` and immediately calling `toUint8Array()` on it */ static serializeAttributes(attributes) { if (TarHeader.isTarHeader(attributes)) { return attributes.toUint8Array(); } return TarHeader.fromAttributes(attributes).toUint8Array(); } static collectPaxRequiredAttributes(attributes) { if (TarUtility.isObject(attributes)) { let collected = {}; if (attributes.fileName && attributes.fileName.length > UstarHeaderField.fileName.size) { collected.path = attributes.fileName; } if (Object.keys(collected).length > 0) { return collected; } } return null; } static splitBaseFileName(fileName) { let offset = fileName.lastIndexOf('/'); if (offset >= 0) { return [fileName.substring(0, offset), fileName.substring(offset + 1)]; } offset = fileName.lastIndexOf('\\'); if (offset >= 0) { return [fileName.substring(0, offset), fileName.substring(offset + 1)]; } return ['', fileName]; } get preamble() { return this.mPreamble; } get fileName() { return this.pax?.path || this.ustar.fileName; } get fileMode() { return this.ustar.fileMode; } get ownerUserId() { return this.pax?.userId || this.ustar.ownerUserId; } get groupUserId() { return this.pax?.groupId || this.ustar.groupUserId; } get fileSize() { return this.pax?.size || this.ustar.fileSize; } get lastModified() { return this.pax?.lastModified || this.ustar.lastModified; } get headerChecksum() { return this.ustar.headerChecksum; } get linkedFileName() { return this.pax?.linkPath || this.ustar.linkedFileName; } get typeFlag() { return this.ustar.typeFlag; } get ustarIndicator() { return this.ustar.ustarIndicator; } get ustarVersion() { return this.ustar.ustarVersion; } get ownerUserName() { return this.pax?.userName || this.ustar.ownerUserName; } get ownerGroupName() { return this.pax?.groupName || this.ustar.ownerGroupName; } get deviceMajorNumber() { return this.ustar.deviceMajorNumber; } get deviceMinorNumber() { return this.ustar.deviceMinorNumber; } get fileNamePrefix() { return this.ustar.fileNamePrefix; } get isPaxHeader() { return this.isLocalPaxHeader || this.isGlobalPaxHeader; } get isGlobalPaxHeader() { return this.isGlobalPaxPreHeader || this.isGlobalPaxPostHeader; } get isLocalPaxHeader() { return this.isLocalPaxPreHeader || this.isLocalPaxPostHeader; } get isGlobalPaxPreHeader() { return this.ustar.isGlobalPaxHeader; } get isLocalPaxPreHeader() { return this.ustar.isLocalPaxHeader; } get isGlobalPaxPostHeader() { return this.preamble?.isGlobalPaxHeader ?? false; } get isLocalPaxPostHeader() { return this.preamble?.isLocalPaxHeader ?? false; } get isFileHeader() { return this.ustar.isFileHeader; } get isDirectoryHeader() { return this.ustar.isDirectoryHeader; } /** * Removes any unknown or un-standardized keys from * the PAX portion of this header (if one exists). * * See also `PaxHeader.clean()`. * * @returns `this` for operation chaining */ clean() { this.pax?.clean(); return this; } /** * Ensures that things such as checksum values are * synchronized with the current underlying header states. * * @returns `this` for operation chaining */ normalize() { this.trySyncPaxHeader(); return this; } /** * @returns A snapshot of the underlying buffer for this header */ toUint8Array() { this.normalize(); const isPax = !!(this.isPaxHeader && this.pax && this.preamble); if (!isPax) { return this.ustar.toUint8Array(); } const preambleBytes = this.preamble.toUint8Array(); const paxBytes = this.pax.toUint8ArrayPadded(); const ownBytes = this.ustar.toUint8Array(); const totalSize = preambleBytes.byteLength + paxBytes.byteLength + ownBytes.byteLength; const result = new Uint8Array(totalSize); let offset = 0; result.set(preambleBytes, offset); offset += preambleBytes.byteLength; result.set(paxBytes, offset); offset += paxBytes.byteLength; result.set(ownBytes, offset); return result; } toJSON() { const { pax, preamble, ustar } = this; return { preamble, pax, ustar }; } trySyncPaxHeader() { if (!this.pax) { return; } const fileName = this.fileName; const fileSize = this.pax.toUint8Array().byteLength; const lastModified = this.pax.lastModified; const typeFlag = this.mIsGlobal ? UstarHeaderLinkIndicatorType.GLOBAL_EXTENDED_HEADER : UstarHeaderLinkIndicatorType.LOCAL_EXTENDED_HEADER; const preambleAttrs = { fileName, typeFlag, lastModified, fileSize, }; if (this.mPreamble) { this.mPreamble.update(preambleAttrs); } else { this.mPreamble = new UstarHeader(preambleAttrs); } } }