From 0c716d7d9c574ddd6b35aba5500e4df016e703b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 18:00:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A7=E7=89=A9=E6=96=87=E4=BB=B6=E5=90=8D?= =?UTF-8?q?=E4=B8=BB=E5=B9=B2=E5=8A=A0=E4=B8=8A=E9=95=BF=E5=BA=A6=E4=B8=8A?= =?UTF-8?q?=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server-rs/crates/platform-tripo/src/common/types.rs:sanitize_artifact_file_stem 在字符过滤后按 MAX_ARTIFACT_FILE_STEM_LEN=128 截断,远端来的超长主干不再拼出超过文件系统名字上限的一长串 - server-rs/crates/platform-tripo/src/common/types.rs:新建 types 的用例模块,锁住超长 / 带路径的主干被中性化并截断、全中性化后为空时退回固定名 --- .../crates/platform-tripo/src/common/types.rs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/server-rs/crates/platform-tripo/src/common/types.rs b/server-rs/crates/platform-tripo/src/common/types.rs index 20836123c..0600e6d36 100644 --- a/server-rs/crates/platform-tripo/src/common/types.rs +++ b/server-rs/crates/platform-tripo/src/common/types.rs @@ -16,6 +16,10 @@ use crate::{ /// 从远端地址推导出的扩展名上限;超出即按不可信处理,退回默认扩展名。 const MAX_ARTIFACT_FILE_EXTENSION_LEN: usize = 10; +/// 文件名主干上限。主干同样来自远端(`TripoArtifactBytes::filename` 是公开 API), +/// 不截断就可能拼出超过文件系统名字上限的一长串。 +const MAX_ARTIFACT_FILE_STEM_LEN: usize = 128; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct TripoTaskHandle { pub task_id: String, @@ -297,7 +301,7 @@ fn fallback_artifact_extension(content_type: Option<&str>) -> &'static str { } /// 文件名主干同样来自不可信输入:路径分隔符与 `..` 会让拼接结果逃出目标目录, -/// 这里统一中性化成安全字符。 +/// 这里统一中性化成安全字符,并截到文件系统友好的一段长度。 fn sanitize_artifact_file_stem(name: &str) -> String { let sanitized: String = name .chars() @@ -308,6 +312,7 @@ fn sanitize_artifact_file_stem(name: &str) -> String { '_' } }) + .take(MAX_ARTIFACT_FILE_STEM_LEN) .collect(); if sanitized.trim_matches('_').is_empty() { "artifact".to_string() @@ -315,3 +320,32 @@ fn sanitize_artifact_file_stem(name: &str) -> String { sanitized } } + +#[cfg(test)] +mod tests { + use super::*; + + fn artifact_url(path: &str) -> TripoUrl { + TripoUrl::parse(&format!("https://cdn.example.com{path}")).expect("夹具地址必须合法") + } + + #[test] + fn path_like_and_oversized_stems_are_neutralized_and_capped() { + let name = format!("{}../../etc/passwd", "a".repeat(400)); + let file_name = + artifact_filename(&name, Some("model/gltf-binary"), &artifact_url("/a.glb")); + + let stem = file_name.strip_suffix(".glb").expect("扩展名应取自 URL"); + assert_eq!(stem.chars().count(), MAX_ARTIFACT_FILE_STEM_LEN); + assert!(!stem.contains('/'), "路径分隔符必须被中性化:{stem}"); + assert!(!stem.contains('.'), "`..` 必须被中性化:{stem}"); + } + + #[test] + fn blank_stem_falls_back_to_a_fixed_name() { + assert_eq!( + artifact_filename("..", Some("model/gltf-binary"), &artifact_url("/a.glb")), + "artifact.glb" + ); + } +}