internal/api/media.go (view raw)
1package api
2
3import (
4 "io/fs"
5 "mime"
6 "net/http"
7 "os"
8 "path/filepath"
9 "strings"
10)
11
12// defaultFavicon is served when no favicon_path is configured.
13var defaultFavicon []byte
14
15// SetDefaultFavicon sets the fallback favicon bytes (called at startup from
16// the embedded web build so the binary doesn't embed a second copy).
17func SetDefaultFavicon(data []byte) {
18 defaultFavicon = data
19}
20
21// InitDefaultFavicon reads the favicon from the embedded web build so it can
22// be served as the fallback for /favicon without an extra embedded copy.
23func InitDefaultFavicon(webFS fs.FS) error {
24 sub, err := fs.Sub(webFS, "web/build")
25 if err != nil {
26 return nil
27 }
28 data, err := fs.ReadFile(sub, "favicon.svg")
29 if err != nil {
30 return nil
31 }
32 SetDefaultFavicon(data)
33 return nil
34}
35
36func (a *API) HandleFavicon(w http.ResponseWriter, r *http.Request) {
37 if a.Cfg.Server.FaviconPath != "" {
38 serveFile(w, r, a.Cfg.Server.FaviconPath)
39 return
40 }
41 if len(defaultFavicon) > 0 {
42 w.Header().Set("Content-Type", "image/svg+xml")
43 w.Header().Set("Cache-Control", "public, max-age=86400")
44 w.Write(defaultFavicon)
45 return
46 }
47 http.NotFound(w, r)
48}
49
50func (a *API) HandleLogo(w http.ResponseWriter, r *http.Request) {
51 if a.Cfg.Server.LogoPath != "" {
52 serveFile(w, r, a.Cfg.Server.LogoPath)
53 return
54 }
55 http.NotFound(w, r)
56}
57
58func serveFile(w http.ResponseWriter, r *http.Request, path string) {
59 ext := strings.ToLower(filepath.Ext(path))
60 mimeType := mime.TypeByExtension(ext)
61 if mimeType == "" {
62 switch ext {
63 case ".svg":
64 mimeType = "image/svg+xml"
65 case ".ico":
66 mimeType = "image/x-icon"
67 default:
68 mimeType = "application/octet-stream"
69 }
70 }
71
72 f, err := os.Open(path)
73 if err != nil {
74 if os.IsNotExist(err) {
75 http.NotFound(w, r)
76 } else {
77 http.Error(w, "internal error", http.StatusInternalServerError)
78 }
79 return
80 }
81 defer f.Close()
82
83 stat, err := f.Stat()
84 if err != nil {
85 http.Error(w, "internal error", http.StatusInternalServerError)
86 return
87 }
88
89 w.Header().Set("Content-Type", mimeType)
90 w.Header().Set("Cache-Control", "public, max-age=86400")
91 http.ServeContent(w, r, filepath.Base(path), stat.ModTime(), f)
92}