import type { Context } from "@oak/oak"; import { APIErrorCode, APIException } from "../model/interfaces.ts"; import { ALLOWED_IMAGE_MIMES, ATTACHMENTS_DIR, AVATARS_DIR, DUMPS_DIR, MAX_IMAGE_SIZE_BYTES, PLAYLIST_IMAGES_DIR, UPLOADS_DIR, } from "../config.ts"; export { ALLOWED_IMAGE_MIMES, ATTACHMENTS_DIR, AVATARS_DIR, DUMPS_DIR, PLAYLIST_IMAGES_DIR, UPLOADS_DIR, }; /** Detect image MIME type from magic bytes, ignoring the browser-declared type. */ export function detectImageMime(data: Uint8Array): string | null { if (data[0] === 0xFF && data[1] === 0xD8 && data[2] === 0xFF) { return "image/jpeg"; } if ( data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47 ) return "image/png"; if ( data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x38 ) return "image/gif"; // RIFF....WEBP if ( data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50 ) return "image/webp"; return null; } /** Validates image upload data: checks size and MIME. Returns the detected MIME type or throws APIException. */ export function validateImageUpload(data: Uint8Array): string { if (data.length > MAX_IMAGE_SIZE_BYTES) { throw new APIException( APIErrorCode.BAD_REQUEST, 400, "File too large (max 5 MB)", ); } const mime = detectImageMime(data); if (!mime) { throw new APIException( APIErrorCode.BAD_REQUEST, 400, "File content is not a recognised image (JPEG, PNG, GIF, WebP)", ); } return mime; } export async function serveUploadedFile( ctx: Context, filePath: string, mime: string, cacheMaxAge = 3600, ): Promise { let data: Uint8Array; try { data = await Deno.readFile(filePath); } catch { ctx.response.status = 404; return false; } ctx.response.headers.set("Content-Type", mime); ctx.response.headers.set("Content-Disposition", "inline"); ctx.response.headers.set("Cache-Control", `public, max-age=${cacheMaxAge}`); ctx.response.headers.set("Cross-Origin-Resource-Policy", "cross-origin"); ctx.response.body = data; return true; }