65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
/**
|
|
* Single-file tar extraction for raw downloads (#1180).
|
|
*
|
|
* Docker's /archive endpoint always wraps file contents in a USTAR tar.
|
|
* When the user picks the "no archive" download format and the path is a
|
|
* regular file, we strip the wrapper and emit the bytes verbatim.
|
|
*
|
|
* Only handles the first regular-file entry — the caller has already
|
|
* guaranteed (via stat) that the requested path is a single file, so the
|
|
* tar contains exactly one entry.
|
|
*/
|
|
|
|
/**
|
|
* Extract the bytes of the first regular file entry in a USTAR tar.
|
|
* Returns the file content as a Uint8Array.
|
|
*
|
|
* Throws when no regular file entry is found (e.g. the tar contained only
|
|
* a directory header) — that's an unexpected state, since the caller is
|
|
* supposed to have already verified the path points to a file.
|
|
*/
|
|
export function extractFirstFileFromTar(tarData: Uint8Array): Uint8Array {
|
|
let offset = 0;
|
|
while (offset + 512 <= tarData.length) {
|
|
const header = tarData.subarray(offset, offset + 512);
|
|
|
|
// Two consecutive zero blocks mark end-of-archive.
|
|
if (isZeroBlock(header)) break;
|
|
|
|
const name = readString(header, 0, 100);
|
|
const sizeOctal = readString(header, 124, 12).trim();
|
|
const size = sizeOctal ? parseInt(sizeOctal, 8) : 0;
|
|
const typeFlag = header[156];
|
|
|
|
// Regular file: typeflag '0' (0x30) or NUL (0x00, legacy)
|
|
const isRegularFile = typeFlag === 0x30 || typeFlag === 0x00;
|
|
|
|
if (isRegularFile && name && size >= 0) {
|
|
const start = offset + 512;
|
|
const end = start + size;
|
|
if (end > tarData.length) {
|
|
throw new Error('Truncated tar archive');
|
|
}
|
|
return tarData.subarray(start, end);
|
|
}
|
|
|
|
// Skip header + content (padded to 512-byte boundary)
|
|
offset += 512 + Math.ceil(size / 512) * 512;
|
|
}
|
|
throw new Error('No regular file entry found in tar archive');
|
|
}
|
|
|
|
function isZeroBlock(block: Uint8Array): boolean {
|
|
for (let i = 0; i < block.length; i++) {
|
|
if (block[i] !== 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function readString(buf: Uint8Array, offset: number, length: number): string {
|
|
let end = offset;
|
|
const limit = offset + length;
|
|
while (end < limit && buf[end] !== 0) end++;
|
|
return new TextDecoder('utf-8').decode(buf.subarray(offset, end));
|
|
}
|