{
  "version": 3,
  "sources": ["../../../../src/packages/store.cafs/parseTarball.ts"],
  "sourcesContent": ["import path from 'node:path';\n\nexport type IParseResult = {\n  buffer: ArrayBufferLike;\n  files: Map<string, IFile>;\n};\n\nexport type IFile = {\n  offset: number;\n  mode: number;\n  size: number;\n};\n\nconst ZERO: number = '0'.charCodeAt(0);\nconst FILE_TYPE_HARD_LINK: number = '1'.charCodeAt(0);\nconst FILE_TYPE_SYMLINK: number = '2'.charCodeAt(0);\nconst FILE_TYPE_DIRECTORY: number = '5'.charCodeAt(0);\nconst SPACE: number = ' '.charCodeAt(0);\nconst SLASH: number = '/'.charCodeAt(0);\nconst BACKSLASH: number = '\\\\'.charCodeAt(0);\nconst FILE_TYPE_PAX_HEADER: number = 'x'.charCodeAt(0);\nconst FILE_TYPE_PAX_GLOBAL_HEADER: number = 'g'.charCodeAt(0);\nconst FILE_TYPE_LONGLINK: number = 'L'.charCodeAt(0);\n\nconst MODE_OFFSET = 100;\nconst FILE_SIZE_OFFSET = 124;\nconst CHECKSUM_OFFSET = 148;\nconst FILE_TYPE_OFFSET = 156;\nconst PREFIX_OFFSET = 345;\n\n// See TAR specification here: https://www.gnu.org/software/tar/manual/html_node/Standard.html\nexport function parseTarball(buffer: Buffer): IParseResult {\n  const files = new Map<string, IFile>();\n\n  let pathTrimmed = false;\n\n  let mode = 0;\n  let fileSize = 0;\n  let fileType = 0;\n\n  let prefix = '';\n  let fileName = '';\n  let longLinkPath = '';\n\n  // If a PAX extended header record is encountered and has a path field, it overrides the next entry's path.\n  let paxHeaderPath = '';\n  let paxHeaderFileSize: number | undefined;\n\n  let blockBytes = 0;\n\n  let blockStart = 0;\n\n  while (buffer[blockStart] !== 0) {\n    // Parse out a TAR header. header size is 512 bytes.\n    // The file type is a single byte at offset 156 in the header\n    fileType = buffer[blockStart + FILE_TYPE_OFFSET] ?? 0;\n\n    if (paxHeaderFileSize === undefined) {\n      // The file size is an octal number encoded as UTF-8. It is terminated by a NUL or space. Maximum length 12 characters.\n      fileSize = parseOctal(blockStart + FILE_SIZE_OFFSET, 12);\n    } else {\n      fileSize = paxHeaderFileSize;\n\n      paxHeaderFileSize = undefined;\n    }\n\n    // The total size will always be an integer number of 512 byte blocks.\n    // Also include 1 block for the header itself.\n    blockBytes = (fileSize & ~0x1ff) + (fileSize & 0x1ff ? 1024 : 512);\n\n    const expectedCheckSum: number = parseOctal(\n      blockStart + CHECKSUM_OFFSET,\n      8\n    );\n\n    const actualCheckSum: number = checkSum(blockStart);\n\n    if (expectedCheckSum !== actualCheckSum) {\n      throw new Error(\n        `Invalid checksum for TAR header at offset ${blockStart}. Expected ${expectedCheckSum}, got ${actualCheckSum}`\n      );\n    }\n\n    // Mark that the first path segment has not been removed.\n    pathTrimmed = false;\n\n    if (longLinkPath) {\n      fileName = longLinkPath;\n      longLinkPath = '';\n    } else if (paxHeaderPath) {\n      fileName = paxHeaderPath;\n\n      // The PAX header only applies to the immediate next entry.\n      paxHeaderPath = '';\n    } else {\n      // The full file path is an optional prefix at offset 345, followed by the file name at offset 0, separated by a '/'.\n      // Both values are terminated by a NUL if not using the full length of the field.\n      prefix = parseString(blockStart + PREFIX_OFFSET, 155);\n\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      // If the prefix is present and did not contain a `/` or `\\\\`, then the prefix is the first path segment and should be dropped entirely.\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n      if (prefix && pathTrimmed !== true) {\n        pathTrimmed = true;\n        prefix = '';\n      }\n\n      // Get the base filename at offset 0, up to 100 characters (where the mode field begins).\n      fileName = parseString(blockStart, MODE_OFFSET);\n\n      if (prefix) {\n        // If the prefix was not trimmed entirely (or absent), need to join with the remaining filename\n        fileName = `${prefix}/${fileName}`;\n      }\n    }\n\n    if (fileName.includes('./')) {\n      // Bizarre edge case\n      fileName = path.posix.join('/', fileName).slice(1);\n    }\n\n    // Values '\\0' and '0' are normal files.\n    // Treat all other file types as non-existent\n    // However, we still need to parse the name to handle collisions\n    switch (fileType) {\n      case 0:\n      case ZERO:\n      case FILE_TYPE_HARD_LINK:\n        // The file mode is an octal number encoded as UTF-8. It is terminated by a NUL or space. Maximum length 8 characters.\n        mode = parseOctal(blockStart + MODE_OFFSET, 8);\n\n        // The TAR format is an append-only data structure; as such later entries with the same name supercede earlier ones.\n        files.set(fileName.replaceAll('//', '/'), {\n          offset: blockStart + 512,\n          mode,\n          size: fileSize,\n        });\n        break;\n      case FILE_TYPE_DIRECTORY:\n      case FILE_TYPE_SYMLINK:\n        // Skip\n        break;\n      case FILE_TYPE_PAX_HEADER:\n        parsePaxHeader(blockStart + 512, fileSize, false);\n        break;\n      case FILE_TYPE_PAX_GLOBAL_HEADER:\n        parsePaxHeader(blockStart + 512, fileSize, true);\n        break;\n      case FILE_TYPE_LONGLINK: {\n        // Read the long filename\n        longLinkPath = buffer\n          .toString('utf8', blockStart + 512, blockStart + 512 + fileSize)\n          .replace(/\\0.*/, '');\n        // Remove the first path segment\n        const slashIndex = longLinkPath.indexOf('/');\n        if (slashIndex >= 0) {\n          longLinkPath = longLinkPath.slice(slashIndex + 1);\n        }\n        break;\n      }\n      default:\n        throw new Error(\n          `Unsupported file type ${fileType} for file ${fileName}.`\n        );\n    }\n\n    // Move to the next record in the TAR archive.\n    blockStart += blockBytes;\n  }\n\n  return { files, buffer: buffer.buffer };\n\n  /**\n   * Computes the checksum for the TAR header at the specified `offset`.\n   * @param offset - The current offset into the tar buffer\n   * @returns The header checksum\n   */\n  function checkSum(offset: number): number {\n    let sum = 256;\n    let i: number = offset;\n\n    const checksumStart: number = offset + 148;\n    const checksumEnd: number = offset + 156;\n    const blockEnd: number = offset + 512;\n\n    for (; i < checksumStart; i++) {\n      sum += buffer[i] ?? 0;\n    }\n\n    for (i = checksumEnd; i < blockEnd; i++) {\n      sum += buffer[i] ?? 0;\n    }\n\n    return sum;\n  }\n\n  /**\n   * Parses a PAX header, which is a series of key/value pairs.\n   *\n   * @param offset - Offset into the buffer where the PAX header starts\n   * @param length - Length of the PAX header, in bytes\n   * @param global - Whether this is a global PAX header\n   * @returns The path field, if present\n   */\n  function parsePaxHeader(\n    offset: number,\n    length: number,\n    global: boolean\n  ): void {\n    const end: number = offset + length;\n    let i: number = offset;\n    while (i < end) {\n      const lineStart: number = i;\n      while (i < end && buffer[i] !== SPACE) {\n        i++;\n      }\n\n      // The format of a PAX header line is \"%d %s=%s\\n\"\n      const strLen: string = buffer.toString('utf-8', lineStart, i);\n      const len: number = Number.parseInt(strLen, 10);\n      if (!len) {\n        throw new Error(`Invalid length in PAX record: ${strLen}`);\n      }\n\n      // Skip the space.\n      i++;\n\n      const lineEnd: number = lineStart + len;\n\n      const record: string = buffer.toString('utf-8', i, lineEnd - 1);\n      i = lineEnd;\n\n      const equalSign: number = record.indexOf('=');\n      const keyword: string = record.slice(0, equalSign);\n\n      if (keyword === 'path') {\n        // Still need to trim the first path segment.\n        const slashIndex: number = record.indexOf('/', equalSign + 1);\n        if (global) {\n          throw new Error(`Unexpected global PAX path: ${record}`);\n        }\n        paxHeaderPath = record.slice(\n          slashIndex >= 0 ? slashIndex + 1 : equalSign + 1\n        );\n      } else if (keyword === 'size') {\n        const size: number = Number.parseInt(record.slice(equalSign + 1), 10);\n        if (Number.isNaN(size) || size < 0) {\n          throw new Error(`Invalid size in PAX record: ${record}`);\n        }\n        if (global) {\n          throw new Error(`Unexpected global PAX file size: ${record}`);\n        }\n        paxHeaderFileSize = size;\n      } else {\n        // Ignore. Not relevant.\n        // continue;\n      }\n    }\n  }\n\n  /**\n   * Parses a UTF-8 string at the specified `offset`, up to `length` characters. If it ends early, it will be terminated by a NUL.\n   * Will trim the first segment if `pathTrimmed` is currently false and the string contains a `/` or `\\\\`.\n   */\n  function parseString(offset: number, length: number): string {\n    let end: number = offset;\n\n    let newOffset = offset;\n\n    const max: number = length + newOffset;\n\n    for (\n      let char: number = buffer[end] ?? 0;\n      char !== 0 && end !== max;\n      char = buffer[++end] ?? 0\n    ) {\n      if (!pathTrimmed && (char === SLASH || char === BACKSLASH)) {\n        pathTrimmed = true;\n\n        newOffset = end + 1;\n      }\n    }\n\n    return buffer.toString('utf8', newOffset, end);\n  }\n\n  /**\n   * Parses an octal number at the specified `offset`, up to `length` characters. If it ends early, it will be terminated by either\n   * a NUL or a space.\n   */\n  function parseOctal(offset: number, length: number): number {\n    const val = buffer.subarray(offset, offset + length);\n\n    let newOffset = 0;\n\n    // Older versions of tar can prefix with spaces\n    while (newOffset < val.length && val[newOffset] === SPACE) newOffset++;\n\n    const end = clamp(\n      indexOf(val, SPACE, newOffset, val.length),\n      val.length,\n      val.length\n    );\n\n    while (newOffset < end && val[newOffset] === 0) newOffset++;\n\n    if (end === newOffset) return 0;\n\n    return Number.parseInt(val.slice(newOffset, end).toString(), 8);\n  }\n  // eslint-enable no-var\n}\n\nfunction indexOf(\n  block: Buffer,\n  num: number,\n  offset: number,\n  end: number\n): number {\n  let newOffset = offset;\n\n  for (; newOffset < end; newOffset++) {\n    if (block[newOffset] === num) return newOffset;\n  }\n\n  return end;\n}\n\nfunction clamp(index: number, len: number, defaultValue: number): number {\n  if (typeof index !== 'number') {\n    return defaultValue;\n  }\n\n  let newIndex = ~~index; // Coerce to integer.\n\n  if (newIndex >= len) {\n    return len;\n  }\n\n  if (newIndex >= 0) {\n    return newIndex;\n  }\n\n  newIndex += len;\n\n  if (newIndex >= 0) {\n    return newIndex;\n  }\n\n  return 0;\n}\n"],
  "mappings": "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",
  "names": []
}
