2.5 KiB
2.5 KiB
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/subtypeform 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-Typeas 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
import { validateMime } from "@suaveplan/api-plugin-file-upload/validators/mime";
const canonical = validateMime({
declaredMimeType: "image/png",
allowedMimeTypes: ["image/png", "image/jpeg"],
deniedMimeTypes: [],
});
Usage: Advanced
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; throwsFileUploadError(UPLOAD.MIME_REJECTED)on any rejection vector.
Implementation Notes
@suaveplan/mime'sdetectFromMagicBytesreturnsundefinedfor 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.