// Package labdb_static embeds labdb's HTML/CSS/JS into the binary so
// the fivenode_go process ships every byte the browser needs —
// no separate web server, no filesystem dependency.
//
// PRG surface
//
// cBody := LABDB_STATIC_FILE(cPath) -> file contents, or "" when missing
// cMime := LABDB_STATIC_MIME(cPath) -> Content-Type guessed from extension
//
// cPath is taken verbatim from hReq["path"], so "/" maps to index.html
// and a missing leading slash is fine.
package labdb_static
import (
"embed"
"path"
"strings"
"five/hbrt"
)
//go:embed public
var fs embed.FS
func init() {
hbrt.HB_FUNC("LABDB_STATIC_FILE", staticFile)
hbrt.HB_FUNC("LABDB_STATIC_MIME", staticMime)
}
// resolve turns "/" -> "public/index.html",
// "/css/app.css" -> "public/css/app.css",
// "css/app.css" -> "public/css/app.css".
func resolve(p string) string {
p = strings.TrimPrefix(p, "/")
if p == "" {
p = "index.html"
}
// Normalize any "..", "//" so an attacker can't escape the embed root.
cleaned := path.Clean("/" + p)
if cleaned == "/" {
return "public/index.html"
}
return "public" + cleaned
}
func staticFile(ctx *hbrt.HBContext) {
if ctx.PCount() < 1 || !ctx.IsChar(1) {
ctx.RetC("")
return
}
data, err := fs.ReadFile(resolve(ctx.ParC(1)))
if err != nil {
ctx.RetC("")
return
}
ctx.RetC(string(data))
}
func staticMime(ctx *hbrt.HBContext) {
if ctx.PCount() < 1 || !ctx.IsChar(1) {
ctx.RetC("application/octet-stream")
return
}
// Use the resolved path so "/" -> .html, not "" -> octet-stream.
ext := strings.ToLower(path.Ext(resolve(ctx.ParC(1))))
switch ext {
case ".html", ".htm":
ctx.RetC("text/html; charset=utf-8")
case ".css":
ctx.RetC("text/css; charset=utf-8")
case ".js", ".mjs":
ctx.RetC("application/javascript; charset=utf-8")
case ".json":
ctx.RetC("application/json; charset=utf-8")
case ".svg":
ctx.RetC("image/svg+xml")
case ".png":
ctx.RetC("image/png")
case ".jpg", ".jpeg":
ctx.RetC("image/jpeg")
case ".ico":
ctx.RetC("image/x-icon")
default:
ctx.RetC("application/octet-stream")
}
}