diff options
Diffstat (limited to 'internal/admin/api_files.go')
| -rw-r--r-- | internal/admin/api_files.go | 22 |
1 files changed, 16 insertions, 6 deletions
diff --git a/internal/admin/api_files.go b/internal/admin/api_files.go index b9f54c5..a19cb3b 100644 --- a/internal/admin/api_files.go +++ b/internal/admin/api_files.go @@ -23,6 +23,20 @@ func resolveDataPath(dataDir, relPath string) (string, error) { return filepath.Join(dataDir, clean), nil } +// canonicalDataRelPath normalizes a client-supplied data-relative path so that +// repeated leading "/" or "data/" prefixes cannot make the security +// classifiers (isPlayerDataFilePath / isAccountFilePath) disagree with the +// path resolveDataPath actually reads. Without this, "//players/..." resolved +// to the real player file while the classifiers saw a non-player path. +func canonicalDataRelPath(relPath string) string { + clean := filepath.ToSlash(filepath.Clean("/" + strings.TrimPrefix(relPath, "/"))) + clean = strings.TrimPrefix(clean, "/") + for strings.HasPrefix(clean, "data/") { + clean = strings.TrimPrefix(clean, "data/") + } + return clean +} + // evictDeletedRoomFile drops cached room state and any cached in-room games // when the raw file manager deletes a room YAML (rooms/<dir>/<id>.yaml). // Best-effort: only numeric .yaml stems under rooms/ count as room files. @@ -75,8 +89,7 @@ func (s *AdminServer) handleFileRename(w http.ResponseWriter, r *http.Request) { } } - relPath := strings.TrimPrefix(body.Path, "/") - relPath = strings.TrimPrefix(relPath, "data/") + relPath := canonicalDataRelPath(body.Path) oldAbs, err := resolveDataPath(s.dataDir, relPath) if err != nil { failJSON(w, err.Error()) @@ -145,9 +158,7 @@ func (s *AdminServer) handleFileRename(w http.ResponseWriter, r *http.Request) { // is currently online is refused: overwriting it would clobber the in-memory // player the game will later save. func (s *AdminServer) handleFiles(w http.ResponseWriter, r *http.Request) { - relPath := r.URL.Query().Get("path") - relPath = strings.TrimPrefix(relPath, "/") - relPath = strings.TrimPrefix(relPath, "data/") + relPath := canonicalDataRelPath(r.URL.Query().Get("path")) fullPath, pathErr := resolveDataPath(s.dataDir, relPath) if pathErr != nil { failJSON(w, pathErr.Error()) @@ -173,7 +184,6 @@ func (s *AdminServer) handleFiles(w http.ResponseWriter, r *http.Request) { return } - fullPath := filepath.Join(s.dataDir, relPath) info, err := os.Stat(fullPath) if err != nil { failJSON(w, "not found: "+relPath) |
