fix(image): reject undecodable images with 422; cap raw file size at 20MB

- decode 失败(WebP 与其他格式)不再降级返回原始字节,防止构造的畸形文件以
  图片 content-type 返回任意内容(M3)
- 原始分支读前查 metadata,超 20MB 返回 413,避免超大文件撑爆内存
This commit is contained in:
xfy 2026-06-18 13:38:03 +08:00
parent 22e883c6d9
commit 7a6e9350fe

View File

@ -350,9 +350,10 @@ fn process_image_blocking(
img img
} }
Err(e) => { Err(e) => {
tracing::warn!("WebP decode failed ({}), returning raw bytes", e); // decode 失败不再降级返回原始字节(可能是构造的畸形文件,配合 nosniff
let ct = content_type(original_format); // 构成内容混淆面),直接报错让上层返回 422M3
return Ok((data, ct)); tracing::warn!("WebP decode failed ({}), rejecting", e);
return Err(StatusCode::UNPROCESSABLE_ENTITY);
} }
} }
} else { } else {
@ -362,9 +363,8 @@ fn process_image_blocking(
match reader.decode() { match reader.decode() {
Ok(img) => img, Ok(img) => img,
Err(e) => { Err(e) => {
tracing::warn!("Image decode failed ({}), returning raw bytes", e); tracing::warn!("Image decode failed ({}), rejecting", e);
let ct = content_type(original_format); return Err(StatusCode::UNPROCESSABLE_ENTITY);
return Ok((data, ct));
} }
} }
}; };
@ -465,11 +465,18 @@ pub async fn serve_image(
// No processing params: return raw file with long-lived cache headers. // No processing params: return raw file with long-lived cache headers.
if params.is_empty() { if params.is_empty() {
return match tokio::fs::read(&file_path).await { // 原始分支也限制大小避免读取超大文件撑爆内存M3。上限 20MB
Ok(data) => { // 覆盖正常上传图(上传侧 MAX_FILE_SIZE=5MB拒绝异常大文件。
let ct = content_type(detect_format(&path)); const MAX_RAW_BYTES: u64 = 20 * 1024 * 1024;
image_response(Bytes::from(data), ct, "public, max-age=31536000, immutable", &headers) return match tokio::fs::metadata(&file_path).await {
} Ok(meta) if meta.len() > MAX_RAW_BYTES => StatusCode::PAYLOAD_TOO_LARGE.into_response(),
Ok(_) => match tokio::fs::read(&file_path).await {
Ok(data) => {
let ct = content_type(detect_format(&path));
image_response(Bytes::from(data), ct, "public, max-age=31536000, immutable", &headers)
}
Err(_) => StatusCode::NOT_FOUND.into_response(),
},
Err(_) => StatusCode::NOT_FOUND.into_response(), Err(_) => StatusCode::NOT_FOUND.into_response(),
}; };
} }