Compare commits
3 Commits
eed0b24ec3
...
ff2694c5a3
| Author | SHA1 | Date | |
|---|---|---|---|
| ff2694c5a3 | |||
| 933c7a8c93 | |||
| 0777fd469a |
14
.env.example
14
.env.example
@ -102,6 +102,20 @@ COMPRESSION_ALGORITHMS=gzip,brotli,deflate,zstd
|
|||||||
# - 原始上传:更改其文件路径;
|
# - 原始上传:更改其文件路径;
|
||||||
# - 处理变体:更改处理参数(查询串不同即视为新资源)。
|
# - 处理变体:更改处理参数(查询串不同即视为新资源)。
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 上传图片尺寸/像素上限(运行时环境变量,启动时读一次)
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 上传与动态处理(?w= 等)在 header 阶段统一校验(JPEG/PNG/WebP/GIF 同一路径),
|
||||||
|
# 超限直接 400 拒绝。这两个值同时也是图片解码的安全护栏(防超大图撑爆内存)。
|
||||||
|
# 只设下限防误调到危险小值,无上限(完全信任运维);低于下限会 clamp 并打 WARN。
|
||||||
|
# 注:读取侧 GET /uploads/* 的无参数取原图分支不查像素(只卡 20MB 字节)。
|
||||||
|
# 图片单边(宽或高)尺寸上限,单位像素。默认 8192,下限 512。
|
||||||
|
MAX_IMAGE_DIMENSION=8192
|
||||||
|
# 图片总像素上限。默认 50_000_000(约 7000×7000),下限 1_000_000。
|
||||||
|
# ⚠️ 此值决定单图解码内存缓冲(max_alloc = pixels × 4 + 1MB),
|
||||||
|
# 默认 50M 像素对应约 200MB/图;上调前确认部署环境内存与并发上传量。
|
||||||
|
MAX_IMAGE_PIXELS=50000000
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 图片磁盘缓存上限(uploads/.cache/,后台清理任务每小时扫描一次)
|
# 图片磁盘缓存上限(uploads/.cache/,后台清理任务每小时扫描一次)
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -35,6 +35,8 @@ Optional tuning via env vars (all have sane defaults):
|
|||||||
```
|
```
|
||||||
WEBP_QUALITY=85.0 # 0.0–100.0, clamped
|
WEBP_QUALITY=85.0 # 0.0–100.0, clamped
|
||||||
WEBP_METHOD=2 # 0–6, clamped
|
WEBP_METHOD=2 # 0–6, clamped
|
||||||
|
MAX_IMAGE_DIMENSION=8192 # max single side in px, min 512, no upper limit
|
||||||
|
MAX_IMAGE_PIXELS=50000000 # max total pixels (~7k×7k), min 1M, no upper limit
|
||||||
RATE_LIMIT_STRICT_PER_SEC=1
|
RATE_LIMIT_STRICT_PER_SEC=1
|
||||||
RATE_LIMIT_STRICT_BURST=5
|
RATE_LIMIT_STRICT_BURST=5
|
||||||
RATE_LIMIT_UPLOAD_PER_SEC=2
|
RATE_LIMIT_UPLOAD_PER_SEC=2
|
||||||
|
|||||||
219
src/api/image.rs
219
src/api/image.rs
@ -44,12 +44,70 @@ fn etag_matches(if_none_match: &str, etag: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub const MAX_IMAGE_DIMENSION: u32 = 4096;
|
/// 图片单边(宽或高)尺寸上限,单位像素。
|
||||||
|
///
|
||||||
|
/// 启动时从 `MAX_IMAGE_DIMENSION` 环境变量读取,默认 8192。
|
||||||
|
/// 只设下限 512(防误调到危险小值导致正常图都传不上),无上限(完全信任运维)。
|
||||||
|
/// 低于下限时 clamp 回 512 并打 WARN。
|
||||||
|
pub static MAX_IMAGE_DIMENSION: LazyLock<u32> = LazyLock::new(|| {
|
||||||
|
const DEFAULT: u32 = 8192;
|
||||||
|
const MIN: u32 = 512;
|
||||||
|
let (val, clamped) = std::env::var("MAX_IMAGE_DIMENSION")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
|
.map(|v| {
|
||||||
|
if v < MIN {
|
||||||
|
(MIN, true)
|
||||||
|
} else {
|
||||||
|
(v, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or((DEFAULT, false));
|
||||||
|
if clamped {
|
||||||
|
tracing::warn!(
|
||||||
|
"MAX_IMAGE_DIMENSION was clamped from {} to {} (minimum {})",
|
||||||
|
std::env::var("MAX_IMAGE_DIMENSION").unwrap_or_default(),
|
||||||
|
val,
|
||||||
|
MIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tracing::info!("Image dimension limit loaded: {}", val);
|
||||||
|
val
|
||||||
|
});
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
const DEFAULT_JPEG_QUALITY: u8 = 85;
|
const DEFAULT_JPEG_QUALITY: u8 = 85;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
/// 允许处理的最大图片像素数(约 5k x 5k)。
|
/// 允许处理的最大图片像素数(默认约 7k x 7k)。
|
||||||
pub const MAX_IMAGE_PIXELS: u32 = 25_000_000; // ~5k x 5k
|
///
|
||||||
|
/// 启动时从 `MAX_IMAGE_PIXELS` 环境变量读取,默认 50_000_000。
|
||||||
|
/// 只设下限 1_000_000(防误调),无上限。
|
||||||
|
/// ⚠️ 此值同时决定单图解码内存缓冲(max_alloc = pixels × 4 + 1MB),
|
||||||
|
/// 默认 50M 像素对应约 200MB/图,上调前确认部署环境内存。
|
||||||
|
pub static MAX_IMAGE_PIXELS: LazyLock<u32> = LazyLock::new(|| {
|
||||||
|
const DEFAULT: u32 = 50_000_000;
|
||||||
|
const MIN: u32 = 1_000_000;
|
||||||
|
let (val, clamped) = std::env::var("MAX_IMAGE_PIXELS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
|
.map(|v| {
|
||||||
|
if v < MIN {
|
||||||
|
(MIN, true)
|
||||||
|
} else {
|
||||||
|
(v, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or((DEFAULT, false));
|
||||||
|
if clamped {
|
||||||
|
tracing::warn!(
|
||||||
|
"MAX_IMAGE_PIXELS was clamped from {} to {} (minimum {})",
|
||||||
|
std::env::var("MAX_IMAGE_PIXELS").unwrap_or_default(),
|
||||||
|
val,
|
||||||
|
MIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tracing::info!("Image pixel limit loaded: {}", val);
|
||||||
|
val
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -122,12 +180,12 @@ impl ImageParams {
|
|||||||
/// 校验参数合法性,返回 HTTP 400 状态码表示非法。
|
/// 校验参数合法性,返回 HTTP 400 状态码表示非法。
|
||||||
fn validate(&self) -> Result<(), StatusCode> {
|
fn validate(&self) -> Result<(), StatusCode> {
|
||||||
if let Some(dim) = self.w {
|
if let Some(dim) = self.w {
|
||||||
if dim == 0 || dim > MAX_IMAGE_DIMENSION {
|
if dim == 0 || dim > *MAX_IMAGE_DIMENSION {
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(dim) = self.h {
|
if let Some(dim) = self.h {
|
||||||
if dim == 0 || dim > MAX_IMAGE_DIMENSION {
|
if dim == 0 || dim > *MAX_IMAGE_DIMENSION {
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +206,7 @@ impl ImageParams {
|
|||||||
}
|
}
|
||||||
let tw: u32 = parts[0].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
let tw: u32 = parts[0].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
let th: u32 = parts[1].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
let th: u32 = parts[1].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
if tw == 0 || th == 0 || tw > MAX_IMAGE_DIMENSION || th > MAX_IMAGE_DIMENSION {
|
if tw == 0 || th == 0 || tw > *MAX_IMAGE_DIMENSION || th > *MAX_IMAGE_DIMENSION {
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,25 +299,88 @@ fn check_image_dimensions(width: u32, height: u32) -> Result<(), StatusCode> {
|
|||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
let pixels = u64::from(width) * u64::from(height);
|
let pixels = u64::from(width) * u64::from(height);
|
||||||
if pixels > u64::from(MAX_IMAGE_PIXELS) {
|
if pixels > u64::from(*MAX_IMAGE_PIXELS) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Image dimensions too large: {}x{} ({} pixels, max {})",
|
"Image dimensions too large: {}x{} ({} pixels, max {})",
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels,
|
pixels,
|
||||||
MAX_IMAGE_PIXELS
|
*MAX_IMAGE_PIXELS
|
||||||
);
|
);
|
||||||
return Err(StatusCode::PAYLOAD_TOO_LARGE);
|
return Err(StatusCode::PAYLOAD_TOO_LARGE);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// 仅读取 header 校验上传图片的尺寸/像素是否超限。
|
||||||
|
///
|
||||||
|
/// 与 `check_image_dimensions` 的区别:
|
||||||
|
/// - 输入是原始字节 + MIME,内部按 MIME 分发只解析 header 拿尺寸(不解码像素)
|
||||||
|
/// - 返回带友好中文提示的 `&'static str`(供上传接口直接回给用户)
|
||||||
|
///
|
||||||
|
/// 上传入口三种格式在此统一拦截至尺寸上限;WebP 走 `zenwebp` header,
|
||||||
|
/// JPEG/PNG/GIF 走 `image` crate 的 `into_dimensions`(均只读 header)。
|
||||||
|
pub fn check_upload_dimensions(data: &[u8], mime_type: &str) -> Result<(), &'static str> {
|
||||||
|
let dims = read_dimensions_by_mime(data, mime_type)?;
|
||||||
|
let (width, height) = dims;
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Err("图片文件损坏或格式不正确");
|
||||||
|
}
|
||||||
|
let pixels = u64::from(width) * u64::from(height);
|
||||||
|
let max_dim = *MAX_IMAGE_DIMENSION;
|
||||||
|
let max_pixels = *MAX_IMAGE_PIXELS;
|
||||||
|
if width > max_dim || height > max_dim || pixels > u64::from(max_pixels) {
|
||||||
|
tracing::warn!(
|
||||||
|
"Uploaded image too large: {}x{} ({} pixels, max {}x{} / {} pixels)",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixels,
|
||||||
|
max_dim,
|
||||||
|
max_dim,
|
||||||
|
max_pixels
|
||||||
|
);
|
||||||
|
return Err("图片尺寸过大,请压缩后再上传");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
/// 按 MIME 只读 header 拿 (width, height)。失败返回损坏错误。
|
||||||
|
fn read_dimensions_by_mime(
|
||||||
|
data: &[u8],
|
||||||
|
mime_type: &str,
|
||||||
|
) -> Result<(u32, u32), &'static str> {
|
||||||
|
match mime_type {
|
||||||
|
"image/webp" => {
|
||||||
|
// zenwebp 的 WebPDecoder::build 只解析 RIFF header,不解码像素(与 webp::decode 同源)。
|
||||||
|
let decoder = zenwebp::WebPDecoder::build(data)
|
||||||
|
.map_err(|_| "图片文件损坏或格式不正确")?;
|
||||||
|
let info = decoder.info();
|
||||||
|
Ok((info.width, info.height))
|
||||||
|
}
|
||||||
|
"image/jpeg" | "image/png" | "image/gif" => {
|
||||||
|
let format = match mime_type {
|
||||||
|
"image/jpeg" => image::ImageFormat::Jpeg,
|
||||||
|
"image/png" => image::ImageFormat::Png,
|
||||||
|
_ => image::ImageFormat::Gif,
|
||||||
|
};
|
||||||
|
let reader = image::ImageReader::with_format(std::io::Cursor::new(data), format);
|
||||||
|
reader
|
||||||
|
.into_dimensions()
|
||||||
|
.map_err(|_| "图片文件损坏或格式不正确")
|
||||||
|
}
|
||||||
|
// 上游已用 ALLOWED_MIME_TYPES 白名单拦截,理论不到这里
|
||||||
|
_ => Err("图片文件损坏或格式不正确"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn image_reader_limits() -> image::Limits {
|
fn image_reader_limits() -> image::Limits {
|
||||||
let mut limits = image::Limits::default();
|
let mut limits = image::Limits::default();
|
||||||
limits.max_image_width = Some(MAX_IMAGE_DIMENSION);
|
limits.max_image_width = Some(*MAX_IMAGE_DIMENSION);
|
||||||
limits.max_image_height = Some(MAX_IMAGE_DIMENSION);
|
limits.max_image_height = Some(*MAX_IMAGE_DIMENSION);
|
||||||
limits.max_alloc = Some(MAX_IMAGE_PIXELS as u64 * 4 + 1024 * 1024);
|
limits.max_alloc = Some(*MAX_IMAGE_PIXELS as u64 * 4 + 1024 * 1024);
|
||||||
limits
|
limits
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +418,7 @@ fn process_image(
|
|||||||
if parts.len() == 2 {
|
if parts.len() == 2 {
|
||||||
let tw: u32 = parts[0].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
let tw: u32 = parts[0].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
let th: u32 = parts[1].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
let th: u32 = parts[1].parse().map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
if tw > 0 && th > 0 && tw <= MAX_IMAGE_DIMENSION && th <= MAX_IMAGE_DIMENSION {
|
if tw > 0 && th > 0 && tw <= *MAX_IMAGE_DIMENSION && th <= *MAX_IMAGE_DIMENSION {
|
||||||
img = img.thumbnail(tw, th);
|
img = img.thumbnail(tw, th);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -644,6 +765,78 @@ mod tests {
|
|||||||
assert_eq!(dims, None);
|
assert_eq!(dims, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// —— check_upload_dimensions:统一上传尺寸/像素上限校验 ——
|
||||||
|
|
||||||
|
/// 构造指定尺寸的 PNG 字节(内存占用 = 尺寸,仅用于 header 校验测试)。
|
||||||
|
fn make_png_bytes(w: u32, h: u32) -> Vec<u8> {
|
||||||
|
let img = image::DynamicImage::new_rgb8(w, h);
|
||||||
|
let mut buf = std::io::Cursor::new(Vec::new());
|
||||||
|
img.write_to(&mut buf, image::ImageFormat::Png).unwrap();
|
||||||
|
buf.into_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_accepts_small_png() {
|
||||||
|
let data = make_png_bytes(100, 100);
|
||||||
|
assert!(check_upload_dimensions(&data, "image/png").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_accepts_boundary_png() {
|
||||||
|
// 用 7000×7000(≈49M 像素):单边 7000 < 默认 8192 上限,且总像素 < 默认 50M 上限,应放行。
|
||||||
|
// 注意不能直接用 *MAX_IMAGE_DIMENSION 做正方形边——8192²≈67M 会触发像素上限拒绝。
|
||||||
|
let data = make_png_bytes(7000, 7000);
|
||||||
|
assert!(check_upload_dimensions(&data, "image/png").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_rejects_oversized_width() {
|
||||||
|
// 单边超限:(上限+1)×1,像素远低于上限,但单边越界
|
||||||
|
let data = make_png_bytes(*MAX_IMAGE_DIMENSION + 1, 1);
|
||||||
|
let err = check_upload_dimensions(&data, "image/png").unwrap_err();
|
||||||
|
assert!(err.contains("尺寸过大"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_rejects_oversized_height() {
|
||||||
|
let data = make_png_bytes(1, *MAX_IMAGE_DIMENSION + 1);
|
||||||
|
let err = check_upload_dimensions(&data, "image/png").unwrap_err();
|
||||||
|
assert!(err.contains("尺寸过大"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_accepts_small_webp() {
|
||||||
|
let img = image::DynamicImage::new_rgb8(64, 48);
|
||||||
|
let webp_bytes = crate::webp::encode(&img, 85.0, 2).unwrap();
|
||||||
|
assert!(check_upload_dimensions(&webp_bytes, "image/webp").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_accepts_gif() {
|
||||||
|
// image crate 默认启用 gif feature,into_dimensions 可读 GIF header
|
||||||
|
let img = image::DynamicImage::new_rgb8(32, 32);
|
||||||
|
let mut buf = std::io::Cursor::new(Vec::new());
|
||||||
|
img.write_to(&mut buf, image::ImageFormat::Gif).unwrap();
|
||||||
|
assert!(check_upload_dimensions(&buf.into_inner(), "image/gif").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_upload_dimensions_rejects_corrupt_bytes() {
|
||||||
|
// 非 magic bytes,与现有损坏文件校验文案对齐
|
||||||
|
let err = check_upload_dimensions(b"not an image at all", "image/png").unwrap_err();
|
||||||
|
assert_eq!(err, "图片文件损坏或格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_dimensions_by_mime_dispatches_webp() {
|
||||||
|
let img = image::DynamicImage::new_rgb8(16, 9);
|
||||||
|
let webp_bytes = crate::webp::encode(&img, 85.0, 2).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
read_dimensions_by_mime(&webp_bytes, "image/webp").unwrap(),
|
||||||
|
(16, 9)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn image_params_validate_valid_defaults() {
|
fn image_params_validate_valid_defaults() {
|
||||||
let params = ImageParams::default();
|
let params = ImageParams::default();
|
||||||
@ -671,7 +864,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn image_params_validate_oversized_width_rejected() {
|
fn image_params_validate_oversized_width_rejected() {
|
||||||
let params = ImageParams {
|
let params = ImageParams {
|
||||||
w: Some(5000),
|
w: Some(*MAX_IMAGE_DIMENSION + 1),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(params.validate().is_err());
|
assert!(params.validate().is_err());
|
||||||
|
|||||||
@ -208,6 +208,19 @@ pub async fn upload_image(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 仅读 header 统一校验尺寸/像素上限。三种格式走同一路径:
|
||||||
|
// JPEG/PNG/GIF 用 image crate 的 into_dimensions,WebP 用 zenwebp header。
|
||||||
|
// 超限直接拒绝,避免大图走 decode 后被静默降级(原 fallback 存原图)。
|
||||||
|
if let Err(msg) = crate::api::image::check_upload_dimensions(&data, mime_type.as_str()) {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(json!({
|
||||||
|
"success": false,
|
||||||
|
"error": msg
|
||||||
|
})),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let is_gif = mime_type.as_str() == "image/gif";
|
let is_gif = mime_type.as_str() == "image/gif";
|
||||||
let is_webp = mime_type.as_str() == "image/webp";
|
let is_webp = mime_type.as_str() == "image/webp";
|
||||||
|
|
||||||
@ -260,9 +273,9 @@ pub async fn upload_image(
|
|||||||
};
|
};
|
||||||
let mut reader = image::ImageReader::with_format(cursor, format);
|
let mut reader = image::ImageReader::with_format(cursor, format);
|
||||||
let mut limits = image::Limits::default();
|
let mut limits = image::Limits::default();
|
||||||
limits.max_image_width = Some(crate::api::image::MAX_IMAGE_DIMENSION);
|
limits.max_image_width = Some(*crate::api::image::MAX_IMAGE_DIMENSION);
|
||||||
limits.max_image_height = Some(crate::api::image::MAX_IMAGE_DIMENSION);
|
limits.max_image_height = Some(*crate::api::image::MAX_IMAGE_DIMENSION);
|
||||||
limits.max_alloc = Some(crate::api::image::MAX_IMAGE_PIXELS as u64 * 4 + 1024 * 1024);
|
limits.max_alloc = Some(*crate::api::image::MAX_IMAGE_PIXELS as u64 * 4 + 1024 * 1024);
|
||||||
reader.limits(limits);
|
reader.limits(limits);
|
||||||
|
|
||||||
match reader.decode() {
|
match reader.decode() {
|
||||||
@ -299,8 +312,9 @@ pub async fn upload_image(
|
|||||||
};
|
};
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
tracing::warn!("Failed to decode image, keeping original format");
|
// 到这里尺寸校验已通过(超限在 header 阶段被拒),decode 失败只能是真损坏。
|
||||||
|
tracing::warn!("Failed to decode image ({}), keeping original format", e);
|
||||||
(original_data, mime_to_ext(&mime).to_string(), false)
|
(original_data, mime_to_ext(&mime).to_string(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,7 +148,7 @@ pub fn decode(data: &[u8]) -> Result<image::DynamicImage, WebpError> {
|
|||||||
let pixel_count = (width as u64) * (height as u64);
|
let pixel_count = (width as u64) * (height as u64);
|
||||||
|
|
||||||
// 超过最大允许像素数时提前拒绝
|
// 超过最大允许像素数时提前拒绝
|
||||||
if pixel_count > crate::api::image::MAX_IMAGE_PIXELS as u64 {
|
if pixel_count > *crate::api::image::MAX_IMAGE_PIXELS as u64 {
|
||||||
return Err(WebpError::Decode(format!(
|
return Err(WebpError::Decode(format!(
|
||||||
"Image dimensions {}x{} exceed maximum allowed pixels",
|
"Image dimensions {}x{} exceed maximum allowed pixels",
|
||||||
width, height
|
width, height
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user