package controller import ( "bytes" htmlpkg "html" "io/fs" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/mhsanaei/3x-ui/v3/internal/config" "github.com/mhsanaei/3x-ui/v3/internal/logger" "github.com/mhsanaei/3x-ui/v3/internal/web/session" ) var distFS fs.FS func SetDistFS(fsys fs.FS) { distFS = fsys } var distPageBuildTime = time.Now() func normalizeWebBasePath(basePath string) string { if basePath == "" { return "/" } if !strings.HasPrefix(basePath, "/") { basePath = "/" + basePath } if !strings.HasSuffix(basePath, "/") { basePath += "/" } return basePath } func pwaHeadInjection(basePath, pageName string) []byte { if pageName != "index.html" && pageName != "login.html" { return nil } basePath = normalizeWebBasePath(basePath) return []byte(``) } func serveDistPage(c *gin.Context, name string) { body, err := fs.ReadFile(distFS, "dist/"+name) if err != nil { c.String(http.StatusInternalServerError, "missing embedded page: %s", name) return } basePath := c.GetString("base_path") if basePath == "" { basePath = "/" } if basePath != "/" { body = bytes.ReplaceAll(body, []byte(`src="/assets/`), []byte(`src="`+basePath+`assets/`)) body = bytes.ReplaceAll(body, []byte(`href="/assets/`), []byte(`href="`+basePath+`assets/`)) } jsEscape := strings.NewReplacer( `\`, `\\`, `"`, `\"`, "\n", `\n`, "\r", `\r`, "<", `<`, ">", `>`, "&", `&`, ) escapedBase := jsEscape.Replace(basePath) csrfToken, err := session.EnsureCSRFToken(c) if err != nil { logger.Warning("Unable to mint CSRF token for", name+":", err) csrfToken = "" } csrfMeta := []byte(``) basePathMeta := []byte(``) nonceAttr := "" if nonce := c.GetString("csp_nonce"); nonce != "" { nonceAttr = ` nonce="` + htmlpkg.EscapeString(nonce) + `"` } script := `` inject := []byte(script) inject = append(inject, csrfMeta...) inject = append(inject, basePathMeta...) inject = append(inject, pwaHeadInjection(basePath, name)...) inject = append(inject, []byte(``)...) out := bytes.Replace(body, []byte(""), inject, 1) c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Pragma", "no-cache") c.Header("Expires", "0") c.Header("Last-Modified", distPageBuildTime.UTC().Format(http.TimeFormat)) c.Data(http.StatusOK, "text/html; charset=utf-8", out) }