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

78 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(' ');
}