Files
OpenFrame/lib/image-upload-validation.ts
T

83 lines
2.0 KiB
TypeScript

export const ALLOWED_IMAGE_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
] as const;
export type AllowedImageMimeType = (typeof ALLOWED_IMAGE_MIME_TYPES)[number];
const EXT_BY_MIME: Record<AllowedImageMimeType, string> = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/webp': 'webp',
'image/gif': 'gif',
};
export function normalizeImageMime(value: string): string {
if (value === 'image/jpg' || value === 'image/pjpeg') return 'image/jpeg';
return value;
}
export function isAllowedImageType(value: string): value is AllowedImageMimeType {
return (ALLOWED_IMAGE_MIME_TYPES as readonly string[]).includes(value);
}
export function detectImageMime(buffer: Uint8Array): AllowedImageMimeType | null {
// JPEG
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xd8) {
return 'image/jpeg';
}
// PNG
if (
buffer.length >= 8 &&
buffer[0] === 0x89 &&
buffer[1] === 0x50 &&
buffer[2] === 0x4e &&
buffer[3] === 0x47 &&
buffer[4] === 0x0d &&
buffer[5] === 0x0a &&
buffer[6] === 0x1a &&
buffer[7] === 0x0a
) {
return 'image/png';
}
// GIF87a/GIF89a
if (
buffer.length >= 6 &&
buffer[0] === 0x47 &&
buffer[1] === 0x49 &&
buffer[2] === 0x46 &&
buffer[3] === 0x38 &&
(buffer[4] === 0x37 || buffer[4] === 0x39) &&
buffer[5] === 0x61
) {
return 'image/gif';
}
// WEBP: "RIFF"...."WEBP"
if (
buffer.length >= 12 &&
buffer[0] === 0x52 &&
buffer[1] === 0x49 &&
buffer[2] === 0x46 &&
buffer[3] === 0x46 &&
buffer[8] === 0x57 &&
buffer[9] === 0x45 &&
buffer[10] === 0x42 &&
buffer[11] === 0x50
) {
return 'image/webp';
}
return null;
}
export function getImageExtension(mime: AllowedImageMimeType): string {
return EXT_BY_MIME[mime];
}
export function firstBytesHex(buffer: Uint8Array, length = 16): string {
return Array.from(buffer.slice(0, length))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join(' ');
}