// Package imghttp holds the HTTP caching contract shared by the subsonic, public, and jellyfin// image handlers, so they apply identical headers without importing each other.package imghttpimport ("net/http""github.com/navidrome/navidrome/core/artwork""github.com/navidrome/navidrome/utils/req")// WriteImageHeaders applies the artwork caching contract and reports whether a 304 was written// (the caller must then not write a body). requestedHash is the hash the client asserted, or "".func WriteImageHeaders(w http.ResponseWriter, r *http.Request, img *artwork.Image, requestedHash string) (wrote304 bool) {h := w.Header()// Placeholders are transient stand-ins for unresolved art: never cached, no validators.if img.Placeholder {h.Set("Cache-Control", "no-store")return false}// The ETag versions the served representation, so a re-encoding config change invalidates a// revalidating client's cache; full-size originals are their own pixel hash.etag := img.ETagif etag == "" {etag = img.Hash}// An empty validator would be shared by every such response, 304ing clients that echo it back.if etag != "" {h.Set("ETag", `"`+etag+`"`)}if !img.LastUpdated.IsZero() {h.Set("Last-Modified", img.LastUpdated.UTC().Format(http.TimeFormat))}// Immutable only when the client asked for the current pixel hash; anything else revalidates,// so art that gets re-resolved is never pinned.if requestedHash != "" && requestedHash == img.Hash {h.Set("Cache-Control", "public, max-age=31536000, immutable")} else {h.Set("Cache-Control", "public, no-cache")}if etag != "" && req.IfNoneMatch(r, etag) {w.WriteHeader(http.StatusNotModified)return true}return false}