import sharp from 'sharp' export const MAX_BRAND_IMAGE_BYTES = 5 * 1024 * 1024 export const MAX_BRAND_IMAGE_PIXELS = 4_194_304 export const MAX_BRAND_IMAGE_DIMENSION = 4096 export type ValidatedBrandImage = { extension: 'png' | 'jpg' | 'webp' contentType: 'image/png' | 'image/jpeg' | 'image/webp' width: number height: number } const FORMAT_DETAILS: Record> = { png: { extension: 'png', contentType: 'image/png' }, jpeg: { extension: 'jpg', contentType: 'image/jpeg' }, webp: { extension: 'webp', contentType: 'image/webp' }, } /** * 品牌图属于高可见度系统配置,不能只看 magic bytes。 * 这里会真正解码整张图,阻止截断文件、伪造 MIME、动画和解压炸弹进入数据库。 */ export async function validateBrandImage( buffer: Buffer, declaredContentType?: string | null, ): Promise { if (buffer.length <= 0 || buffer.length > MAX_BRAND_IMAGE_BYTES) { throw new Error('品牌图片必须是 5MB 以内的有效文件') } const image = sharp(buffer, { failOn: 'error', limitInputPixels: MAX_BRAND_IMAGE_PIXELS, animated: false, }) const metadata = await image.metadata() const format = metadata.format ? FORMAT_DETAILS[metadata.format] : undefined const width = metadata.width || 0 const height = metadata.height || 0 if (!format) { throw new Error('品牌图片只支持 PNG、JPEG 或 WebP') } if ( width < 1 || height < 1 || width > MAX_BRAND_IMAGE_DIMENSION || height > MAX_BRAND_IMAGE_DIMENSION || width * height > MAX_BRAND_IMAGE_PIXELS ) { throw new Error('品牌图片尺寸无效或像素过大') } if ((metadata.pages || 1) > 1) { throw new Error('品牌图片不支持动画格式') } const normalizedDeclaredType = String(declaredContentType || '') .split(';', 1)[0] .trim() .toLowerCase() if (normalizedDeclaredType && normalizedDeclaredType !== format.contentType) { throw new Error('品牌图片内容与声明格式不一致') } // metadata() 主要读取头信息;raw().toBuffer() 强制完整解码,截断图片会在这里失败。 await image.clone().raw().toBuffer() return { ...format, width, height, } }