From 85c61d9f2444dcc1c6b85b4fa603adad3a6c8a50 Mon Sep 17 00:00:00 2001 From: XingfenD Date: Mon, 14 Sep 2026 23:26:16 +0800 Subject: [PATCH] refactor: consolidate isUnique and path validation helpers (Task 28) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - seed.Admin: hand-rolled pgconn.PgError 23505 check → store.IsUniqueViolation (single predicate for unique violations across the codebase) - books.absBookPath: local hasPrefixDir (filepath.Rel-based) removed in favor of bookfile.Contains, which also resolves symlinks — stricter escape check - handlers isUnique alias + libraries.go local prefix check were already folded into ports.IsUniqueViolation / bookfile.Contains in Task 25; scanner.inside was folded in Task 24 — this commit closes the last two Full gate green: gofmt, vet, go test -p 1 (real PG+Redis, 0 skip) --- backend/cmd/webui/handlers/books.go | 11 +++-------- backend/internal/seed/seed.go | 6 +----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/backend/cmd/webui/handlers/books.go b/backend/cmd/webui/handlers/books.go index 04531dc..b23c237 100644 --- a/backend/cmd/webui/handlers/books.go +++ b/backend/cmd/webui/handlers/books.go @@ -7,7 +7,6 @@ import ( "os" "path/filepath" "strconv" - "strings" "time" "github.com/gin-gonic/gin" @@ -38,20 +37,16 @@ func (h *H) bookFromParam(c *gin.Context) (store.Book, bool) { return h.getBookRow(c, id) } -// absBookPath: books.path 永远相对且不含 ..;拼接后二次前缀校验(纵深防御) +// absBookPath: books.path 永远相对且不含 ..;拼接后二次前缀校验(纵深防御)。 +// bookfile.Contains 带 EvalSymlinks,比裸 filepath.Rel 更能拦住软链逃逸。 func absBookPath(root string, b store.Book) (string, error) { abs := filepath.Join(root, filepath.FromSlash(b.Path)) - if filepath.Clean(abs) != abs || !hasPrefixDir(abs, root) { + if filepath.Clean(abs) != abs || !bookfile.Contains(root, abs) { return "", os.ErrPermission } return abs, nil } -func hasPrefixDir(p, dir string) bool { - rel, err := filepath.Rel(filepath.Clean(dir), filepath.Clean(p)) - return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) -} - func bookJSON(b store.Book, percent float64, libraryName string) gin.H { h := bookfile.Hash(b.FileSize, b.ModTS) j := gin.H{ diff --git a/backend/internal/seed/seed.go b/backend/internal/seed/seed.go index 168f2b0..1cd4418 100644 --- a/backend/internal/seed/seed.go +++ b/backend/internal/seed/seed.go @@ -2,11 +2,8 @@ package seed import ( "context" - "errors" "log" - "github.com/jackc/pgx/v5/pgconn" - "booklib/internal/auth" "booklib/internal/store" ) @@ -28,8 +25,7 @@ func Admin(ctx context.Context, s *store.Store, user, pass string) error { return err } if _, err := s.CreateUser(ctx, user, h, "admin"); err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == "23505" { + if store.IsUniqueViolation(err) { log.Printf("seed admin %q may already exist: %v", user, err) return nil }