{"version":3,"file":"paths-DXx1EfjM.cjs","sources":["../src/core/paths.ts"],"sourcesContent":["/**\n * Path confinement for every path that reaches us from outside: caller file\n * inputs, tar entry names, TDS paths parsed out of engine logs.\n *\n * All of them are interpreted relative to a root the engine must not escape\n * (`/project` for inputs, `/texmf-dist` for the tree). A `..` segment — or a\n * tar entry named `/etc/passwd` — would place the write outside that root.\n * The escape is confined to the WASM filesystem today, but the engine also\n * reads its own configuration from there, so treat the roots as boundaries.\n */\n\n/**\n * Normalize a caller-supplied path to a root-relative one, or return null if\n * it tries to escape. Leading slashes are stripped (an \"absolute\" path is\n * taken as relative to the root); `.` segments are dropped; any `..` segment\n * is a rejection rather than something to resolve, since a legitimate TDS or\n * project path never contains one.\n */\nexport function safeRelativePath(path: string): string | null {\n  const segments = path.split(/[/\\\\]+/).filter((s) => s !== '' && s !== '.');\n  if (segments.length === 0) return null;\n  if (segments.some((s) => s === '..')) return null;\n  // A Windows drive letter or a UNC-ish leading segment would be nonsense\n  // inside the WASM FS; reject rather than silently reinterpret.\n  if (/^[a-z]:$/i.test(segments[0]!)) return null;\n  // A NUL byte truncates the path at the C boundary: everything after it is\n  // invisible to us but not to the engine.\n  if (segments.some((s) => s.includes('\\0'))) return null;\n  return segments.join('/');\n}\n\n/**\n * Resolve a path against an absolute root, or return null if it escapes.\n * `root` itself is the identity case — `cwd: '/project'` must stay legal.\n */\nexport function safeResolve(root: string, path: string): string | null {\n  const base = root.replace(/\\/+$/, '');\n  // Only a path AT the root or BELOW it is inside it: \"/projectile\" shares a\n  // prefix with \"/project\" and is not under it.\n  const inRoot = path === base || path.startsWith(`${base}/`);\n  // An absolute path pointing somewhere else entirely is not \"relative to the\n  // root\" — it is an escape spelled without any `..`.\n  if (!inRoot && path.startsWith('/')) return null;\n  const rest = inRoot ? path.slice(base.length) : path;\n  if (rest === '' || rest === '/') return base;\n  const rel = safeRelativePath(rest);\n  return rel === null ? null : `${base}/${rel}`;\n}\n"],"names":[],"mappings":";;AAkBO,SAAS,iBAAiB,IAAA,EAA6B;AAC5D,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,KAAM,EAAA,IAAM,CAAA,KAAM,GAAG,CAAA;AACzE,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAClC,EAAA,IAAI,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,KAAM,IAAI,GAAG,OAAO,IAAA;AAG7C,EAAA,IAAI,YAAY,IAAA,CAAK,QAAA,CAAS,CAAC,CAAE,GAAG,OAAO,IAAA;AAG3C,EAAA,IAAI,QAAA,CAAS,KAAK,CAAC,CAAA,KAAM,EAAE,QAAA,CAAS,IAAI,CAAC,CAAA,EAAG,OAAO,IAAA;AACnD,EAAA,OAAO,QAAA,CAAS,KAAK,GAAG,CAAA;AAC1B;AAMO,SAAS,WAAA,CAAY,MAAc,IAAA,EAA6B;AACrE,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAGpC,EAAA,MAAM,SAAS,IAAA,KAAS,IAAA,IAAQ,KAAK,UAAA,CAAW,CAAA,EAAG,IAAI,CAAA,CAAA,CAAG,CAAA;AAG1D,EAAA,IAAI,CAAC,MAAA,IAAU,IAAA,CAAK,UAAA,CAAW,GAAG,GAAG,OAAO,IAAA;AAC5C,EAAA,MAAM,OAAO,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA;AAChD,EAAA,IAAI,IAAA,KAAS,EAAA,IAAM,IAAA,KAAS,GAAA,EAAK,OAAO,IAAA;AACxC,EAAA,MAAM,GAAA,GAAM,iBAAiB,IAAI,CAAA;AACjC,EAAA,OAAO,QAAQ,IAAA,GAAO,IAAA,GAAO,CAAA,EAAG,IAAI,IAAI,GAAG,CAAA,CAAA;AAC7C;;;;;"}