修复预览界面资源路径

将 /ui 资源映射到项目根 ui 目录

允许 legacy 布局安全访问 ui 目录并补 HTTP 响应回归测试
This commit is contained in:
2026-09-01 19:52:39 +08:00
parent 97ed504a88
commit 14de561408
@@ -1429,7 +1429,7 @@ pub(crate) fn resolve_preview_path(root: &Path, url_path: &str) -> Result<PathBu
if first.is_empty() || first == "." || first == ".." || first.contains('\\') {
return Err("预览路径非法".to_string());
}
if first == "game" || first == "assets" {
if first == "game" || first == "assets" || first == "ui" {
file_path.push(first);
} else {
// `/` serves the resolved game entry (project-root index.html or the
@@ -1492,7 +1492,7 @@ fn canonical_preview_path(root: &Path, file_path: &Path) -> Result<PathBuf, Stri
}
}
for segment in ["game", "assets"] {
for segment in ["game", "assets", "ui"] {
let allowed_dir = root.join(segment);
let metadata = match fs::symlink_metadata(&allowed_dir) {
Ok(metadata) => metadata,
@@ -1517,7 +1517,7 @@ fn canonical_preview_path(root: &Path, file_path: &Path) -> Result<PathBuf, Stri
return Ok(canonical_file);
}
}
Err("预览路径只能访问真实 game/ 或 assets/ 目录".to_string())
Err("预览路径只能访问真实 game/、assets/ 或 ui/ 目录".to_string())
}
fn percent_decode_path(path: &str) -> Option<String> {
@@ -1659,4 +1659,27 @@ mod tests {
assert!(resolve_preview_path(root.path(), "/.agent/secret.json").is_err());
assert!(resolve_preview_path(root.path(), "/memory/private.md").is_err());
}
#[test]
fn legacy_layout_serves_root_ui_modules() {
let root = tempfile::tempdir().expect("create preview root");
fs::create_dir_all(root.path().join("game")).expect("create game directory");
fs::create_dir_all(root.path().join("ui")).expect("create ui directory");
fs::write(root.path().join("game/index.html"), "<!doctype html>")
.expect("write game entry");
fs::write(root.path().join("ui/generated-x.js"), "export {};")
.expect("write generated module");
let expected = root
.path()
.join("ui/generated-x.js")
.canonicalize()
.expect("canonical generated module");
assert_eq!(resolve_preview_path(root.path(), "/ui/generated-x.js").unwrap(), expected);
let response = build_preview_response(root.path(), "GET", "/ui/generated-x.js");
let response_text = String::from_utf8_lossy(&response);
assert!(response_text.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response_text.contains("export {};"));
}
}