fix: address code review findings

This commit is contained in:
2026-07-20 00:05:22 +03:00
parent ce1c39e14b
commit e0211343e0
7 changed files with 159 additions and 53 deletions

View File

@@ -7,12 +7,25 @@ import (
"fmt"
"html/template"
"net/http"
"regexp"
"naviwatcher/internal/config"
"naviwatcher/internal/database"
"naviwatcher/internal/scanner"
)
// idPattern bounds the {id} path segment accepted by artist routes. Artist IDs
// come from Navidrome (numeric/UUID) and MusicBrainz (UUID), so word
// characters and hyphens cover every legitimate value. Rejecting anything else
// prevents a crafted id (containing "/", control characters, or whitespace)
// from breaking route matching or being reflected into a Location header.
var idPattern = regexp.MustCompile(`^[\w-]+$`)
// isValidID reports whether s is a safe artist-ID path segment.
func isValidID(s string) bool {
return idPattern.MatchString(s)
}
//go:embed templates/*.html
var templates embed.FS
@@ -84,7 +97,7 @@ func (s *Server) handleArtist(w http.ResponseWriter, r *http.Request) {
// The enhanced ServeMux extracts the {id} path segment for us.
id := r.PathValue("id")
if id == "" {
if id == "" || !isValidID(id) {
http.NotFound(w, r)
return
}
@@ -156,7 +169,7 @@ func (s *Server) buildArtistData(ctx context.Context, id string) (*ArtistData, e
// ArchiveData is the view model for the ignored-releases archive page.
type ArchiveData struct {
Releases []MissingReleaseView
Releases []MissingReleaseView
UIBaseURL string
}
@@ -196,15 +209,27 @@ func (s *Server) handleArchive(w http.ResponseWriter, r *http.Request) {
// *config.Config (mirroring how the app constructs other components). It
// forwards the server sub-config and derives uiBaseURL from the configured
// public_url, falling back to a best-effort host:port.
// ResolveUIBaseURL derives the externally-reachable base URL of the Web UI from
// config. An explicit public_url (e.g. behind a reverse proxy) is preferred.
// When unset, it falls back to http://host:port — unless the bind host is the
// unspecified "0.0.0.0" (not reachable from outside the host), in which case an
// empty string is returned so callers omit the link rather than advertise an
// unusable address. The same derivation is used by both the Web UI and the
// Telegram notifier so dashboard links are consistent across surfaces.
func ResolveUIBaseURL(cfg *config.ServerConfig) string {
base := cfg.PublicURL
if base == "" && cfg.Host != "0.0.0.0" && cfg.Host != "" {
base = fmt.Sprintf("http://%s:%d", cfg.Host, cfg.Port)
}
return base
}
func NewServerWithConfig(cfg *config.Config, db *database.DB) *Server {
// Prefer an explicit, externally-reachable public_url (e.g. behind a
// reverse proxy). Fall back to host:port — but if the bind host is the
// unspecified "0.0.0.0", it is not reachable from outside the host, so
// omit the link rather than advertise an unusable address.
base := cfg.Server.PublicURL
if base == "" && cfg.Server.Host != "0.0.0.0" && cfg.Server.Host != "" {
base = fmt.Sprintf("http://%s:%d", cfg.Server.Host, cfg.Server.Port)
}
base := ResolveUIBaseURL(&cfg.Server)
return NewServer(&cfg.Server, db, base, cfg.Scanner.FuzzyThreshold)
}
@@ -219,7 +244,7 @@ func (s *Server) ignoreOrRestore(w http.ResponseWriter, r *http.Request) {
return
}
id := r.PathValue("id")
if id == "" {
if id == "" || !isValidID(id) {
http.NotFound(w, r)
return
}
@@ -270,7 +295,7 @@ func (s *Server) toggleIgnoreSingles(w http.ResponseWriter, r *http.Request) {
return
}
id := r.PathValue("id")
if id == "" {
if id == "" || !isValidID(id) {
http.NotFound(w, r)
return
}
@@ -289,4 +314,3 @@ func (s *Server) toggleIgnoreSingles(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/artist/"+id, http.StatusSeeOther)
}