@obsidize/tar-browserify
Version:
Browser-based tar utility for packing and unpacking tar files (stream-capable)
228 lines (227 loc) • 7.82 kB
JavaScript
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);
}
}
}