feat: complete v0.5 operations foundation
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { extname } from "node:path";
|
||||
import { logEvent } from "@/lib/observability";
|
||||
|
||||
const defaults = new Set(["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf", "text/plain", "text/markdown"]);
|
||||
|
||||
export async function validateUpload(file: File) {
|
||||
const allowed = new Set((process.env.UPLOAD_ALLOWED_TYPES || "").split(",").map((item) => item.trim()).filter(Boolean));
|
||||
const types = allowed.size ? allowed : defaults;
|
||||
const max = Number(process.env.UPLOAD_MAX_BYTES || 10 * 1024 * 1024);
|
||||
if (!types.has(file.type)) throw new Error(`不允許的附件類型:${file.type || extname(file.name) || "未知"}`);
|
||||
if (file.size > max) throw new Error(`${file.name} exceeds upload limit`);
|
||||
const scanner = process.env.VIRUS_SCAN_URL;
|
||||
if (!scanner) return;
|
||||
try {
|
||||
const response = await fetch(scanner, { method: "POST", headers: { "content-type": file.type || "application/octet-stream", "x-filename": encodeURIComponent(file.name) }, body: await file.arrayBuffer(), signal: AbortSignal.timeout(15_000) });
|
||||
const result = await response.json().catch(() => ({})) as { clean?: boolean };
|
||||
if (!response.ok || result.clean !== true) throw new Error("附件未通過掃描");
|
||||
} catch (error) {
|
||||
logEvent("warn", "upload_scan_unavailable", { name: file.name });
|
||||
if (process.env.VIRUS_SCAN_REQUIRED === "1") throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user