Files
tamabox/internal/imagecheck/imagecheck.go
T
2026-10-16 09:39:30 +08:00

89 lines
3.4 KiB
Go
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.
// 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 {
cfg, _, err := image.DecodeConfig(r)
if err != nil {
return err
}
return CheckAspectRatioSize(cfg.Width, cfg.Height, wantW, wantH, tolerance)
}
// CheckAspectRatioSize 是 CheckAspectRatio 的「宽高已知」版本。
// 调用方若已从文件头拿到尺寸(或需要先按 EXIF Orientation 修正宽高后再比对),
// 用本函数可避免重复读取文件头。
func CheckAspectRatioSize(w, h int, wantW, wantH float64, tolerance float64) error {
if wantW <= 0 || wantH <= 0 {
return nil
}
if w <= 0 || h <= 0 {
return nil
}
actual := float64(w) / float64(h)
if math.Abs(actual-wantW/wantH) > tolerance {
return ErrBadAspectRatio
}
return nil
}