82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
/**
|
|
* Minimal extension ↔ MIME mapping + `accept` matching. Zero-dep: just a table
|
|
* big enough for the common upload types (images, docs, media, archives).
|
|
*/
|
|
|
|
const BY_EXT: Record<string, string> = {
|
|
// images
|
|
png: "image/png",
|
|
jpg: "image/jpeg",
|
|
jpeg: "image/jpeg",
|
|
gif: "image/gif",
|
|
webp: "image/webp",
|
|
avif: "image/avif",
|
|
svg: "image/svg+xml",
|
|
ico: "image/x-icon",
|
|
bmp: "image/bmp",
|
|
// documents
|
|
pdf: "application/pdf",
|
|
txt: "text/plain",
|
|
html: "text/html",
|
|
htm: "text/html",
|
|
csv: "text/csv",
|
|
json: "application/json",
|
|
xml: "application/xml",
|
|
doc: "application/msword",
|
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
xls: "application/vnd.ms-excel",
|
|
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
ppt: "application/vnd.ms-powerpoint",
|
|
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
// media
|
|
mp3: "audio/mpeg",
|
|
wav: "audio/wav",
|
|
ogg: "audio/ogg",
|
|
mp4: "video/mp4",
|
|
webm: "video/webm",
|
|
mov: "video/quicktime",
|
|
// archives / misc
|
|
zip: "application/zip",
|
|
gz: "application/gzip",
|
|
tar: "application/x-tar",
|
|
};
|
|
|
|
/** Lowercased extension WITHOUT the dot (e.g. "png"), or "" if none. */
|
|
export function extOf(name: string): string {
|
|
const clean = name.split(/[?#]/)[0] ?? "";
|
|
const dot = clean.lastIndexOf(".");
|
|
return dot >= 0 ? clean.slice(dot + 1).toLowerCase() : "";
|
|
}
|
|
|
|
/** MIME type for a filename/key by its extension, or a safe default. */
|
|
export function contentTypeOf(name: string, fallback = "application/octet-stream"): string {
|
|
return BY_EXT[extOf(name)] ?? fallback;
|
|
}
|
|
|
|
/** The conventional extension for a MIME type, or "" (used to name S3 keys). */
|
|
export function extForType(type: string): string {
|
|
const t = type.split(";")[0]!.trim().toLowerCase();
|
|
for (const [ext, mime] of Object.entries(BY_EXT)) if (mime === t) return ext;
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Does `file` (its MIME `type` + `name`) satisfy an `accept` list? Each accept
|
|
* entry is a MIME type (`"image/png"`), a wildcard MIME (`"image/*"`), or a
|
|
* dotted extension (`".pdf"`). An empty/omitted list accepts everything.
|
|
*/
|
|
export function accepts(
|
|
accept: string[] | undefined,
|
|
file: { type: string; name: string },
|
|
): boolean {
|
|
if (!accept || accept.length === 0) return true;
|
|
const type = (file.type || contentTypeOf(file.name)).toLowerCase();
|
|
const ext = "." + extOf(file.name);
|
|
return accept.some((raw) => {
|
|
const rule = raw.trim().toLowerCase();
|
|
if (rule.startsWith(".")) return rule === ext;
|
|
if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1)); // "image/" prefix
|
|
return rule === type;
|
|
});
|
|
}
|