@obsidize/tar-browserify
Version:
Browser-based tar utility for packing and unpacking tar files (stream-capable)
267 lines (266 loc) • 9.71 kB
JavaScript
import { Constants } from '../../common/constants';
import { TarUtility } from '../../common/tar-utility';
import { TarHeaderUtility } from '../tar-header-utility';
import { UstarHeaderField } from './ustar-header-field';
import { UstarHeaderLinkIndicatorType } from './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 UstarHeader {
constructor(bytes = new Uint8Array(Constants.HEADER_SIZE), offset = 0) {
this.bytes = bytes;
this.offset = offset;
}
/**
* @returns A copy of the defaults used by all headers
*/
static defaultValues() {
return {
fileName: '',
fileMode: Constants.FILE_MODE_DEFAULT,
groupUserId: 0,
ownerUserId: 0,
fileSize: 0,
lastModified: TarUtility.getUstarTimestamp(),
headerChecksum: 0,
linkedFileName: '',
typeFlag: UstarHeaderLinkIndicatorType.NORMAL_FILE,
ustarIndicator: Constants.USTAR_INDICATOR_VALUE,
ustarVersion: Constants.USTAR_VERSION_VALUE,
ownerUserName: '',
ownerGroupName: '',
deviceMajorNumber: '00',
deviceMinorNumber: '00',
fileNamePrefix: ''
};
}
static isUstarHeader(value) {
return !!(value && (value instanceof UstarHeader));
}
/**
* @returns A new `UstarHeader` instance based on the given attributes (if they are a POJO).
* Note that if the given value is already a UstarHeader instance, this will return it as-is.
*/
static from(attrs) {
if (UstarHeader.isUstarHeader(attrs)) {
return attrs;
}
return new UstarHeader().initialize(attrs);
}
/**
* Short-hand for constructing a new `UstarHeader` and immediately calling `toUint8Array()` on it
*/
static serialize(attrs) {
if (UstarHeader.isUstarHeader(attrs)) {
return attrs.toUint8Array();
}
return UstarHeader.from(attrs).toUint8Array();
}
/**
* @returns A new `UstarHeader` instance populated with the content returned by `defaultValues()`
*/
static seeded() {
return UstarHeader.from({});
}
get byteLength() {
return this.bytes.byteLength;
}
get fileName() {
return UstarHeaderField.fileName.readFrom(this.bytes, this.offset);
}
set fileName(value) {
UstarHeaderField.fileName.writeTo(this.bytes, this.offset, value);
}
get fileMode() {
return UstarHeaderField.fileMode.readFrom(this.bytes, this.offset);
}
set fileMode(value) {
UstarHeaderField.fileMode.writeTo(this.bytes, this.offset, value);
}
get ownerUserId() {
return UstarHeaderField.ownerUserId.readFrom(this.bytes, this.offset);
}
set ownerUserId(value) {
UstarHeaderField.ownerUserId.writeTo(this.bytes, this.offset, value);
}
get groupUserId() {
return UstarHeaderField.groupUserId.readFrom(this.bytes, this.offset);
}
set groupUserId(value) {
UstarHeaderField.groupUserId.writeTo(this.bytes, this.offset, value);
}
get fileSize() {
return UstarHeaderField.fileSize.readFrom(this.bytes, this.offset);
}
set fileSize(value) {
UstarHeaderField.fileSize.writeTo(this.bytes, this.offset, value);
}
get lastModified() {
return UstarHeaderField.lastModified.readFrom(this.bytes, this.offset);
}
set lastModified(value) {
UstarHeaderField.lastModified.writeTo(this.bytes, this.offset, value);
}
get headerChecksum() {
return UstarHeaderField.headerChecksum.readFrom(this.bytes, this.offset);
}
set headerChecksum(value) {
UstarHeaderField.headerChecksum.writeTo(this.bytes, this.offset, value);
}
get linkedFileName() {
return UstarHeaderField.linkedFileName.readFrom(this.bytes, this.offset);
}
set linkedFileName(value) {
UstarHeaderField.linkedFileName.writeTo(this.bytes, this.offset, value);
}
get typeFlag() {
return UstarHeaderField.typeFlag.readFrom(this.bytes, this.offset)
|| UstarHeaderLinkIndicatorType.UNKNOWN;
}
set typeFlag(value) {
UstarHeaderField.typeFlag.writeTo(this.bytes, this.offset, value);
}
get ustarIndicator() {
return UstarHeaderField.ustarIndicator.readFrom(this.bytes, this.offset);
}
get ustarVersion() {
return UstarHeaderField.ustarVersion.readFrom(this.bytes, this.offset);
}
set ustarVersion(value) {
UstarHeaderField.ustarVersion.writeTo(this.bytes, this.offset, value);
}
get ownerUserName() {
return UstarHeaderField.ownerUserName.readFrom(this.bytes, this.offset);
}
set ownerUserName(value) {
UstarHeaderField.ownerUserName.writeTo(this.bytes, this.offset, value);
}
get ownerGroupName() {
return UstarHeaderField.ownerGroupName.readFrom(this.bytes, this.offset);
}
set ownerGroupName(value) {
UstarHeaderField.ownerGroupName.writeTo(this.bytes, this.offset, value);
}
get deviceMajorNumber() {
return UstarHeaderField.deviceMajorNumber.readFrom(this.bytes, this.offset);
}
set deviceMajorNumber(value) {
UstarHeaderField.deviceMajorNumber.writeTo(this.bytes, this.offset, value);
}
get deviceMinorNumber() {
return UstarHeaderField.deviceMinorNumber.readFrom(this.bytes, this.offset);
}
set deviceMinorNumber(value) {
UstarHeaderField.deviceMinorNumber.writeTo(this.bytes, this.offset, value);
}
get fileNamePrefix() {
return UstarHeaderField.fileNamePrefix.readFrom(this.bytes, this.offset);
}
set fileNamePrefix(value) {
UstarHeaderField.fileNamePrefix.writeTo(this.bytes, this.offset, value);
}
get isPaxHeader() {
return this.isLocalPaxHeader || this.isGlobalPaxHeader;
}
get isGlobalPaxHeader() {
return this.typeFlag === UstarHeaderLinkIndicatorType.GLOBAL_EXTENDED_HEADER;
}
get isLocalPaxHeader() {
return this.typeFlag === UstarHeaderLinkIndicatorType.LOCAL_EXTENDED_HEADER;
}
get isFileHeader() {
return TarHeaderUtility.isTarHeaderLinkIndicatorTypeFile(this.typeFlag);
}
get isDirectoryHeader() {
return TarHeaderUtility.isTarHeaderLinkIndicatorTypeDirectory(this.typeFlag);
}
/**
* @returns A snapshot of the underlying buffer for this header
*/
toUint8Array() {
this.updateChecksum();
return this.bytes.slice(this.offset, this.offset + Constants.HEADER_SIZE);
}
toJSON() {
const attributes = this.toAttributes();
const { bytes, offset } = this;
const buffer = {
byteLength: bytes.byteLength,
content: TarUtility.getDebugHexString(bytes)
};
return {
offset,
attributes,
buffer
};
}
toAttributes() {
const { fileName, fileMode, groupUserId, ownerUserId, fileSize, lastModified, headerChecksum, linkedFileName, typeFlag, ustarIndicator, ustarVersion, ownerUserName, ownerGroupName, deviceMajorNumber, deviceMinorNumber, fileNamePrefix } = this;
return {
fileName,
fileMode,
groupUserId,
ownerUserId,
fileSize,
lastModified,
headerChecksum,
linkedFileName,
typeFlag,
ustarIndicator,
ustarVersion,
ownerUserName,
ownerGroupName,
deviceMajorNumber,
deviceMinorNumber,
fileNamePrefix
};
}
/**
* Override all values in the header.
* Any values not provided in `attrs` will be filled in with default values.
* @returns `this` for operation chaining
*/
initialize(attrs = {}) {
const completeAttrs = Object.assign(UstarHeader.defaultValues(), (attrs || {}));
this.update(completeAttrs);
return this;
}
/**
* Ensures the state of the header is synced after changes have been made.
* @returns `this` for operation chaining
*/
updateChecksum() {
let checksum = TarHeaderUtility.CHECKSUM_SEED;
for (const field of TarHeaderUtility.CHECKSUM_FIELDS) {
checksum += field.calculateChecksum(this.bytes, this.offset);
}
this.headerChecksum = checksum;
return this;
}
/**
* Mechanism to batch-update properties.
* Automatically normalizes the header if any changes were made.
* @returns `this` for operation chaining
*/
update(attrs) {
if (!attrs) {
return this;
}
let didModifyAnyField = false;
for (const field of TarHeaderUtility.ALL_FIELDS) {
const value = attrs[field.name];
if (TarUtility.isDefined(value)) {
const modified = field.writeTo(this.bytes, this.offset, value);
didModifyAnyField = didModifyAnyField || modified;
}
}
if (didModifyAnyField) {
this.updateChecksum();
}
return this;
}
}