Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
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<string, Pick<ValidatedBrandImage, 'extension' | 'contentType'>> = {
|
||
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<ValidatedBrandImage> {
|
||
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,
|
||
}
|
||
}
|