import { access, stat } from "node:fs/promises"; import { basename, extname, isAbsolute, join, relative, resolve } from "node:path"; export interface DeliverableFile { readonly path: string; readonly name: string; } const DELIVERABLE_EXTENSIONS = new Set([ ".csv", ".doc", ".docx", ".gif", ".jpeg", ".jpg", ".md", ".pdf", ".png", ".ppt", ".pptx", ".svg", ".txt", ".typ", ".xls", ".xlsx", ".zip", ]); export async function resolveDeliverableFile( requestedPath: string, workspaceDir: string, ): Promise { const token = requestedPath.trim(); if (token === "" || !DELIVERABLE_EXTENSIONS.has(extname(token).toLowerCase())) { return null; } const roots = await allowedRoots(workspaceDir); for (const candidate of candidatePaths(token, workspaceDir, roots)) { if (!isAllowedPath(candidate, roots)) continue; const existing = await existingFile(candidate); if (existing !== null) return { path: existing, name: basename(existing) }; } return null; } async function allowedRoots(workspaceDir: string): Promise { const roots = [resolve(workspaceDir)]; const gitRoot = await findGitRoot(workspaceDir); if (gitRoot !== undefined) roots.push(gitRoot); return [...new Set(roots)]; } async function findGitRoot(startDir: string): Promise { let current = resolve(startDir); while (true) { try { await access(join(current, ".git")); return current; } catch { const parent = resolve(current, ".."); if (parent === current) return undefined; current = parent; } } } function candidatePaths(token: string, workspaceDir: string, roots: readonly string[]): string[] { if (isAbsolute(token)) return [resolve(token)]; const candidates: string[] = []; for (const root of roots) { candidates.push(resolve(root, token)); } if (!token.includes("/") && !token.includes("\\")) { candidates.push(resolve(workspaceDir, "build", token)); } return [...new Set(candidates)]; } function isAllowedPath(path: string, roots: readonly string[]): boolean { return roots.some((root) => { const rel = relative(root, path); return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)); }); } async function existingFile(path: string): Promise { try { const s = await stat(path); return s.isFile() ? resolve(path) : null; } catch { return null; } }