You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
55 lines
1.3 KiB
55 lines
1.3 KiB
import fs from "node:fs";
|
|
|
|
/** 与 multer 白名单一致:仅 PNG / JPEG / WebP(RIFF…WEBP) */
|
|
export const IMAGE_MAGIC_MISMATCH_MESSAGE = "文件内容与真实格式不符,请上传 PNG、JPEG 或 WebP 图片";
|
|
|
|
function matchesAllowedRasterMagic(head: Uint8Array): boolean {
|
|
if (head.length < 12) {
|
|
return false;
|
|
}
|
|
// JPEG
|
|
if (head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff) {
|
|
return true;
|
|
}
|
|
// PNG
|
|
if (
|
|
head[0] === 0x89
|
|
&& head[1] === 0x50
|
|
&& head[2] === 0x4e
|
|
&& head[3] === 0x47
|
|
&& head[4] === 0x0d
|
|
&& head[5] === 0x0a
|
|
&& head[6] === 0x1a
|
|
&& head[7] === 0x0a
|
|
) {
|
|
return true;
|
|
}
|
|
// WebP (RIFF .... WEBP)
|
|
if (
|
|
head[0] === 0x52
|
|
&& head[1] === 0x49
|
|
&& head[2] === 0x46
|
|
&& head[3] === 0x46
|
|
&& head[8] === 0x57
|
|
&& head[9] === 0x45
|
|
&& head[10] === 0x42
|
|
&& head[11] === 0x50
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** 读取文件头并校验魔数(不依赖客户端 Content-Type) */
|
|
export function assertDiskFileIsAllowedRasterImage(filePath: string): void {
|
|
const fd = fs.openSync(filePath, "r");
|
|
try {
|
|
const buf = Buffer.alloc(12);
|
|
const n = fs.readSync(fd, buf, 0, 12, 0);
|
|
if (n < 12 || !matchesAllowedRasterMagic(buf)) {
|
|
throw new Error(IMAGE_MAGIC_MISMATCH_MESSAGE);
|
|
}
|
|
} finally {
|
|
fs.closeSync(fd);
|
|
}
|
|
}
|
|
|