feat(libs): create library by name only; root_path derived server-side as BOOKS_DIR/SafeName(name), validated at creation; drop root_path input from admin UI; align changelogs to bilingual template; trim AGENTS.md to rules not derivable from repo layout

This commit is contained in:
2026-09-07 21:56:59 +08:00
parent ca64bc0d73
commit 45eefd6741
9 changed files with 80 additions and 146 deletions
+18 -6
View File
@@ -14,23 +14,35 @@ import (
func TestLibraryCreateListUpload(t *testing.T) {
_, _, h, booksDir := setupAPI(t)
tok := adminToken(t, h)
root := filepath.Join(booksDir, "lib1") // 必须落在解析过软链的 booksDir 内
os.MkdirAll(root, 0o755)
w := do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics", "root_path": root})
root := filepath.Join(booksDir, "comics") // 服务端自动拼接 BooksDir/<清洗后的库名>
w := do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics"})
if w.Code != 201 {
t.Fatalf("create lib %d %s", w.Code, w.Body)
}
var lib map[string]any
json.Unmarshal(w.Body.Bytes(), &lib)
if lib["root_path"] != root {
t.Fatalf("root_path want %q got %v", root, lib["root_path"])
}
libID := itoa(lib["id"])
w = do(h, "GET", "/api/libraries", tok, nil)
if !strings.Contains(w.Body.String(), `"comics"`) {
t.Fatalf("list: %s", w.Body)
}
// 相对路径 root 必须 400(前缀校验的根)
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "x", "root_path": "relative/path"})
// 恶意库名必须清洗,root 仍在 booksDir 内
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "../../etc/passwd"})
if w.Code != 201 || !strings.Contains(w.Body.String(), filepath.Join(booksDir, "passwd")) {
t.Fatalf("traversal name want sanitized 201 got %d %s", w.Code, w.Body)
}
// "." / ".." 清洗后非法 → 400
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": ".."})
if w.Code != 400 {
t.Fatalf("relative root want 400 got %d", w.Code)
t.Fatalf("'..' want 400 got %d", w.Code)
}
// 重名(同 root)→ 409
w = do(h, "POST", "/api/libraries", tok, map[string]string{"name": "comics"})
if w.Code != 409 {
t.Fatalf("dup want 409 got %d", w.Code)
}
// 上传:白名单 + 防穿越 + 原子落盘
body, mw := uploadBody("my 01.cbz", []byte("zipbytes"))