package sdkserver import ( "net/http" "os" "path/filepath" "regexp" "strings" "gac-hkrpg-go/internal/common" ) // Route name is part of this server's HTTP interface, not a machine URL. const luaOverlayRoute = "/gacy-lua-overlay" var luaBundleName = regexp.MustCompile(`^(?:M_LuaV|LuaV_[0-9a-f]{32}|[0-9a-f]{32})\.bytes$`) // Dispatch outside ServeMux: a subtree registration conflicts with the // product-name wildcard used by the existing login endpoints in newer Go. func withLuaOverlay(next http.Handler, cfg common.LuaOverlayConfig) http.Handler { if !cfg.Enabled { return next } overlay := luaOverlayHandler(cfg) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, luaOverlayRoute+"/") { overlay(w, r) return } next.ServeHTTP(w, r) }) } // Serve only the Lua updater's known flat namespace. Stage files win, then the // local hotfix and shipping files; no filesystem path or remote URL is derived // from untrusted input. The original caches remain untouched. func luaOverlayHandler(cfg common.LuaOverlayConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { w.WriteHeader(http.StatusMethodNotAllowed) return } path := strings.TrimPrefix(r.URL.Path, luaOverlayRoute+"/") path = strings.TrimPrefix(path, "client/Windows/") if !luaBundleName.MatchString(path) { http.NotFound(w, r) return } for _, root := range []string{cfg.StageDir, cfg.HotfixDir, cfg.StreamingDir} { file, err := os.Open(filepath.Join(root, path)) if os.IsNotExist(err) { continue } if err != nil { http.Error(w, "Lua overlay read failed", http.StatusInternalServerError) return } info, err := file.Stat() if err != nil || !info.Mode().IsRegular() { file.Close() http.Error(w, "Lua overlay file invalid", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/octet-stream") http.ServeContent(w, r, path, info.ModTime(), file) file.Close() return } http.NotFound(w, r) } }