tar-iterator
Version:
Extract contents from tar archive type using an iterator API using streams or paths. Use stream interface and pipe transforms to add decompression algorithms
272 lines • 10.6 kB
JavaScript
/**
* GNU Sparse File Support
*
* Handles parsing of GNU sparse file headers and stream reconstruction.
*
* GNU sparse format stores:
* 1. A sparse map in the header (offset 386) with up to 4 entries
* 2. Extended sparse headers (512-byte blocks) if more entries needed
* 3. The actual data chunks (only non-zero portions of the file)
*
* Each sparse entry contains:
* - offset: position in the virtual file where this data chunk belongs
* - numbytes: size of this data chunk
*
* Node 0.8 compatible - uses only basic Buffer operations.
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: Object.getOwnPropertyDescriptor(all, name).get
});
}
_export(exports, {
get SparseStream () {
return SparseStream;
},
get parseGnuSparseExtended () {
return parseGnuSparseExtended;
},
get parseGnuSparseHeader () {
return parseGnuSparseHeader;
},
get sparseDataSize () {
return sparseDataSize;
}
});
var _extractbaseiterator = require("extract-base-iterator");
var _constantsts = require("./constants.js");
var _EntryStreamts = /*#__PURE__*/ _interop_require_default(require("./EntryStream.js"));
var _headersts = require("./headers.js");
function _assert_this_initialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _call_super(_this, derived, args) {
derived = _get_prototype_of(derived);
return _possible_constructor_return(_this, _is_native_reflect_construct() ? Reflect.construct(derived, args || [], _get_prototype_of(_this).constructor) : derived.apply(_this, args));
}
function _class_call_check(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function get(target, property, receiver) {
var base = _super_prop_base(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver || target);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}
function _get_prototype_of(o) {
_get_prototype_of = Object.setPrototypeOf ? Object.getPrototypeOf : function getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _get_prototype_of(o);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _set_prototype_of(subClass, superClass);
}
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _possible_constructor_return(self, call) {
if (call && (_type_of(call) === "object" || typeof call === "function")) {
return call;
}
return _assert_this_initialized(self);
}
function _set_prototype_of(o, p) {
_set_prototype_of = Object.setPrototypeOf || function setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _set_prototype_of(o, p);
}
function _super_prop_base(object, property) {
while(!Object.prototype.hasOwnProperty.call(object, property)){
object = _get_prototype_of(object);
if (object === null) break;
}
return object;
}
function _type_of(obj) {
"@swc/helpers - typeof";
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
}
function _is_native_reflect_construct() {
try {
var result = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
} catch (_) {}
return (_is_native_reflect_construct = function() {
return !!result;
})();
}
// Reusable zero buffer for sparse hole emission (64KB)
var ZERO_BUFFER_SIZE = 65536;
var zeroBuffer = null;
function getZeroBuffer() {
if (!zeroBuffer) {
// allocBuffer already zero-fills on Node 4.5+, manual fill for Node 0.8
zeroBuffer = (0, _extractbaseiterator.allocBuffer)(ZERO_BUFFER_SIZE);
}
return zeroBuffer;
}
/**
* Parse sparse entries from a buffer starting at given offset
*
* @param buf - Buffer containing sparse entries
* @param startOffset - Offset in buffer where entries begin
* @param maxEntries - Maximum number of entries to read
* @returns Array of valid sparse entries (stops at first zero entry)
*/ function parseSparseEntries(buf, startOffset, maxEntries) {
var entries = [];
for(var i = 0; i < maxEntries; i++){
var entryOffset = startOffset + i * _constantsts.SPARSE_ENTRY_SIZE;
var offset = (0, _headersts.decodeOct)(buf, entryOffset, _constantsts.SPARSE_ENTRY_OFFSET_SIZE);
var numbytes = (0, _headersts.decodeOct)(buf, entryOffset + _constantsts.SPARSE_ENTRY_OFFSET_SIZE, _constantsts.SPARSE_ENTRY_NUMBYTES_SIZE);
// Stop at first zero entry (end of sparse map)
if (offset === 0 && numbytes === 0) {
break;
}
entries.push({
offset: offset,
numbytes: numbytes
});
}
return entries;
}
function parseGnuSparseHeader(headerBuf) {
// Parse sparse entries from header (up to 4)
var entries = parseSparseEntries(headerBuf, _constantsts.SPARSE_OFFSET, _constantsts.SPARSE_ENTRIES_IN_HEADER);
// Parse isextended flag
var isExtended = headerBuf[_constantsts.SPARSE_ISEXTENDED_OFFSET] !== 0;
// Parse real file size
var realSize = (0, _headersts.decodeOct)(headerBuf, _constantsts.SPARSE_REALSIZE_OFFSET, _constantsts.SPARSE_REALSIZE_SIZE);
return {
realSize: realSize,
entries: entries,
isExtended: isExtended
};
}
function parseGnuSparseExtended(extBuf) {
// Parse sparse entries from extended block (up to 21)
var entries = parseSparseEntries(extBuf, 0, _constantsts.SPARSE_EXTENDED_ENTRIES);
// Parse isextended flag
var isExtended = extBuf[_constantsts.SPARSE_EXTENDED_ISEXTENDED_OFFSET] !== 0;
return {
entries: entries,
isExtended: isExtended
};
}
function sparseDataSize(entries) {
var total = 0;
for(var i = 0; i < entries.length; i++){
total += entries[i].numbytes;
}
return total;
}
var SparseStream = /*#__PURE__*/ function(EntryStream) {
"use strict";
_inherits(SparseStream, EntryStream);
function SparseStream(entries, realSize) {
_class_call_check(this, SparseStream);
var _this;
_this = _call_super(this, SparseStream);
_this.entries = entries;
_this.realSize = realSize;
_this.currentEntry = 0;
_this.virtualPosition = 0;
_this.entryBytesRemaining = entries.length > 0 ? entries[0].numbytes : 0;
return _this;
}
var _proto = SparseStream.prototype;
/**
* Push data from the tar archive (actual sparse data chunk)
* Overrides EntryStream.push() to reconstruct sparse file with holes.
*/ _proto.push = function push(data) {
// Allow null through to signal end
if (data === null) return _get(_get_prototype_of(SparseStream.prototype), "push", this).call(this, null);
if (this.ended) return false;
var dataOffset = 0;
var result = true;
while(dataOffset < data.length && this.currentEntry < this.entries.length){
var entry = this.entries[this.currentEntry];
// First, emit zeros for any hole before current entry
if (this.virtualPosition < entry.offset) {
var holeSize = entry.offset - this.virtualPosition;
this._emitZeros(holeSize);
this.virtualPosition = entry.offset;
}
// Now emit actual data for this entry
var toEmit = Math.min(this.entryBytesRemaining, data.length - dataOffset);
if (toEmit > 0) {
var chunk = data.slice(dataOffset, dataOffset + toEmit);
result = _get(_get_prototype_of(SparseStream.prototype), "push", this).call(this, chunk);
dataOffset += toEmit;
this.virtualPosition += toEmit;
this.entryBytesRemaining -= toEmit;
}
// Move to next entry if current is exhausted
if (this.entryBytesRemaining <= 0) {
this.currentEntry++;
if (this.currentEntry < this.entries.length) {
this.entryBytesRemaining = this.entries[this.currentEntry].numbytes;
}
}
}
return result;
};
/**
* End the stream - emit any trailing zeros
* Overrides EntryStream.end() to emit trailing zeros first.
*/ _proto.end = function end() {
if (this.ended) return;
// Emit remaining zeros to reach real file size
if (this.virtualPosition < this.realSize) {
this._emitZeros(this.realSize - this.virtualPosition);
this.virtualPosition = this.realSize;
}
_get(_get_prototype_of(SparseStream.prototype), "end", this).call(this);
};
/**
* Emit zeros for a hole, reusing the shared zero buffer
*/ _proto._emitZeros = function _emitZeros(size) {
var zeros = getZeroBuffer();
var remaining = size;
while(remaining > 0){
var toEmit = Math.min(remaining, ZERO_BUFFER_SIZE);
// Slice from the reusable buffer to emit exact size needed
_get(_get_prototype_of(SparseStream.prototype), "push", this).call(this, zeros.slice(0, toEmit));
remaining -= toEmit;
}
};
return SparseStream;
}(_EntryStreamts.default);
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }