diff options
Diffstat (limited to 'internal/admin/api_helpers.go')
| -rw-r--r-- | internal/admin/api_helpers.go | 46 |
1 files changed, 44 insertions, 2 deletions
diff --git a/internal/admin/api_helpers.go b/internal/admin/api_helpers.go index 11ad958..37cc4ae 100644 --- a/internal/admin/api_helpers.go +++ b/internal/admin/api_helpers.go @@ -3,10 +3,13 @@ package admin import ( "bytes" "fmt" - "os" + "path/filepath" "strings" "gopkg.in/yaml.v3" + + "thehouseoficarus/internal/persistence" + "thehouseoficarus/internal/world" ) // validEntityID reports whether an entity ID can be safely joined into a @@ -23,6 +26,45 @@ func validEntityID(id string) bool { return true } +// resolveRoomSubdir validates a client-supplied room subdirectory and returns +// the cleaned path relative to <dataDir>/rooms. Absolute paths, ".." escapes, +// drive-like prefixes, and backslashes are rejected; the empty string is the +// rooms root. Room-directory API handlers must route body.Dir/patch.Dir through +// this before joining, mirroring validEntityID/resolveDataPath elsewhere. +func (s *AdminServer) resolveRoomSubdir(dir string) (string, error) { + dir = strings.TrimSpace(dir) + if dir == "" { + return "", nil + } + if filepath.IsAbs(dir) || strings.ContainsRune(dir, '\\') || strings.ContainsRune(dir, 0) { + return "", fmt.Errorf("invalid directory") + } + clean := filepath.Clean(dir) + if clean == "." { + return "", nil + } + if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("directory escapes the rooms tree") + } + base := filepath.Join(s.dataDir, "rooms") + rel, err := filepath.Rel(base, filepath.Join(base, clean)) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("directory escapes the rooms tree") + } + return clean, nil +} + +// validateRoomSchema decodes a rendered room YAML into world.Room before it is +// written, so the editor cannot persist a room the runtime loader would later +// reject (for example a non-numeric exit target). KnownFields(true) also +// catches keys the runtime would silently ignore. +func validateRoomSchema(content []byte) error { + dec := yaml.NewDecoder(bytes.NewReader(content)) + dec.KnownFields(true) + var room world.Room + return dec.Decode(&room) +} + func yamlToMap(data []byte) (map[string]any, error) { var v any if err := yaml.Unmarshal(data, &v); err != nil { @@ -65,7 +107,7 @@ func writeMapAsYAML(path string, m map[string]any) ([]byte, error) { if err != nil { return nil, err } - if err := os.WriteFile(path, content, 0644); err != nil { + if err := persistence.WriteFile(path, content, 0644); err != nil { return nil, err } return content, nil |
