MCPcopy Create free account
hub / github.com/heygen-com/hyperframes / processUploadedFiles

Function processUploadedFiles

packages/studio-server/src/routes/files.ts:1496–1578  ·  view source on GitHub ↗
(
  formData: FormData,
  targetDir: string,
  projectDir: string,
)

Source from the content-addressed store, hash-verified

1494// ── Upload file processing ──────────────────────────────────────────────────
1495
1496async function processUploadedFiles(
1497 formData: FormData,
1498 targetDir: string,
1499 projectDir: string,
1500): Promise<{
1501 uploaded: string[];
1502 skipped: string[];
1503 invalid: Array<{ name: string; reason: string }>;
1504}> {
1505 const MAX_UPLOAD_BYTES = 500 * 1024 * 1024; // 500 MB per file
1506 const uploaded: string[] = [];
1507 const skipped: string[] = [];
1508 const invalid: Array<{ name: string; reason: string }> = [];
1509
1510 // @types/node v25 narrows the ambient `FormData.entries()` to
1511 // `[string, string]` in workspaces where another dep declares an
1512 // `onmessage` global (it trips the worker branch of v25's conditional
1513 // File type). At runtime the value is still `File | string` — cast the
1514 // iterator so the rest of this block keeps type-checking on every
1515 // bun-install layout (hoisted on Windows surfaces this; isolated on
1516 // Linux happens to keep v24 in scope).
1517 type FileLike = {
1518 readonly name: string;
1519 readonly size: number;
1520 arrayBuffer(): Promise<ArrayBuffer>;
1521 };
1522 const entries = formData.entries() as unknown as Iterable<[string, FileLike | string]>;
1523
1524 // Derive the subdirectory prefix from targetDir relative to projectDir
1525 const subDir = targetDir === projectDir ? "" : targetDir.slice(projectDir.length + 1);
1526
1527 for (const [, value] of entries) {
1528 if (typeof value === "string") continue;
1529
1530 // Strip path separators — browsers may include directory components
1531 const name = value.name.split("/").pop()?.split("\\").pop() ?? "";
1532 if (!name || name.includes("\0") || name.includes("..")) continue;
1533
1534 // Reject individual files that exceed the size limit
1535 if (value.size > MAX_UPLOAD_BYTES) {
1536 skipped.push(name);
1537 continue;
1538 }
1539
1540 const destPath = resolve(targetDir, name);
1541 if (!isSafePath(projectDir, destPath)) continue;
1542
1543 // Don't overwrite — append (2), (3), etc.
1544 let finalPath = destPath;
1545 let finalName = name;
1546 if (existsSync(finalPath)) {
1547 // Handle dotfiles correctly: .gitignore → ext="", base=".gitignore"
1548 const dotIdx = name.indexOf(".", name.startsWith(".") ? 1 : 0);
1549 const ext = dotIdx > 0 ? name.slice(dotIdx) : "";
1550 const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
1551 let n = 2;
1552 const MAX_COPY_INDEX = 10000;
1553 while (n < MAX_COPY_INDEX && existsSync(resolve(targetDir, `${base} (${n})${ext}`))) n++;

Callers 1

registerFileRoutesFunction · 0.85

Calls 7

resolveFunction · 0.85
isSafePathFunction · 0.85
generateWaveformCacheFunction · 0.85
entriesMethod · 0.80
fromMethod · 0.80
isAudioFileFunction · 0.50

Tested by

no test coverage detected