Files
kita-planer/src/lib/attachments.ts
T

38 lines
2.1 KiB
TypeScript

import { rm } from "node:fs/promises";
import path from "node:path";
export const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
export function attachmentDirectory(): string {
// Preserve the existing storage layout, including explicitly configured roots.
return path.resolve(process.env.UPLOAD_DIR ?? "uploads", "announcements");
}
export function attachmentPath(id: string): string {
if (!/^(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|c[a-z0-9]{24,32})$/i.test(id)) throw new Error("Invalid attachment ID");
return path.join(attachmentDirectory(), id);
}
export function detectAttachmentType(bytes: Buffer): string | null {
if (bytes.length < 12 || bytes.length > MAX_ATTACHMENT_SIZE) return null;
if (bytes.subarray(0, 8).equals(Buffer.from([137,80,78,71,13,10,26,10])) &&
bytes.subarray(-12).equals(Buffer.from([0,0,0,0,73,69,78,68,174,66,96,130]))) return "image/png";
if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255 && bytes.subarray(-2).equals(Buffer.from([255,217]))) return "image/jpeg";
if (bytes.subarray(0, 5).toString("ascii") === "%PDF-" && bytes.subarray(-1024).toString("ascii").trimEnd().endsWith("%%EOF")) return "application/pdf";
return null;
}
export function attachmentExtensionMatches(name: string, type: string): boolean {
const extension = path.extname(name).toLowerCase();
return type === "image/jpeg" ? [".jpg", ".jpeg"].includes(extension) :
type === "image/png" ? extension === ".png" : type === "application/pdf" && extension === ".pdf";
}
export function attachmentDisposition(name: string): string {
const safe = name.replace(/[\r\n]/g, "").slice(0, 160);
const encoded = encodeURIComponent(safe).replace(/['()*]/g, char => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
return `attachment; filename="${safe.replace(/[^\w.\-]/g, "_")}"; filename*=UTF-8''${encoded}`;
}
export async function removeAttachmentFiles(ids: string[]): Promise<void> {
await Promise.all(ids.map(async id => {
try { await rm(attachmentPath(id), { force: true }); }
catch (error) { console.error("Attachment cleanup failed; run the orphan cleanup command", id, error); }
}));
}