55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
|
|
import { writeFileSync, mkdirSync } from "node:fs";
|
||
|
|
import { dirname } from "node:path";
|
||
|
|
import { safeResolveNewPath, isSystemFile } from "@/lib/workspace";
|
||
|
|
|
||
|
|
export const dynamic = "force-dynamic";
|
||
|
|
export const runtime = "nodejs";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* POST /api/workspace/write-binary
|
||
|
|
* Accepts FormData with `file` (Blob) and `path` (string).
|
||
|
|
* Writes the binary data to the workspace path.
|
||
|
|
*/
|
||
|
|
export async function POST(req: Request) {
|
||
|
|
let formData: FormData;
|
||
|
|
try {
|
||
|
|
formData = await req.formData();
|
||
|
|
} catch {
|
||
|
|
return Response.json({ error: "Invalid form data" }, { status: 400 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const file = formData.get("file");
|
||
|
|
const relPath = formData.get("path");
|
||
|
|
|
||
|
|
if (!relPath || typeof relPath !== "string") {
|
||
|
|
return Response.json({ error: "Missing 'path' field" }, { status: 400 });
|
||
|
|
}
|
||
|
|
if (!(file instanceof Blob)) {
|
||
|
|
return Response.json({ error: "Missing 'file' field (Blob)" }, { status: 400 });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isSystemFile(relPath)) {
|
||
|
|
return Response.json({ error: "Cannot modify system file" }, { status: 403 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const absPath = safeResolveNewPath(relPath);
|
||
|
|
if (!absPath) {
|
||
|
|
return Response.json(
|
||
|
|
{ error: "Invalid path or path traversal rejected" },
|
||
|
|
{ status: 400 },
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||
|
|
mkdirSync(dirname(absPath), { recursive: true });
|
||
|
|
writeFileSync(absPath, buffer);
|
||
|
|
return Response.json({ ok: true, path: relPath });
|
||
|
|
} catch (err) {
|
||
|
|
return Response.json(
|
||
|
|
{ error: err instanceof Error ? err.message : "Write failed" },
|
||
|
|
{ status: 500 },
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|