# `mime.ts` ## Purpose Cross-checks the declared `Content-Type` against the plugin's configured allow/deny lists AND (optionally) against the magic-bytes signature of the upload's first 4 KiB. This is the spoof-detection layer: a client uploading an `.exe` with `Content-Type: text/plain` is rejected because the sniffed type disagrees with the declared one. ## Features - Three rejection vectors: denylist, allow-list (when non-empty), and sniff-mismatch. - Canonical lowercase `type/subtype` form returned so every downstream record stores a normalised value. - Structured `context.reason` (`"deny-list"`, `"allow-list"`, `"sniff-mismatch"`, `"malformed"`) so handlers and dashboards can branch on rejection cause without parsing message text. - Treats malformed declared `Content-Type` as a 415 rather than a 400 because the user payload was the data we couldn't accept — not the route shape. - Sniff comparison only fires when magic bytes produce a confident match (it never blocks unknown formats). ## Usage: Basic ```ts import { validateMime } from "@suaveplan/api-plugin-file-upload/validators/mime"; const canonical = validateMime({ declaredMimeType: "image/png", allowedMimeTypes: ["image/png", "image/jpeg"], deniedMimeTypes: [], }); ``` ## Usage: Advanced ```ts import { validateMime, FileUploadError } from "@suaveplan/api-plugin-file-upload"; const sample = new Uint8Array(buf.slice(0, 4096)); try { validateMime({ declaredMimeType: "text/plain", allowedMimeTypes: [], deniedMimeTypes: [], sniffSample: sample, }); } catch (err) { if (err instanceof FileUploadError) { // err.context.reason === "sniff-mismatch" } } ``` ## API Reference - `ValidateMimeOptions` — `{ declaredMimeType, allowedMimeTypes, deniedMimeTypes, sniffSample? }`. - `validateMime(opts: ValidateMimeOptions): string` — returns the canonical type; throws `FileUploadError(UPLOAD.MIME_REJECTED)` on any rejection vector. ## Implementation Notes - `@suaveplan/mime`'s `detectFromMagicBytes` returns `undefined` for unrecognised payloads; we treat that as "no signal" and skip the cross-check, so unknown binary formats still upload successfully if the declared type is otherwise allowed. - The "allow-list" semantics only engage when the array is non-empty so the default config (`allowedMimeTypes: []`) means "rely on the denylist alone". - Comparisons are lowercase string-equality, not glob/regex, so callers needing `image/*` semantics should pre-expand the list at config time.