// 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") } }