package studio import ( "net/http" "os" ) // Sharing the studio's files with another tool on the same machine. // // World Orogen (Tools/Orogen, D-66) reads the same painting and the same legends this studio edits, and until // now the only way to get them there was a file picker: choose the PNG, choose the legend, choose the overlay, // choose its legend, retype the manifest's numbers. The studio already holds every one of those - the painting // in memory, exactly as the next plan will read it - so it serves them, and Orogen loads a planet with one // button. // // Two rules keep this from turning the studio into something a web page can drive: // // - **Only GET is shared.** The CORS header goes on GET responses and nothing else, and no preflight is ever // answered. A cross-origin POST with a JSON body needs a preflight, so every endpoint that paints, saves, // plans or bakes stays reachable from this page and from nothing else. The studio listens on loopback, but // a browser on the same machine visits other origins all day, and "any tab can start a two-hour bake" is // not a property to give away for a convenience. // - **The files are served as they are on disk.** The legend and the manifest are text somebody wrote, with // commentary; Orogen reads the same keys the plan does and ignores the rest. Nothing is re-marshalled, so // what Orogen sees is byte for byte what `terrain plan` will see. // readOnlyCORS lets any origin *read* the API and touches nothing else. func readOnlyCORS(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet || r.Method == http.MethodHead { w.Header().Set("Access-Control-Allow-Origin", "*") } h.ServeHTTP(w, r) }) } // serveJSONFile answers a GET for one of the planet's JSON files, or says there is none. The POST handlers // call it first and return, so an endpoint that edits a file also hands the file out. func serveJSONFile(w http.ResponseWriter, path string) { if path == "" { http.Error(w, "the manifest names no such file", http.StatusNotFound) return } data, err := os.ReadFile(path) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write(data) }