Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
82 lines
3.1 KiB
Go
82 lines
3.1 KiB
Go
// Package imagecheck 提供图片尺寸校验,用于在上传/解码前拦截"解压炸弹"。
|
||
package imagecheck
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"image"
|
||
_ "image/gif"
|
||
_ "image/jpeg"
|
||
_ "image/png"
|
||
"io"
|
||
"math"
|
||
)
|
||
|
||
// maxImageSide 是单边最大像素,MaxImagePixels 为其平方。
|
||
const maxImageSide = 4096
|
||
|
||
// MaxImageSide 导出单边最大像素,供上层在错误提示中向用户展示具体限制。
|
||
const MaxImageSide = maxImageSide
|
||
|
||
// MaxImagePixels 限制解码后的最大像素总量(4096×4096),用于拦截"解压炸弹":
|
||
// 超大纯色 PNG/GIF 压缩后体积很小,但 image.Decode 会按宽×高分配内存导致 OOM。
|
||
const MaxImagePixels = maxImageSide * maxImageSide
|
||
|
||
// ErrImageTooLarge 表示图片像素总量超过 MaxImagePixels,
|
||
// 是所有"图片尺寸超限"的统一错误,供各上传入口识别并展示统一文案。
|
||
var ErrImageTooLarge = errors.New(fmt.Sprintf(
|
||
"图片尺寸过大,最大支持 %d×%d 像素,请压缩后重新上传",
|
||
maxImageSide, maxImageSide,
|
||
))
|
||
|
||
// CheckPixelLimit 仅读取图片头部(image.DecodeConfig)校验宽高与像素总量,
|
||
// 在完整解码前拦截超大图片,避免内存耗尽。
|
||
//
|
||
// 说明:对 GIF 同样适用——image.DecodeConfig 对 GIF 只读取逻辑屏幕宽高(不解码帧),
|
||
// 成本极低;但大逻辑屏幕的 GIF 在后续 image.Decode 时仍会按屏幕尺寸分配内存,故必须校验。
|
||
//
|
||
// 注意:该方法会读取(消耗)传入的 reader,调用方需自行重建/复位 reader。
|
||
func CheckPixelLimit(r io.Reader) error {
|
||
cfg, _, err := image.DecodeConfig(r)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if cfg.Width <= 0 || cfg.Height <= 0 {
|
||
return nil
|
||
}
|
||
if int64(cfg.Width)*int64(cfg.Height) > MaxImagePixels {
|
||
return ErrImageTooLarge
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ErrBadAspectRatio 表示图片宽高比不符合用途要求(如头像必须 1:1、背景必须 16:9)。
|
||
// 由 storage 在头像/背景上传入口校验后返回,route 层负责映射为对应语言的提示文案。
|
||
var ErrBadAspectRatio = errors.New("image aspect ratio does not meet the requirement")
|
||
|
||
// DefaultAspectTolerance 是宽高比允许的绝对偏差。
|
||
// 客户端裁剪输出即为精确比例,此处仅用于拦截绕过裁剪直传的明显不符图片,
|
||
// 因此使用窄容差(±0.01);宽高比越界(如 4:3 传成 16:9)会稳定触发拒绝。
|
||
const DefaultAspectTolerance = 0.01
|
||
|
||
// CheckAspectRatio 仅读取图片头部校验宽高比是否接近 wantW:wantH(绝对偏差 ≤ tolerance)。
|
||
// 与 CheckPixelLimit 一致:只做 DecodeConfig 级轻量检查,不解码全图。
|
||
// 注意:该方法会读取(消耗)传入的 reader,调用方需自行重建/复位 reader。
|
||
func CheckAspectRatio(r io.Reader, wantW, wantH float64, tolerance float64) error {
|
||
if wantW <= 0 || wantH <= 0 {
|
||
return nil
|
||
}
|
||
cfg, _, err := image.DecodeConfig(r)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if cfg.Width <= 0 || cfg.Height <= 0 {
|
||
return nil
|
||
}
|
||
actual := float64(cfg.Width) / float64(cfg.Height)
|
||
if math.Abs(actual-wantW/wantH) > tolerance {
|
||
return ErrBadAspectRatio
|
||
}
|
||
return nil
|
||
}
|