55 lines · 2.0 KB
/** Minimal ustar writing shared by repository archives and action archives. */
export const TAR_BLOCK = 512;
const TAR_NAME_BYTES = 100;
const TAR_PREFIX_BYTES = 155;
const encoder = new TextEncoder();

function octal(value: number, length: number): string {
  return value.toString(8).padStart(length - 1, "0") + "\0";
}

function writeString(target: Uint8Array, offset: number, length: number, value: string): void {
  target.set(encoder.encode(value).subarray(0, length), offset);
}

/** A ustar header, or null when the path can't be represented. `mtimeSeconds` defaults to now. */
export function tarHeader(
  path: string,
  size: number,
  mode: number,
  type: "0" | "2" | "5",
  linkTarget = "",
  mtimeSeconds = Math.floor(Date.now() / 1000),
): Uint8Array<ArrayBuffer> | null {
  let name = path;
  let prefix = "";
  if (encoder.encode(name).byteLength > TAR_NAME_BYTES) {
    const split = path.lastIndexOf("/", TAR_PREFIX_BYTES);
    if (split <= 0) return null;
    prefix = path.slice(0, split);
    name = path.slice(split + 1);
    if (encoder.encode(name).byteLength > TAR_NAME_BYTES || encoder.encode(prefix).byteLength > TAR_PREFIX_BYTES)
      return null;
  }
  const header = new Uint8Array(TAR_BLOCK);
  writeString(header, 0, 100, name);
  writeString(header, 100, 8, octal(mode, 8));
  writeString(header, 108, 8, octal(0, 8));
  writeString(header, 116, 8, octal(0, 8));
  writeString(header, 124, 12, octal(size, 12));
  writeString(header, 136, 12, octal(mtimeSeconds, 12));