import { createHash } from "node:crypto"; /** Content-type decided by magic bytes, never by the client's header. */ export type SubmissionKind = "video" | "photo"; export interface SniffResult { kind: SubmissionKind; mime: string; } const SNIFF_WINDOWS = 32; /** mp4 (ftyp at offset 4), webm/mkv (EBML), jpeg (FFD8FF), png. */ export function sniffMedia(bytes: Buffer): SniffResult | null { if (bytes.length < SNIFF_WINDOWS) return null; if (bytes.subarray(4, 8).toString("latin1") === "ftyp") { const brand = bytes.subarray(8, 12).toString("latin1"); return { kind: "video", mime: brand.startsWith("avi") ? "video/x-msvideo" : "video/mp4" }; } if (bytes.subarray(0, 4).toString("hex") === "1a45dfa3") return { kind: "video", mime: "video/webm" }; if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return { kind: "photo", mime: "image/jpeg" }; if (bytes.subarray(0, 8).toString("hex") === "89504e470d0a1a0a") return { kind: "photo", mime: "image/png" }; return null; } /** sha256(ip + daily salt): rate-limit key only — never stored as IP, never rendered. */ export function ipHash(ip: string, salt: string, day: string): string { return createHash("sha256").update(`${salt}:${day}:${ip}`).digest("base64url"); }