all repos — weimar @ afea02cbeb79ac199b088f364eeadd21c25e0ca2

Unnamed repository; edit this file 'description' to name the repository.

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}