diff options
Diffstat (limited to 'internal/admin/api_files.go')
| -rw-r--r-- | internal/admin/api_files.go | 28 |
1 files changed, 23 insertions, 5 deletions
diff --git a/internal/admin/api_files.go b/internal/admin/api_files.go index 496b08d..f2ef928 100644 --- a/internal/admin/api_files.go +++ b/internal/admin/api_files.go @@ -1,6 +1,7 @@ package admin import ( + "fmt" "io" "net/http" "os" @@ -8,6 +9,17 @@ import ( "strings" ) +// resolveDataPath resolves a request-supplied path inside the admin data +// directory, rejecting paths that would escape it via .. segments. The +// returned path is always filepath.Join(dataDir, cleaned relative path). +func resolveDataPath(dataDir, relPath string) (string, error) { + clean := filepath.Clean(strings.TrimPrefix(strings.TrimPrefix(relPath, "/"), "data/")) + if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("path escapes the data directory: %s", relPath) + } + return filepath.Join(dataDir, clean), nil +} + func (s *AdminServer) handleFileRename(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) @@ -36,7 +48,11 @@ func (s *AdminServer) handleFileRename(w http.ResponseWriter, r *http.Request) { relPath := strings.TrimPrefix(body.Path, "/") relPath = strings.TrimPrefix(relPath, "data/") - oldAbs := filepath.Join(s.dataDir, relPath) + oldAbs, err := resolveDataPath(s.dataDir, relPath) + if err != nil { + writeJSON(w, map[string]any{"error": err.Error()}) + return + } oldContent, err := snapshotFile(oldAbs) if err != nil { @@ -77,11 +93,16 @@ 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/") + fullPath, pathErr := resolveDataPath(s.dataDir, relPath) + if pathErr != nil { + writeJSON(w, map[string]any{"error": pathErr.Error()}) + return + } switch r.Method { case http.MethodGet: if relPath == "" { - entries, err := os.ReadDir(s.dataDir) + entries, err := os.ReadDir(fullPath) if err != nil { writeJSON(w, map[string]any{"error": err.Error()}) return @@ -137,8 +158,6 @@ func (s *AdminServer) handleFiles(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "missing path"}) return } - fullPath := filepath.Join(s.dataDir, relPath) - body, err := io.ReadAll(r.Body) if err != nil { writeJSON(w, map[string]any{"error": "read body: " + err.Error()}) @@ -180,7 +199,6 @@ func (s *AdminServer) handleFiles(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "missing path"}) return } - fullPath := filepath.Join(s.dataDir, relPath) oldContent, _ := snapshotFile(fullPath) if err := os.Remove(fullPath); err != nil { writeJSON(w, map[string]any{"error": "delete error: " + err.Error()}) |
