70 lines
1.4 KiB
Go
70 lines
1.4 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 {
|
||
|
|
sub, err := fs.Sub(uiDist, "ui-dist")
|
||
|
|
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(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
|
||
|
|
}
|