3D 产物落盘文件名补上安全处理

- URL 无可用扩展名时按 content type 兜底,预览图不再写成 .glb
- 文件名主干中性化路径分隔符与遍历片段,避免逃出目标目录
This commit is contained in:
2026-09-22 13:50:29 +08:00
parent 7dd7bdd3c7
commit fd0a37969e
@@ -197,7 +197,42 @@ impl TripoDownloadedArtifact {
&& extension.len() <= MAX_ARTIFACT_FILE_EXTENSION_LEN
&& extension.bytes().all(|byte| byte.is_ascii_alphanumeric())
})
.unwrap_or("glb");
format!("{name}.{extension}")
.unwrap_or_else(|| fallback_artifact_extension(self.content_type.as_deref()));
format!("{}.{extension}", sanitize_artifact_file_stem(name))
}
}
/// URL 路径给不出可用扩展名时按 content type 兜底:同一条下载路径也服务预览图,
/// 一律写 `.glb` 会把 PNG / JPEG 产物写成模型文件名。
fn fallback_artifact_extension(content_type: Option<&str>) -> &'static str {
let content_type = content_type.unwrap_or_default();
if content_type.starts_with("image/png") {
"png"
} else if content_type.starts_with("image/jpeg") {
"jpg"
} else if content_type.starts_with("image/webp") {
"webp"
} else {
"glb"
}
}
/// 文件名主干同样来自不可信输入:路径分隔符与 `..` 会让拼接结果逃出目标目录,
/// 这里统一中性化成安全字符。
fn sanitize_artifact_file_stem(name: &str) -> String {
let sanitized: String = name
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_') {
ch
} else {
'_'
}
})
.collect();
if sanitized.trim_matches('_').is_empty() {
"artifact".to_string()
} else {
sanitized
}
}