fix(library): reject reserved names (cache, .uploads) with 400 reserved_name (B8)

This commit is contained in:
2026-09-14 19:42:52 +08:00
parent 0e62d4aa18
commit 0c5dfd4353
3 changed files with 38 additions and 0 deletions
+6
View File
@@ -15,6 +15,7 @@ import (
"github.com/jackc/pgx/v5"
"booklib/internal/bookfile"
"booklib/internal/media"
"booklib/internal/store"
)
@@ -61,6 +62,11 @@ func (h *H) CreateLibrary(c *gin.Context) {
err(c, http.StatusBadRequest, "bad_request", "bad name")
return
}
// B8: reject reserved names that conflict with system directories.
if media.IsReservedName(safe) {
err(c, http.StatusBadRequest, "bad_request", "reserved_name")
return
}
root := filepath.Join(filepath.Clean(h.cfg.BooksDir), safe)
id, e := h.st.CreateLibrary(c, req.Name, root)
if e != nil {
+16
View File
@@ -0,0 +1,16 @@
package media
import "strings"
// reservedNames are library names that conflict with system directories.
// This is the single source of truth for reserved name validation (B8).
var reservedNames = map[string]bool{
"cache": true, // CACHE_DIR
".uploads": true, // upload session directory
".trash": true, // potential future use
}
// IsReservedName reports whether name conflicts with system directories.
func IsReservedName(name string) bool {
return reservedNames[strings.ToLower(name)]
}
+16
View File
@@ -0,0 +1,16 @@
package media
import "testing"
func TestIsReservedName(t *testing.T) {
for _, n := range []string{"cache", "Cache", "CACHE", ".uploads", ".Uploads", ".trash"} {
if !IsReservedName(n) {
t.Errorf("IsReservedName(%q) = false, want true", n)
}
}
for _, n := range []string{"comics", "books", "my-library", "Cache1"} {
if IsReservedName(n) {
t.Errorf("IsReservedName(%q) = true, want false", n)
}
}
}