200 lines
6.0 KiB
JavaScript
200 lines
6.0 KiB
JavaScript
const TAR_TYPE_FILE = 0;
|
|
const TAR_TYPE_DIR = 5;
|
|
function parseTar(data, opts) {
|
|
const buffer = data.buffer || data;
|
|
const files = [];
|
|
let offset = 0;
|
|
while (offset < buffer.byteLength - 512) {
|
|
const name = _readString(buffer, offset, 100);
|
|
if (name.length === 0) {
|
|
break;
|
|
}
|
|
const mode = _readString(buffer, offset + 100, 8).trim();
|
|
const uid = Number.parseInt(_readString(buffer, offset + 108, 8));
|
|
const gid = Number.parseInt(_readString(buffer, offset + 116, 8));
|
|
const size = _readNumber(buffer, offset + 124, 12);
|
|
const seek = 512 + 512 * Math.trunc(size / 512) + (size % 512 ? 512 : 0);
|
|
const mtime = _readNumber(buffer, offset + 136, 12);
|
|
const _type = _readNumber(buffer, offset + 156, 1);
|
|
const type = _type === TAR_TYPE_FILE ? "file" : _type === TAR_TYPE_DIR ? "directory" : _type;
|
|
const user = _readString(buffer, offset + 265, 32);
|
|
const group = _readString(buffer, offset + 297, 32);
|
|
const meta = {
|
|
name,
|
|
type,
|
|
size,
|
|
attrs: {
|
|
mode,
|
|
uid,
|
|
gid,
|
|
mtime,
|
|
user,
|
|
group
|
|
}
|
|
};
|
|
if (opts?.filter && !opts.filter(meta)) {
|
|
offset += seek;
|
|
continue;
|
|
}
|
|
if (opts?.metaOnly) {
|
|
files.push(meta);
|
|
offset += seek;
|
|
continue;
|
|
}
|
|
const data2 = _type === TAR_TYPE_DIR ? undefined : new Uint8Array(buffer, offset + 512, size);
|
|
files.push({
|
|
...meta,
|
|
data: data2,
|
|
get text() {
|
|
return new TextDecoder().decode(this.data);
|
|
}
|
|
});
|
|
offset += seek;
|
|
}
|
|
return files;
|
|
}
|
|
async function parseTarGzip(data, opts = {}) {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new Uint8Array(data));
|
|
controller.close();
|
|
}
|
|
}).pipeThrough(new DecompressionStream(opts.compression ?? "gzip"));
|
|
const decompressedData = await new Response(stream).arrayBuffer();
|
|
return parseTar(decompressedData, opts);
|
|
}
|
|
function _readString(buffer, offset, size) {
|
|
const view = new Uint8Array(buffer, offset, size);
|
|
const i = view.indexOf(0);
|
|
const td = new TextDecoder();
|
|
return td.decode(i === -1 ? view : view.slice(0, i));
|
|
}
|
|
function _readNumber(buffer, offset, size) {
|
|
const view = new Uint8Array(buffer, offset, size);
|
|
let str = "";
|
|
for (let i = 0; i < size; i++) {
|
|
str += String.fromCodePoint(view[i]);
|
|
}
|
|
return Number.parseInt(str, 8);
|
|
}
|
|
|
|
function createTar(files, opts = {}) {
|
|
const _files = files.map((file) => {
|
|
const data = _normalizeData(file.data);
|
|
return {
|
|
...file,
|
|
data,
|
|
size: data?.length || 0
|
|
};
|
|
});
|
|
let tarDataSize = 0;
|
|
for (let i = 0; i < files.length; i++) {
|
|
const size = _files[i].data?.length ?? 0;
|
|
tarDataSize += 512 + 512 * Math.trunc(size / 512);
|
|
if (size % 512) {
|
|
tarDataSize += 512;
|
|
}
|
|
}
|
|
let bufSize = 10240 * Math.trunc(tarDataSize / 10240);
|
|
if (tarDataSize % 10240) {
|
|
bufSize += 10240;
|
|
}
|
|
const buffer = new ArrayBuffer(bufSize);
|
|
let offset = 0;
|
|
for (const file of _files) {
|
|
const isDir = !file.data;
|
|
_writeString(buffer, file.name, offset, 100);
|
|
const mode = file.attrs?.mode ?? opts.attrs?.mode ?? (isDir ? "775" : "664");
|
|
_writeString(buffer, _leftPad(mode, 7), offset + 100, 8);
|
|
const uid = file.attrs?.uid ?? opts.attrs?.uid ?? 1e3;
|
|
_writeString(buffer, _leftPad(uid.toString(8), 7), offset + 108, 8);
|
|
const gid = file.attrs?.gid ?? opts.attrs?.gid ?? 1e3;
|
|
_writeString(buffer, _leftPad(gid.toString(8), 7), offset + 116, 8);
|
|
_writeString(buffer, _leftPad(file.size.toString(8), 11), offset + 124, 12);
|
|
const mtime = file.attrs?.mtime ?? opts.attrs?.mtime ?? Date.now();
|
|
_writeString(
|
|
buffer,
|
|
_leftPad(Math.trunc(mtime / 1e3).toString(8), 11),
|
|
offset + 136,
|
|
12
|
|
);
|
|
const type = isDir ? "5" : "0";
|
|
_writeString(buffer, type, offset + 156, 1);
|
|
_writeString(
|
|
buffer,
|
|
"ustar",
|
|
offset + 257,
|
|
6
|
|
/* magic string */
|
|
);
|
|
_writeString(
|
|
buffer,
|
|
"00",
|
|
offset + 263,
|
|
2
|
|
/* magic version */
|
|
);
|
|
const user = file.attrs?.user ?? opts.attrs?.user ?? "";
|
|
_writeString(buffer, user, offset + 265, 32);
|
|
const group = file.attrs?.group ?? opts.attrs?.group ?? "";
|
|
_writeString(buffer, group, offset + 297, 32);
|
|
_writeString(buffer, " ", offset + 148, 8);
|
|
const header = new Uint8Array(buffer, offset, 512);
|
|
let chksum = 0;
|
|
for (let i = 0; i < 512; i++) {
|
|
chksum += header[i];
|
|
}
|
|
_writeString(buffer, chksum.toString(8), offset + 148, 8);
|
|
if (!isDir) {
|
|
const destArray = new Uint8Array(buffer, offset + 512, file.size);
|
|
for (let byteIdx = 0; byteIdx < file.size; byteIdx++) {
|
|
destArray[byteIdx] = file.data[byteIdx];
|
|
}
|
|
offset += 512 * Math.trunc(file.size / 512);
|
|
if (file.size % 512) {
|
|
offset += 512;
|
|
}
|
|
}
|
|
offset += 512;
|
|
}
|
|
return new Uint8Array(buffer);
|
|
}
|
|
function createTarGzipStream(files, opts = {}) {
|
|
const buffer = createTar(files, opts);
|
|
return new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(buffer);
|
|
controller.close();
|
|
}
|
|
}).pipeThrough(new CompressionStream(opts.compression ?? "gzip"));
|
|
}
|
|
async function createTarGzip(files, opts = {}) {
|
|
const data = await new Response(createTarGzipStream(files, opts)).arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
|
return data;
|
|
}
|
|
function _writeString(buffer, str, offset, size) {
|
|
const strView = new Uint8Array(buffer, offset, size);
|
|
const te = new TextEncoder();
|
|
const written = te.encodeInto(str, strView).written;
|
|
for (let i = written; i < size; i++) {
|
|
strView[i] = 0;
|
|
}
|
|
}
|
|
function _leftPad(input, targetLength) {
|
|
return String(input).padStart(targetLength, "0");
|
|
}
|
|
function _normalizeData(data) {
|
|
if (data === null || data === undefined) {
|
|
return undefined;
|
|
}
|
|
if (typeof data === "string") {
|
|
return new TextEncoder().encode(data);
|
|
}
|
|
if (data instanceof ArrayBuffer) {
|
|
return new Uint8Array(data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
export { createTar, createTarGzip, createTarGzipStream, parseTar, parseTarGzip };
|