soteria/internal/server/ui_renderer.go

74 lines
1.5 KiB
Go

package server
import (
"embed"
"fmt"
"io/fs"
"net/http"
"path"
"strings"
)
//go:embed ui-dist/*
var uiDist embed.FS
type uiRenderer struct {
fsys fs.FS
}
func newUIRenderer() *uiRenderer {
return newUIRendererFromFS(uiDist, "ui-dist")
}
func newUIRendererFromFS(root fs.FS, dir string) *uiRenderer {
sub, err := fs.Sub(root, dir)
if err != nil {
return &uiRenderer{}
}
return &uiRenderer{fsys: sub}
}
func (u *uiRenderer) ServeIndex(w http.ResponseWriter, _ *http.Request) error {
if u.fsys == nil {
return fmt.Errorf("UI assets are not available")
}
body, err := fs.ReadFile(u.fsys, "index.html")
if err != nil {
return fmt.Errorf("read index: %w", err)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
_, _ = w.Write(body)
return nil
}
func (u *uiRenderer) ServeAsset(w http.ResponseWriter, r *http.Request) bool {
if u.fsys == nil {
return false
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
return false
}
requestPath := strings.TrimPrefix(r.URL.Path, "/")
if requestPath == "" || requestPath == "." {
return false
}
if strings.HasPrefix(requestPath, "v1/") {
return false
}
cleaned := path.Clean(requestPath)
if cleaned == "." || strings.HasPrefix(cleaned, "../") {
return false
}
if strings.HasSuffix(requestPath, "/") || strings.HasSuffix(cleaned, "/") {
return false
}
if _, err := fs.Stat(u.fsys, cleaned); err != nil {
return false
}
http.FileServer(http.FS(u.fsys)).ServeHTTP(w, r)
return true
}