feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
Pure-stdlib Go implementation of the whole control plane: same nexusgate.json schema (unknown fields preserved verbatim, explicit null/false tri-states modeled), same HTTP API and error strings, embedded frontend (NG_PUBLIC_DIR override), self-implemented scrypt auth, X25519 Reality keys, panel-hosted keyStore and heartbeat reconciliation included. Version stays 0.6.8. Verified: Go tests 33/33 (scrypt + Reality vectors generated from the Node implementation, subscription goldens byte-identical), Node suite 52/52 untouched, differential E2E against the Node panel 50/50 including two-way DB swap. See panel-go/PARITY.md and panel-go/INSTALL-GO.md.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
// Static file serving: the Go port of lib/http.js serveStatic. Assets are
|
||||
// embedded from embed_public/ (populated by build.sh from ../public);
|
||||
// when NG_PUBLIC_DIR is set, files are read from that directory instead.
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed embed_public
|
||||
var embeddedPublic embed.FS
|
||||
|
||||
const embedRoot = "embed_public"
|
||||
|
||||
var staticMIME = map[string]string{
|
||||
".html": "text/html; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".svg": "image/svg+xml",
|
||||
".json": "application/json; charset=utf-8",
|
||||
}
|
||||
|
||||
// ServeStatic serves pathname from the public root; returns false when no
|
||||
// regular file matches (mirroring serveStatic's false), and an error for
|
||||
// unexpected I/O failures (mirroring its throw).
|
||||
func ServeStatic(w http.ResponseWriter, pathname string) (bool, error) {
|
||||
if dir := os.Getenv("NG_PUBLIC_DIR"); dir != "" {
|
||||
return serveStaticDisk(w, dir, pathname)
|
||||
}
|
||||
relative := "index.html"
|
||||
if pathname != "/" {
|
||||
relative = strings.TrimLeft(pathname, "/")
|
||||
}
|
||||
// Reject any path that would escape the root (JS path.resolve check).
|
||||
if escapesRoot(relative) {
|
||||
return false, nil
|
||||
}
|
||||
name := embedRoot + "/" + relative
|
||||
info, err := fs.Stat(embeddedPublic, name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return false, nil
|
||||
}
|
||||
body, err := fs.ReadFile(embeddedPublic, name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
writeStatic(w, relative, body)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// escapesRoot reports whether a slash-separated relative path climbs above
|
||||
// its root via ".." segments.
|
||||
func escapesRoot(relative string) bool {
|
||||
depth := 0
|
||||
for _, seg := range strings.Split(relative, "/") {
|
||||
switch seg {
|
||||
case "..":
|
||||
if depth == 0 {
|
||||
return true
|
||||
}
|
||||
depth--
|
||||
case "", ".":
|
||||
default:
|
||||
depth++
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func serveStaticDisk(w http.ResponseWriter, publicDir, pathname string) (bool, error) {
|
||||
relative := "index.html"
|
||||
if pathname != "/" {
|
||||
relative = strings.TrimLeft(pathname, "/")
|
||||
}
|
||||
root := filepath.Clean(publicDir)
|
||||
target := filepath.Join(root, filepath.FromSlash(relative))
|
||||
if !strings.HasPrefix(target, root+string(filepath.Separator)) && target != filepath.Join(root, "index.html") {
|
||||
return false, nil
|
||||
}
|
||||
info, err := os.Stat(target)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return false, nil
|
||||
}
|
||||
body, err := os.ReadFile(target)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
writeStatic(w, relative, body)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func writeStatic(w http.ResponseWriter, name string, body []byte) {
|
||||
contentType := staticMIME[path.Ext(name)]
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
h := w.Header()
|
||||
h.Set("Content-Type", contentType)
|
||||
h.Set("Content-Length", strconv.Itoa(len(body)))
|
||||
h.Set("Cache-Control", "no-store")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
Reference in New Issue
Block a user