产物文件名主干加上长度上限

- 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 的用例模块,锁住超长 / 带路径的主干被中性化并截断、全中性化后为空时退回固定名
This commit is contained in:
2026-09-24 18:00:05 +08:00
parent 2330a25172
commit 0c716d7d9c
@@ -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"
);
}
}