diff --git a/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs b/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs index f8e86833f..207efb87d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs @@ -1062,9 +1062,18 @@ fn image_content_type(relative_path: &str) -> Result<&'static str, String> { } } +/// 单张发布图片的读取上限,与服务端 `MAX_SINGLE_MEDIA_BYTES` 保持一致:先看体积再读, +/// 避免草稿指向一个超大本地文件时先把宿主内存顶爆、再由服务端事后拒绝。 +const MAX_DRAFT_MEDIA_BYTES: u64 = 8 * 1024 * 1024; + /// 读一张草稿声明的项目内图片:路径先过宿主既有门禁(拒符号链接/越界),再读原始字节。 fn read_draft_image(root: &Path, relative_path: &str) -> Result { let absolute = crate::project::resolve_local_project_path(root, relative_path)?; + let metadata = std::fs::metadata(&absolute) + .map_err(|error| format!("读取发布图片「{relative_path}」失败:{error}"))?; + if metadata.len() > MAX_DRAFT_MEDIA_BYTES { + return Err(format!("发布图片「{relative_path}」超过 8MB 大小限制")); + } let bytes = std::fs::read(&absolute) .map_err(|error| format!("读取发布图片「{relative_path}」失败:{error}"))?; if bytes.is_empty() { @@ -1899,6 +1908,17 @@ mod tests { } } + #[test] + fn read_draft_image_rejects_oversized_files_before_reading() { + let root = fixture_root("oversized"); + let file = std::fs::File::create(root.join("huge.png")).unwrap(); + file.set_len(MAX_DRAFT_MEDIA_BYTES + 1).unwrap(); + drop(file); + + let error = read_draft_image(&root, "huge.png").expect_err("must reject oversized media"); + assert!(error.contains("超过 8MB"), "{error}"); + } + #[test] fn resolve_publish_media_reads_local_cover_and_screenshots_from_the_draft() { let root = fixture_root("local");