mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
// Package imghttp holds the shared HTTP caching contract for artwork responses, so the
|
|
// subsonic, public, and jellyfin image handlers apply identical headers without importing
|
|
// each other.
|
|
package imghttp
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/core/artwork"
|
|
)
|
|
|
|
// WriteImageHeaders applies the artwork caching contract and reports whether a 304 was written
|
|
// (in which case the caller must not write a body). requestedHash is the hash the client asserted
|
|
// (id suffix / JWT payload / jellyfin tag param), or "" when the request carried no hash.
|
|
func WriteImageHeaders(w http.ResponseWriter, r *http.Request, img *artwork.Image, requestedHash string) (wrote304 bool) {
|
|
h := w.Header()
|
|
// Placeholders are transient stand-ins for not-yet-resolved art: never cached, no validators.
|
|
if img.Placeholder {
|
|
h.Set("Cache-Control", "no-store")
|
|
return false
|
|
}
|
|
|
|
h.Set("ETag", `"`+img.Hash+`"`)
|
|
if !img.LastUpdated.IsZero() {
|
|
h.Set("Last-Modified", img.LastUpdated.UTC().Format(http.TimeFormat))
|
|
}
|
|
// Immutable only when the client asked for the exact current hash; bare/legacy/mismatched
|
|
// requests get cheap ETag revalidation instead, which fixes stale art after re-resolution.
|
|
if requestedHash != "" && requestedHash == img.Hash {
|
|
h.Set("Cache-Control", "public, max-age=31536000, immutable")
|
|
} else {
|
|
h.Set("Cache-Control", "public, no-cache")
|
|
}
|
|
|
|
if ifNoneMatch(r.Header.Get("If-None-Match"), img.Hash) {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ifNoneMatch reports whether the If-None-Match header asserts the given hash, using weak
|
|
// comparison (RFC 9110): "*" matches any current representation and W/ prefixes are ignored.
|
|
func ifNoneMatch(header, hash string) bool {
|
|
header = strings.TrimSpace(header)
|
|
if header == "" {
|
|
return false
|
|
}
|
|
if header == "*" {
|
|
return true
|
|
}
|
|
for _, tag := range strings.Split(header, ",") {
|
|
tag = strings.TrimSpace(tag)
|
|
tag = strings.TrimPrefix(tag, "W/")
|
|
if strings.Trim(tag, `"`) == hash {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|