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