Files
OJ2/apps/api/src/services/image.ts
T
xuyueandClaude Opus 5.5 e17da63fc6 fix(上传): 头像和题面插图按文件头认格式,不信扩展名
原来只看扩展名:改个后缀什么都能落盘,再由 /public/* 按扩展名配 content-type
发出去。线上 Caddy 有 nosniff 兜着,但不该把非图片存到盘上。

- services/image.ts:按 PNG / JPEG / GIF / BMP 的文件头识别,返回落盘用的扩展名 ——
  以内容为准,改名成 .png 的 JPEG 存成 .jpg,content-type 才对得上
- 头像上传、后台插图上传都走它;插图那边扩展名白名单保留当第一道快速筛
- serveUpload 自己也带 x-content-type-options: nosniff,不只靠 Caddy

实跑核过:真 PNG / GIF 正常;改名成 .png 的 JPEG 存成 .jpg;改名成 .png 的 HTML
两个入口都拒;/public/upload 回 image/png + nosniff。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:15:36 +08:00

22 lines
897 B
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 按文件头认图片格式,不信文件名。头像和题面插图原来只看扩展名:改个后缀什么都能
* 落盘,再由 /public/* 按扩展名配 content-type 发出去。
*
* 返回值就是落盘该用的扩展名 —— 以内容为准,一张改名成 .png 的 JPEG 会存成 .jpg,
* 浏览器拿到的 content-type 才对得上。
*/
const SIGNATURES: [extension: string, bytes: number[]][] = [
[".png", [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]],
[".jpg", [0xff, 0xd8, 0xff]],
[".gif", [0x47, 0x49, 0x46, 0x38]], // GIF8(GIF87a / GIF89a)
[".bmp", [0x42, 0x4d]], // BM
]
export async function sniffImageExtension(file: Blob) {
const head = new Uint8Array(await file.slice(0, 8).arrayBuffer())
for (const [extension, bytes] of SIGNATURES) {
if (bytes.every((byte, index) => head[index] === byte)) return extension
}
return null
}